File size: 15,507 Bytes
abc6d3e
 
 
 
 
fd1b24a
abc6d3e
 
fd1b24a
abc6d3e
 
06188f8
abc6d3e
 
 
 
 
 
f7b8f12
927c4f3
3d7ef93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9dd1fca
 
 
1566af8
abc6d3e
 
 
3d7ef93
 
 
 
 
 
abc6d3e
3d7ef93
 
 
 
 
 
abc6d3e
3d7ef93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1a124d9
82e6b2b
 
 
abc6d3e
82e6b2b
abc6d3e
82e6b2b
9414e21
abc6d3e
fd1b24a
9414e21
fd1b24a
 
1a124d9
abc6d3e
9414e21
abc6d3e
 
 
 
9414e21
abc6d3e
 
 
 
 
 
 
 
fd1b24a
abc6d3e
 
 
563d741
abc6d3e
 
 
 
 
 
 
 
 
 
 
 
 
1cd7d07
06188f8
 
a9f3875
22704b7
06188f8
abc6d3e
b15d357
abc6d3e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a9f3875
9414e21
fd1b24a
9414e21
 
 
 
 
 
 
fd1b24a
1cd7d07
fd1b24a
 
62858ef
fd1b24a
 
1cd7d07
fd1b24a
62858ef
abc6d3e
 
 
 
f26110a
abc6d3e
 
 
 
 
 
 
f26110a
abc6d3e
 
 
 
 
 
 
 
 
 
 
1cd7d07
fd1b24a
1cd7d07
723c207
9414e21
 
abc6d3e
9414e21
 
 
 
 
1cd7d07
723c207
 
 
 
 
abc6d3e
 
fd1b24a
723c207
768a35d
06188f8
 
 
 
 
 
 
 
 
 
 
723c207
abc6d3e
06188f8
723c207
 
 
fd1b24a
723c207
 
 
 
 
8ef1d8a
723c207
 
1cd7d07
fd1b24a
 
1cd7d07
723c207
fd1b24a
 
 
1cd7d07
723c207
9414e21
1a124d9
9414e21
 
1a124d9
9414e21
768a35d
9414e21
1a124d9
9414e21
 
abc6d3e
9414e21
 
 
fd1b24a
 
 
 
 
1a124d9
8ef1d8a
fd1b24a
1cd7d07
723c207
abc6d3e
1cd7d07
723c207
 
7e78c4e
abc6d3e
9414e21
abc6d3e
 
9414e21
 
abc6d3e
 
768a35d
1a124d9
 
9414e21
723c207
 
 
 
 
 
 
 
16e7310
3d2f56d
abc6d3e
1a124d9
abc6d3e
9414e21
abc6d3e
 
9414e21
abc6d3e
 
 
 
 
c1493db
abc6d3e
c1493db
1a124d9
abc6d3e
7e78c4e
9414e21
82e6b2b
fd1b24a
abc6d3e
 
fd1b24a
 
 
 
 
723c207
fd1b24a
abc6d3e
 
3d7ef93
9dd1fca
 
1a124d9
abc6d3e
1a124d9
 
9dd1fca
 
abc6d3e
 
 
 
9dd1fca
abc6d3e
 
1a124d9
abc6d3e
 
 
7cb6d47
01b9d96
9dd1fca
1a124d9
 
768a35d
 
 
abc6d3e
768a35d
 
 
 
abc6d3e
f6ab2d7
95cefa6
ec5258e
95cefa6
3d2f56d
 
 
9dd1fca
abc6d3e
9dd1fca
3fb64c6
abc6d3e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
#!/usr/bin/env python
# coding: utf-8
import json
import os
import re
import time
from random import random
import socket

from threading import Thread
from time import sleep

# Twitter keys
consumer_token = os.getenv('CONSUMER_TOKEN')
consumer_secret = os.getenv('CONSUMER_SECRET')
my_access_token = os.getenv('ACCESS_TOKEN')
my_access_secret = os.getenv('ACCESS_SECRET')
bearer = os.getenv('BEARER')

html_data = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
body,h1,h2,h3,h4,h5 {font-family: "Poppins", sans-serif}
body {font-size: 16px;}
img {margin-bottom: -8px;}
.mySlides {display: none;}
</style>
</head>
<body class="w3-content w3-black" style="max-width:1500px;">

