Spaces:
Runtime error
Runtime error
File size: 16,671 Bytes
777faa3 46eb547 777faa3 9068a73 099b590 554bf13 410897c cccb37c 630cf05 cccb37c 630cf05 410897c 630cf05 cccb37c 630cf05 410897c 630cf05 ee5e227 f83a470 10a08bd f83a470 3836d30 099b590 f83a470 ee5e227 e5308f6 f83a470 90083eb f83a470 099b590 e5308f6 d198451 b849715 d198451 b849715 d198451 b849715 64db771 d198451 b849715 d198451 b849715 56bc054 b849715 fe9821a 099b590 fe9821a 49ff06b f83a470 adaab60 49ff06b adaab60 56bc054 66a6cf5 099b590 f83a470 630cf05 b849715 d198451 b849715 d198451 b849715 3d14943 b849715 099b590 f83a470 099b590 a232672 769ac7d 90083eb 099b590 289d4b9 099b590 f83a470 099b590 f83a470 099b590 f83a470 099b590 777faa3 099b590 9068a73 032cf36 46eb547 cccb37c 46eb547 a4eb24a 9068a73 a4eb24a b35b784 099b590 f83a470 099b590 630cf05 099b590 f83a470 a50eff2 cccb37c f83a470 cccb37c f83a470 099b590 f83a470 099b590 f83a470 099b590 f83a470 099b590 289d4b9 099b590 f83a470 099b590 f83a470 3836d30 099b590 e5308f6 099b590 d198451 3ff4afd f83a470 d198451 4bb0c0a f83a470 099b590 e5308f6 099b590 f83a470 099b590 e5308f6 49ff06b 099b590 f83a470 099b590 8f8d5b4 099b590 9ee7117 099b590 f83a470 099b590 777faa3 d198451 4a93549 099b590 f83a470 099b590 b99285a 099b590 f83a470 099b590 d198451 099b590 af9dd11 099b590 f83a470 099b590 4bb0c0a 099b590 ed5c9a9 | 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 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 | import time
from pprint import pprint
from mastodon import Mastodon, MastodonNotFoundError # Mastodon.py
list_of_servers = [
'hostux.social',
'social.linux.pizza',
'nerdculture.de',
'toot.wales',
'kolektiva.social',
'noc.social',
'seo.chat',
'ioc.exchange',
'glasgow.social',
'mindly.social',
'mstdn.party',
'universeodon.com',
'learningdisability.social',
'ravenation.club',
'home.social',
'mastodon.ie',
'techhub.social',
'mastodon.scot',
'sfba.social',
'mastodon.sdf.org',
'mastodon.lol',
'mstdn.social',
'mas.to',
'newsie.social']
# A HTML blob of text used to define the 'login in with' button
auth_button_text = '''<a href="{}"><button class="w3-button w3-light-grey w3-padding-large w3-section " onclick="document.getElementById('download').style.display='block'">
<i class=""></i> Login with Mastodon! π
</button></a><br>'''
def client_log_in(provided_server = None):
'''
Generates an authenticated Mastodon client for the admin user. Used for retireving the target website URL
'''
if provided_server == None:
global slider_choice
server = slider_choice
else:
server = provided_server
if server != None:
id = "WatchTower Ivory | Mastodon"
secrets = json.loads(os.getenv('secrets_json'))
client_id = secrets[server]["client_id"]
client_secret = secrets[server]["client_secret"]
password = secrets[server]["password"]
username = secrets[server]["email"]
mastodon = Mastodon(
client_id=client_id,client_secret=client_secret,
api_base_url='https://{}'.format(server)
)
access_token = mastodon.log_in(
username=username,
scopes=["write:blocks", "write:mutes","read:search","read:accounts"],
password=password,
redirect_uri="https://user1342-ivory.hf.space/"
)
else:
mastodon = None
return mastodon
def get_auth_url(mastodon,provided_server = None):
'''
Retrieves a URL for the user to visit to auth them with WatchTower.
:param mastodon: A admin masterdon instance.
:return: The target URL.
'''
if provided_server == None:
global slider_choice
server = slider_choice
else:
server = provided_server
secrets = json.loads(os.getenv('secrets_json'))
client_id = secrets[server]["client_id"]
client_secret = secrets[server]["client_secret"]
return mastodon.auth_request_url(client_id=client_id, scopes=["write:blocks", "write:mutes","read:search","read:accounts"],
redirect_uris="https://user1342-ivory.hf.space/")
def login_from_code(code):
'''
Used to create a masterdon client instance based on an authenticated user code (retrieved from the URL).
:param code: The code which will authenticate the user/ WatchTower.
:return: A masterdon client instance signed in as the user.
'''
global slider_choice
server = slider_choice
if server != None:
secrets = json.loads(os.getenv('secrets_json'))
client_id = secrets[server]["client_id"]
client_secret = secrets[server]["client_secret"]
mastodon = Mastodon(
client_id=client_id, client_secret=client_secret,
api_base_url='https://{}'.format(server)
)
mastodon.log_in(code=code,
scopes=["write:blocks", "write:mutes","read:search","read:accounts"],
redirect_uri="https://user1342-ivory.hf.space/")
else:
mastodon = None
return mastodon
# !/usr/bin/env python
# coding: utf-8
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-purple"><b>Remove Unfavorable Messages From Your Mastodon Feed </b></h1>
<p><span class="w3-xlarge">Scroll down to use WatchTower Ivory. β¬ </span> WatchTower is a tool that identifies hate speech, misinformation, and extremist content and blocks/ mutes it from your feed. WatchTower Ivory is the second iteration of WatchTower, designed specifically for Mastodon. 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, choose your Mastodon server, then click 'sign in with Mastodon', after this you'll be taken to the Mastodon server 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://infosec.exchange/@JamesStevenson"><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>
<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>
<br>
<br>
</body>
</html>
'''
# Imports
import json
import os
import gradio as gr
# 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(code):
'''
This function is used for generating a masterdon client object based on the code URL paramiters
:param code
:return: A Masterdon client object
'''
return login_from_code(code)
def block_user(user_id, user, reason):
finished = False
blocked = True
attempts = 0
while not finished:
try:
user ="alcinnz@floss.social"
user_dict = client.account_search(user)
pprint(user_dict)
client.account_block(user_dict[0]["id"])
print("Blocked {} for {}".format(user, reason))
except MastodonNotFoundError as e:
if "Record not found" in str(e):
print("Record not found...")
return False
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 "Violent" in dataset:
# Due to the low number of posts used to aggregate the initial dataset and unreliability of this model,
# the weight has been lowered to represent this.
if user["violence-threshold"]/2 >= threshold:
user_id = str(user["acct"])
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["acct"])
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 Mastodon.
: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 "code" in url_params and client is None:
client = get_client_from_tokens(url_params["code"])
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 Mastodon.
#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 Mastodon and configure your blocking options above."]]
chatbot.value = chat_history
chatbot.update(value=chat_history)
mastodon_auth_button.value = auth_button_text.format(
get_target_website())
mastodon_auth_button.update(
value=auth_button_text.format(
get_target_website()))
return auth_button_text.format(get_target_website(list_of_servers[0]))
# 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];
}
"""
slider_choice = list_of_servers[0]
def update_server(choice):
print("In change server")
global slider_choice
slider_choice = choice
get_target_website()
return auth_button_text.format(get_target_website())
def get_chatbot_text():
return [('Welcome to Watchtower.', 'Log in via Mastodon and configure your blocking options above.')]
def get_target_website(provided_server = None):
'''
A wrapper function used for retrieving the URL a user will use to authenticate WatchTower with Mastodon.
:return: auth url
'''
mastodon = client_log_in(provided_server)
return get_auth_url(mastodon, provided_server)
# The Gradio HTML component used for the 'sign in with Mastodon' button
# The main chunk of code that uses Gradio blocks to create the UI
html_button = None
dropdown = 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 Mastodon 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()
with gr.Row().style():
dropdown = gr.Dropdown(choices=list_of_servers, label="Server", value=list_of_servers[0])
html_button = mastodon_auth_button = gr.HTML(
value=auth_button_text.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=35, interactive=True, label="Threshold Confidence Tolerance").style()
chatbot = gr.Chatbot(label="Watchtower Output", value=[('Welcome to Watchtower.',
'Log in via Mastodon and configure your blocking options above.')]).style(
color_map=["grey", "purple"])
btn = gr.Button("Run WatchTower").style(full_width=True).style()
dropdown.change(fn=update_server, inputs=[dropdown], outputs=html_button)
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://JamesStevenson.me" target="_blank"</a> James Stevenson
<br/>
</p>"""
)
# Setup callback for when page loads (used to set a new Mastodon 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)
|