<!-- The App Section -->

<div class="w3-padding-large w3-white">
  <div class="w3-row-padding-large">
    <div class="w3-col">
      <h1 class="w3-jumbo"><b>WatchTower 🐦🚧</b></h1>
      <h1 class="w3-xxxlarge w3-text-blue"><b>Remove Unfavorable Tweets From Your Feed </b></h1>
      <p><span class="w3-xlarge">Scroll down to use WatchTower 1.0. ⬇ </span> WatchTower is a tool that identifies hate speech, misinformation, and extremist content and blocks/ mutes it from your Twitter feed. WatchTower blocks content based on it's current database, so make sure to come back regularly to ensure you're up to date! We use a queue system, which means <b> you may need to wait your turn to run WatchTower</b> - however, once you've clicked run, you can close the tab as WatchTower will continue in the background. WatchTower is simple to use: first scroll down the page and click the 'sign in with Twitter' button, then you'll be taken to the Twitter website and asked to verify yourself, after this you'll be taken back here, then simply scroll down to the bottom of the page and click run!</p>
      <a href="https://www.watchtower.cartographer.one/"><button class="w3-button w3-light-grey w3-padding-large w3-section " onclick="document.getElementById('download').style.display='block'">
        <i class=""></i> Find Out More! 💬
      </button></a>
      <a href="https://ko-fi.com/jamesstevenson"><button class="w3-button w3-light-grey w3-padding-large w3-section " onclick="document.getElementById('download').style.display='block'">
        <i class=""></i> Support The Creator! ❤
      </button></a>
            <a href="https://twitter.com/WATCHTOWER_WEB"><button class="w3-button w3-light-grey w3-padding-large w3-section " onclick="document.getElementById('download').style.display='block'">
        <i class=""></i> Follow Us! 🐦
      </button></a>
    </div>
  </div>
</div>

<!-- Modal -->
<script>
// Slideshow
var slideIndex = 1;
showDivs(slideIndex);

function plusDivs(n) {
  showDivs(slideIndex += n);
}

function showDivs(n) {
  var i;
  var x = document.getElementsByClassName("mySlides");
  if (n > x.length) {slideIndex = 1}
  if (n < 1) {slideIndex = x.length}
  for (i = 0; i < x.length; i++) {
    x[i].style.display = "none";  
  }
  x[slideIndex-1].style.display = "block";  
}
</script>
<br>
</body>
</html>

'''

# Imports
import json
import os
import time
import gradio as gr
import tweepy

# Setup the gradio block and add some generic CSS
block = gr.Blocks(css=".container { max-width: 800px; margin: auto; } h1 { margin: 0px; padding: 5px 0; line-height: 50px; font-size: 60pt; }.close-heading {margin: 0px; padding: 0px;} .close-heading p { margin: 0px; padding: 0px;}", title="WatchTower")

# Chat history variable used for the chatbot prompt on the 'getting started' page.
chat_history = []


def get_client_from_tokens(oauth_verifier, oauth_token):
    '''
    This function is used for generating a Tweepy client object based on Oauth verifier and token paramiters
    :param oauth_verifier:
    :param oauth_token:
    :return: A Tweepy client object
    '''
    new_oauth1_user_handler = tweepy.OAuth1UserHandler(
        consumer_token, consumer_secret,
        callback="https://hf.space/embed/User1342/WatchTower/"
    )
    new_oauth1_user_handler.request_token = {
        "oauth_token": oauth_token,
        "oauth_token_secret": consumer_secret
    }

    access_token, access_token_secret = new_oauth1_user_handler.get_access_token(
        oauth_verifier
    )

    their_client = tweepy.Client(
        bearer_token=bearer,
        consumer_key=consumer_token,
        consumer_secret=consumer_secret,
        access_token=access_token,
        access_token_secret=access_token_secret
    )

    # TODO: The below is not necessary and can be removed.
    global client
    client = their_client

    return their_client

def block_user(user_id, user, reason):
    finished = False
    blocked = True
    attempts = 0
    while not finished:
       
        try:
            print("preparing to block {}".format(user_id))
            client.block(target_user_id=user_id)
            print("User blocked")
        except tweepy.errors.TooManyRequests as e:
            try:
                client.mute(target_user_id=user_id)
                print("Could not block, so muted")
            except tweepy.errors.TooManyRequests as e:
                if attempts == 0:
                    print("waiting 15 minutes for rate limit to finish")
                    time.sleep(900)
                    attempts = attempts + 1
                    continue
                else:
                    finished = True
                    blocked = False
                    continue
            except tweepy.errors.BadRequest as e:
                print("bad request error")
                print(e)
                finished = True
                blocked = False
                continue
        except tweepy.errors.BadRequest as e:
            print("bad request error")
            print(e)
            finished = True
            blocked = False
            continue
        except:
            time.sleep(240)
            continue
        #time.sleep(1)
        finished = True
        try:
            me = client.get_me()
            print("{} blocked {}, for {}".format(me.data["username"], user, reason))
        except tweepy.errors.TooManyRequests as e:
            print("Blocked {}, for {}".format(user, reason))
        return blocked

def block_users(client, threshold, dataset):
    '''
    Used for blocking a series of users based on the threshold and datasets provided. Here the users folder is used.
    :param client:
    :param threshold:
    :param dataset:
    :return: The number of blocked users.
    '''
    num_users_blocked = 0

    for filename in os.listdir("users"):
        filename = os.path.join("users", filename)
        print("File {} open".format(filename))
        user_file = open(filename, "r")
        users = json.load(user_file)

        for user in users:
            print("Reviewing user {}".format(user))

            if "threshold" in user:
                # old type of dataset being used, only 'violent' data available
                if "Violent" in dataset:
                    if user["threshold"]/2 >= threshold:

                        user_id = str(user["username"])
                        if block_user(user_id, user, "Violent - old dataset"):
                            num_users_blocked = num_users_blocked + 1
            else:
                # modern dataset being used
                if "Violent" in dataset:
                    if user["violence-threshold"]/2 >= threshold:
                        user_id = str(user["username"])
                        if block_user(user_id, user, "Violent"):
                            num_users_blocked = num_users_blocked + 1
                        continue
                if "Hate Speech"  in dataset:
                    if user["toxicity-threshold"] >= threshold:
                        user_id = str(user["username"])
                        if block_user(user_id, user, "Hate Speech"):
                            num_users_blocked = num_users_blocked + 1
                        continue


    return num_users_blocked


def chat(selected_option=None, radio_score=None, url_params=None):
    '''
    This function is used to initialise blocking users once the user has authenticated with Twitter.
    :param selected_option:
    :param radio_score:
    :param url_params:
    :return: the chatbot history is returned (including information on blocked accounts).
    '''

    global client
    global chat_history
    history = []

    # app id
    if "oauth_verifier" in url_params and "oauth_token" in url_params and client is None:
        client = get_client_from_tokens(url_params["oauth_verifier"], url_params["oauth_token"])
    if radio_score != None and selected_option != None:

        if client != None:

            # Extract the list to a string representation
            if type(selected_option) is list:
                block_type = ""
                for b_type in selected_option:
                    block_type = block_type + " + " + b_type.capitalize()
                block_type = "'" + block_type[3:] + "'"
            else:
                block_type = selected_option

            # Display to user, set options
            history.append(
                ["Model tuned to a '{}%' threshold and is using the {} dataset.".format(radio_score, block_type.capitalize()),
                 "{} Account blocking initialised".format(block_type.capitalize())])
            num_users_blocked = block_users(client, radio_score, selected_option)
            history.append(
                ["Blocked {} user account(s).".format(num_users_blocked), "Thank you for using Watchtower."])
    elif radio_score != None or selected_option != None:
        chat_history.append(["Initialisation error!", "Please tune the model by using the above options"])

    history = chat_history + history
    chatbot.value = history
    chatbot.update(value=history)
    client = None
    return history


def infer(prompt):
    pass


have_initialised = False
client = None
name = None


def button_pressed(slider_value, url_params):
    # print(url_params)
    return [None, chat(radio.value, slider_value, url_params)]


# The website that the user will visit to authenticate WatchTower.
target_website = None


def update_target_website():
    '''
    Updates the URL used to authenticate WatchTower with Twitter.
    #TODO this function is full of old code and can be optimised.
    :return:
    '''
    global have_initialised
    global chatbot
    global chat_history
    global client
    global name

    client = None
    name = "no username"

    chat_history = [
        ["Welcome to Watchtower.".format(name), "Log in via Twitter and configure your blocking options above."]]

    chatbot.value = chat_history
    chatbot.update(value=chat_history)

    twitter_auth_button.value = '<a href={}><img src="https://cdn.cms-twdigitalassets.com/content/dam/developer-twitter/auth-docs/sign-in-with-twitter-gray.png.twimg.1920.png" alt="Log In With Twitter"></a><br>'.format(
        get_target_website())
    twitter_auth_button.update(
        value='<a href={}><img src="https://cdn.cms-twdigitalassets.com/content/dam/developer-twitter/auth-docs/sign-in-with-twitter-gray.png.twimg.1920.png" alt="Log In With Twitter"></a><br>'.format(
            get_target_website()))

    return '<a href={}><img src="https://cdn.cms-twdigitalassets.com/content/dam/developer-twitter/auth-docs/sign-in-with-twitter-gray.png.twimg.1920.png" alt="Log In With Twitter"></a><br>'.format(
        get_target_website())


# The below is a JS blob used to retrieve the URL params.
# Thanks to here: https://discuss.huggingface.co/t/hugging-face-and-gradio-url-paramiters/21110/2
get_window_url_params = """
    function(text_input, url_params) {
        console.log(text_input, url_params);
        const params = new URLSearchParams(window.location.search);
        url_params = Object.fromEntries(params);
        return [text_input, url_params];
        }
    """

def get_chatbot_text():
    return [('Welcome to Watchtower.', 'Log in via Twitter and configure your blocking options above.')]

def get_target_website():
    '''
    A wrapper function used for retrieving the URL a user will use to authenticate WatchTower with Twitter.
    :return:
    '''
    oauth1_user_handler = tweepy.OAuth1UserHandler(
        consumer_token, consumer_secret,
        callback="https://hf.space/embed/User1342/WatchTower/"
    )
    target_website = oauth1_user_handler.get_authorization_url(signin_with_twitter=True)

    return target_website


# The Gradio HTML component used for the 'sign in with Twitter' button

# The main chunk of code that uses Gradio blocks to create the UI
html_button = None
with block:


    gr.HTML('''
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
''')

    # todo check if user signed in

    user_message = "Log in via Twitter and configure your blocking options above."
    chat_history.append(["Welcome to Watchtower.", user_message])
    gr.HTML(value=html_data)
    with gr.Group():
        with gr.Row().style(equal_height=True):
            with gr.Box():
                #gr.Label(value="WatchTower", visible=True, interactive=False)
                url_params = gr.JSON({}, visible=False, label="URL Params").style(
                )
                text_input = gr.Text(label="Input", visible=False).style()
                text_output = gr.Text(label="Output", visible=False).style()
                html_button = twitter_auth_button = gr.HTML(
                    value='<a href={}><img src="https://cdn.cms-twdigitalassets.com/content/dam/developer-twitter/auth-docs/sign-in-with-twitter-gray.png.twimg.1920.png" alt="Log In With Twitter"></a><br>'.format(
                        get_target_website())).style(
                )
                with gr.Row().style(equal_height=True):
                    radio = gr.CheckboxGroup(value=["Violent", "Hate Speech"], choices=["Violent", "Hate Speech", "Misinformation"],
                                                 interactive=False, label="Behaviour To Block").style()

                    slider = gr.Slider(value=30, interactive=True, label="Threshold Confidence Tolerance")

                chatbot = gr.Chatbot(label="Watchtower Output", value=get_chatbot_text()).style(color_map=["blue","grey"])


                btn = gr.Button("Run WatchTower").style(full_width=True).style()
    btn.click(fn=button_pressed, inputs=[slider, url_params],
              outputs=[text_output, chatbot], _js=get_window_url_params)
    gr.Markdown(
        """___
   <p style='text-align: center'>
   Created by <a href="https://twitter.com/_JamesStevenson" target="_blank"</a> James Stevenson
   <br/>
   </p>"""
    )

# Setup callback for when page loads (used to set a new Twitter auth target webspage)
block.__enter__()
block.set_event_trigger(
    event_name="load", fn=update_target_website, inputs=None, outputs=[html_button], no_target=True
)
block.set_event_trigger(
    event_name="load", fn=get_chatbot_text, inputs=None, outputs=[chatbot], no_target=True
)

#block.attach_load_events()

# Launcg the page
block.launch(enable_queue = True)