epicyon/webapp_moderation.py

423 lines
17 KiB
Python
Raw Normal View History

2020-11-10 10:25:21 +00:00
__filename__ = "webapp_moderation.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2021-01-26 10:07:42 +00:00
__version__ = "1.2.0"
2020-11-10 10:25:21 +00:00
__maintainer__ = "Bob Mottram"
2021-09-10 16:14:50 +00:00
__email__ = "bob@libreserver.org"
2020-11-10 10:25:21 +00:00
__status__ = "Production"
2021-06-26 11:27:14 +00:00
__module_group__ = "Moderation"
2020-11-10 10:25:21 +00:00
import os
2021-12-26 14:17:13 +00:00
from utils import is_artist
2021-12-26 18:46:43 +00:00
from utils import is_account_dir
2021-12-26 12:45:03 +00:00
from utils import get_full_domain
2021-12-26 13:27:57 +00:00
from utils import is_editor
2021-12-26 15:13:34 +00:00
from utils import load_json
2021-12-27 22:19:18 +00:00
from utils import get_nickname_from_actor
2021-12-27 19:05:25 +00:00
from utils import get_domain_from_actor
2021-12-26 14:08:58 +00:00
from utils import get_config_param
2021-12-26 10:19:59 +00:00
from utils import local_actor_url
2021-12-29 21:55:09 +00:00
from posts import download_follow_collection
from posts import get_public_post_info
2021-12-28 19:33:29 +00:00
from posts import is_moderator
2021-12-29 21:55:09 +00:00
from webapp_timeline import html_timeline
# from webapp_utils import get_person_avatar_url
from webapp_utils import get_content_warning_button
from webapp_utils import html_header_with_external_style
from webapp_utils import html_footer
2021-12-28 21:55:38 +00:00
from blocking import is_blocked_domain
2021-12-29 21:55:09 +00:00
from blocking import is_blocked
2021-12-28 16:56:57 +00:00
from session import create_session
2020-11-10 10:25:21 +00:00
2021-12-31 23:50:29 +00:00
def html_moderation(css_cache: {}, default_timeline: str,
2021-12-29 21:55:09 +00:00
recent_posts_cache: {}, max_recent_posts: int,
2022-01-04 10:13:28 +00:00
translate: {}, page_number: int, items_per_page: int,
2022-01-02 14:51:02 +00:00
session, base_dir: str, wf_request: {}, person_cache: {},
2022-01-04 10:13:28 +00:00
nickname: str, domain: str, port: int, inbox_json: {},
2021-12-29 21:55:09 +00:00
allow_deletion: bool,
http_prefix: str, project_version: str,
yt_replace_domain: str,
twitter_replacement_domain: str,
show_published_date_only: bool,
newswire: {}, positive_voting: bool,
show_publish_as_icon: bool,
full_width_tl_button_header: bool,
icons_as_buttons: bool,
rss_icon_at_top: bool,
publish_button_at_top: bool,
2022-01-04 10:13:28 +00:00
authorized: bool, moderation_action_str: str,
2021-12-29 21:55:09 +00:00
theme: str, peertube_instances: [],
allow_local_network_access: bool,
text_mode_banner: str,
2021-12-31 21:18:12 +00:00
access_keys: {}, system_language: str,
2021-12-29 21:55:09 +00:00
max_like_count: int,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str) -> str:
2020-11-10 10:25:21 +00:00
"""Show the moderation feed as html
This is what you see when selecting the "mod" timeline
"""
2021-12-26 14:17:13 +00:00
artist = is_artist(base_dir, nickname)
2021-12-31 23:50:29 +00:00
return html_timeline(css_cache, default_timeline,
2021-12-29 21:55:09 +00:00
recent_posts_cache, max_recent_posts,
2022-01-04 10:13:28 +00:00
translate, page_number,
items_per_page, session, base_dir,
2022-01-02 14:51:02 +00:00
wf_request, person_cache,
2022-01-04 10:13:28 +00:00
nickname, domain, port, inbox_json, 'moderation',
2021-12-29 21:55:09 +00:00
allow_deletion, http_prefix,
project_version, True, False,
yt_replace_domain,
twitter_replacement_domain,
show_published_date_only,
newswire, False, False, artist, positive_voting,
show_publish_as_icon,
full_width_tl_button_header,
icons_as_buttons, rss_icon_at_top,
publish_button_at_top,
2022-01-04 10:13:28 +00:00
authorized, moderation_action_str, theme,
2021-12-29 21:55:09 +00:00
peertube_instances, allow_local_network_access,
2021-12-31 21:18:12 +00:00
text_mode_banner, access_keys, system_language,
2021-12-29 21:55:09 +00:00
max_like_count, shared_items_federated_domains,
signing_priv_key_pem, cw_lists, lists_enabled)
def html_account_info(css_cache: {}, translate: {},
base_dir: str, http_prefix: str,
nickname: str, domain: str, port: int,
2022-01-04 10:13:28 +00:00
search_handle: str, debug: bool,
2021-12-29 21:55:09 +00:00
system_language: str, signing_priv_key_pem: str) -> str:
2020-12-09 22:55:15 +00:00
"""Shows which domains a search handle interacts with.
This screen is shown if a moderator enters a handle and selects info
on the moderation screen
"""
2021-12-25 23:03:28 +00:00
signing_priv_key_pem = None
2022-01-04 10:13:28 +00:00
msg_str1 = 'This account interacts with the following instances'
2020-12-09 22:55:15 +00:00
2022-01-04 10:13:28 +00:00
info_form = ''
2021-12-31 21:18:12 +00:00
css_filename = base_dir + '/epicyon-profile.css'
2021-12-25 16:17:53 +00:00
if os.path.isfile(base_dir + '/epicyon.css'):
2021-12-31 21:18:12 +00:00
css_filename = base_dir + '/epicyon.css'
2020-12-09 22:55:15 +00:00
2022-01-04 10:13:28 +00:00
instance_title = \
2021-12-26 14:08:58 +00:00
get_config_param(base_dir, 'instanceTitle')
2022-01-04 10:13:28 +00:00
info_form = \
html_header_with_external_style(css_filename, instance_title, None)
2020-12-09 22:55:15 +00:00
2022-01-04 10:13:28 +00:00
search_nickname = get_nickname_from_actor(search_handle)
search_domain, search_port = get_domain_from_actor(search_handle)
2020-12-09 22:55:15 +00:00
2022-01-04 10:13:28 +00:00
search_handle = search_nickname + '@' + search_domain
search_actor = \
local_actor_url(http_prefix, search_nickname, search_domain)
info_form += \
2020-12-10 10:21:54 +00:00
'<center><h1><a href="/users/' + nickname + '/moderation">' + \
2022-01-04 10:13:28 +00:00
translate['Account Information'] + ':</a> <a href="' + search_actor + \
'">' + search_handle + '</a></h1><br>\n'
2020-12-09 22:55:15 +00:00
2022-01-04 10:13:28 +00:00
info_form += translate[msg_str1] + '</center><br><br>\n'
2020-12-09 22:55:15 +00:00
2021-12-25 21:09:22 +00:00
proxy_type = 'tor'
if not os.path.isfile('/usr/bin/tor'):
2021-12-25 21:09:22 +00:00
proxy_type = None
if domain.endswith('.i2p'):
2021-12-25 21:09:22 +00:00
proxy_type = None
2021-01-10 23:06:38 +00:00
2021-12-28 16:56:57 +00:00
session = create_session(proxy_type)
2021-01-10 23:06:38 +00:00
2022-01-04 10:13:28 +00:00
word_frequency = {}
origin_domain = None
domain_dict = get_public_post_info(session, base_dir,
search_nickname, search_domain,
origin_domain,
proxy_type, search_port,
http_prefix, debug,
__version__, word_frequency,
system_language,
signing_priv_key_pem)
2021-01-10 23:06:38 +00:00
# get a list of any blocked followers
2022-01-04 10:13:28 +00:00
followers_list = \
2021-12-29 21:55:09 +00:00
download_follow_collection(signing_priv_key_pem,
'followers', session,
2022-01-04 10:13:28 +00:00
http_prefix, search_actor, 1, 5, debug)
blocked_followers = []
for follower_actor in followers_list:
follower_nickname = get_nickname_from_actor(follower_actor)
follower_domain, follower_port = get_domain_from_actor(follower_actor)
follower_domain_full = get_full_domain(follower_domain, follower_port)
2021-12-29 21:55:09 +00:00
if is_blocked(base_dir, nickname, domain,
2022-01-04 10:13:28 +00:00
follower_nickname, follower_domain_full):
blocked_followers.append(follower_actor)
2021-01-10 23:06:38 +00:00
# get a list of any blocked following
2022-01-04 10:13:28 +00:00
following_list = \
2021-12-29 21:55:09 +00:00
download_follow_collection(signing_priv_key_pem,
'following', session,
2022-01-04 10:13:28 +00:00
http_prefix, search_actor, 1, 5, debug)
blocked_following = []
for following_actor in following_list:
following_nickname = get_nickname_from_actor(following_actor)
following_domain, following_port = \
get_domain_from_actor(following_actor)
following_domain_full = \
get_full_domain(following_domain, following_port)
2021-12-29 21:55:09 +00:00
if is_blocked(base_dir, nickname, domain,
2022-01-04 10:13:28 +00:00
following_nickname, following_domain_full):
blocked_following.append(following_actor)
2022-01-04 10:13:28 +00:00
info_form += '<div class="accountInfoDomains">\n'
users_path = '/users/' + nickname + '/accountinfo'
ctr = 1
2022-01-04 10:13:28 +00:00
for post_domain, blocked_post_urls in domain_dict.items():
info_form += '<a href="' + \
http_prefix + '://' + post_domain + '" ' + \
2021-07-23 13:50:32 +00:00
'target="_blank" rel="nofollow noopener noreferrer">' + \
2022-01-04 10:13:28 +00:00
post_domain + '</a> '
if is_blocked_domain(base_dir, post_domain):
blocked_posts_links = ''
url_ctr = 0
for url in blocked_post_urls:
if url_ctr > 0:
blocked_posts_links += '<br>'
blocked_posts_links += \
2021-07-23 13:50:32 +00:00
'<a href="' + url + '" ' + \
'target="_blank" rel="nofollow noopener noreferrer">' + \
url + '</a>'
2022-01-04 10:13:28 +00:00
url_ctr += 1
blocked_posts_html = ''
if blocked_posts_links:
block_no_str = 'blockNumber' + str(ctr)
blocked_posts_html = \
get_content_warning_button(block_no_str,
translate,
blocked_posts_links)
2020-12-16 16:46:36 +00:00
ctr += 1
2022-01-04 10:13:28 +00:00
info_form += \
'<a href="' + users_path + '?unblockdomain=' + post_domain + \
'?handle=' + search_handle + '">'
info_form += '<button class="buttonhighlighted"><span>' + \
translate['Unblock'] + '</span></button></a> ' + \
2022-01-04 10:13:28 +00:00
blocked_posts_html + '\n'
2020-12-09 22:55:15 +00:00
else:
2022-01-04 10:13:28 +00:00
info_form += \
'<a href="' + users_path + '?blockdomain=' + post_domain + \
'?handle=' + search_handle + '">'
if post_domain != domain:
info_form += '<button class="button"><span>' + \
translate['Block'] + '</span></button>'
2022-01-04 10:13:28 +00:00
info_form += '</a>\n'
info_form += '<br>\n'
2021-01-10 23:06:38 +00:00
2022-01-04 10:13:28 +00:00
info_form += '</div>\n'
2021-01-10 23:06:38 +00:00
2022-01-04 10:13:28 +00:00
if blocked_following:
blocked_following.sort()
info_form += '<div class="accountInfoDomains">\n'
info_form += '<h1>' + translate['Blocked following'] + '</h1>\n'
info_form += \
'<p>' + \
translate['Receives posts from the following accounts'] + \
2021-01-11 20:46:57 +00:00
':</p>\n'
2022-01-04 10:13:28 +00:00
for actor in blocked_following:
following_nickname = get_nickname_from_actor(actor)
following_domain, following_port = get_domain_from_actor(actor)
following_domain_full = \
get_full_domain(following_domain, following_port)
info_form += '<a href="' + actor + '" ' + \
2021-07-23 13:50:32 +00:00
'target="_blank" rel="nofollow noopener noreferrer">' + \
2022-01-04 10:13:28 +00:00
following_nickname + '@' + following_domain_full + \
'</a><br><br>\n'
2022-01-04 10:13:28 +00:00
info_form += '</div>\n'
2022-01-04 10:13:28 +00:00
if blocked_followers:
blocked_followers.sort()
info_form += '<div class="accountInfoDomains">\n'
info_form += '<h1>' + translate['Blocked followers'] + '</h1>\n'
info_form += \
'<p>' + \
translate['Sends out posts to the following accounts'] + \
2021-01-11 20:46:57 +00:00
':</p>\n'
2022-01-04 10:13:28 +00:00
for actor in blocked_followers:
follower_nickname = get_nickname_from_actor(actor)
follower_domain, follower_port = get_domain_from_actor(actor)
follower_domain_full = \
get_full_domain(follower_domain, follower_port)
info_form += '<a href="' + actor + '" ' + \
2021-07-23 13:50:32 +00:00
'target="_blank" rel="nofollow noopener noreferrer">' + \
2022-01-04 10:13:28 +00:00
follower_nickname + '@' + \
follower_domain_full + '</a><br><br>\n'
info_form += '</div>\n'
if word_frequency:
max_count = 1
for word, count in word_frequency.items():
if count > max_count:
max_count = count
minimum_word_count = int(max_count / 2)
if minimum_word_count >= 3:
info_form += '<div class="accountInfoDomains">\n'
info_form += '<h1>' + translate['Word frequencies'] + '</h1>\n'
word_swarm = ''
2021-01-11 13:42:47 +00:00
ctr = 0
2022-01-04 10:13:28 +00:00
for word, count in word_frequency.items():
if count >= minimum_word_count:
2021-01-11 13:42:47 +00:00
if ctr > 0:
2022-01-04 10:13:28 +00:00
word_swarm += ' '
if count < max_count - int(max_count / 4):
word_swarm += word
2021-01-11 13:42:47 +00:00
else:
2022-01-04 10:13:28 +00:00
if count != max_count:
word_swarm += '<b>' + word + '</b>'
2021-01-11 13:42:47 +00:00
else:
2022-01-04 10:13:28 +00:00
word_swarm += '<b><i>' + word + '</i></b>'
2021-01-11 13:42:47 +00:00
ctr += 1
2022-01-04 10:13:28 +00:00
info_form += word_swarm
info_form += '</div>\n'
2021-01-11 13:42:47 +00:00
2022-01-04 10:13:28 +00:00
info_form += html_footer()
return info_form
2020-12-09 22:55:15 +00:00
2021-12-29 21:55:09 +00:00
def html_moderation_info(css_cache: {}, translate: {},
base_dir: str, http_prefix: str,
nickname: str) -> str:
2022-01-04 10:13:28 +00:00
msg_str1 = \
2020-11-10 10:25:21 +00:00
'These are globally blocked for all accounts on this instance'
2022-01-04 10:13:28 +00:00
msg_str2 = \
2020-11-10 10:25:21 +00:00
'Any blocks or suspensions made by moderators will be shown here.'
2020-12-09 22:55:15 +00:00
2022-01-04 10:13:28 +00:00
info_form = ''
2021-12-31 21:18:12 +00:00
css_filename = base_dir + '/epicyon-profile.css'
2021-12-25 16:17:53 +00:00
if os.path.isfile(base_dir + '/epicyon.css'):
2021-12-31 21:18:12 +00:00
css_filename = base_dir + '/epicyon.css'
2020-11-10 10:25:21 +00:00
2022-01-04 10:13:28 +00:00
instance_title = \
2021-12-26 14:08:58 +00:00
get_config_param(base_dir, 'instanceTitle')
2022-01-04 10:13:28 +00:00
info_form = html_header_with_external_style(css_filename,
instance_title, None)
2020-11-10 10:25:21 +00:00
2022-01-04 10:13:28 +00:00
info_form += \
2020-12-09 19:17:42 +00:00
'<center><h1><a href="/users/' + nickname + '/moderation">' + \
2020-11-12 17:05:38 +00:00
translate['Moderation Information'] + \
2020-12-09 22:55:15 +00:00
'</a></h1></center><br>'
2020-11-10 10:25:21 +00:00
2022-01-04 10:13:28 +00:00
info_shown = False
2020-12-20 11:31:29 +00:00
accounts = []
2022-01-04 10:13:28 +00:00
for _, dirs, _ in os.walk(base_dir + '/accounts'):
for acct in dirs:
2021-12-26 18:46:43 +00:00
if not is_account_dir(acct):
continue
2020-12-20 11:31:29 +00:00
accounts.append(acct)
break
2020-12-20 11:31:29 +00:00
accounts.sort()
cols = 5
if len(accounts) > 10:
2022-01-04 10:13:28 +00:00
info_form += '<details><summary><b>' + translate['Show Accounts']
info_form += '</b></summary>\n'
info_form += '<div class="container">\n'
info_form += '<table class="accountsTable">\n'
info_form += ' <colgroup>\n'
for col in range(cols):
2022-01-04 10:13:28 +00:00
info_form += ' <col span="1" class="accountsTableCol">\n'
info_form += ' </colgroup>\n'
info_form += '<tr>\n'
2020-12-20 11:31:29 +00:00
col = 0
for acct in accounts:
2022-01-04 10:13:28 +00:00
acct_nickname = acct.split('@')[0]
account_dir = os.path.join(base_dir + '/accounts', acct)
actor_json = load_json(account_dir + '.json')
2021-12-26 10:29:52 +00:00
if not actor_json:
2020-12-20 11:31:29 +00:00
continue
2021-12-26 10:29:52 +00:00
actor = actor_json['id']
2022-01-04 10:13:28 +00:00
avatar_url = ''
2020-12-20 13:28:33 +00:00
ext = ''
2021-12-26 10:29:52 +00:00
if actor_json.get('icon'):
if actor_json['icon'].get('url'):
2022-01-04 10:13:28 +00:00
avatar_url = actor_json['icon']['url']
if '.' in avatar_url:
ext = '.' + avatar_url.split('.')[-1]
acct_url = \
2020-12-20 11:31:29 +00:00
'/users/' + nickname + '?options=' + actor + ';1;' + \
2022-01-04 10:13:28 +00:00
'/members/' + acct_nickname + ext
info_form += '<td>\n<a href="' + acct_url + '">'
info_form += '<img loading="lazy" style="width:90%" '
info_form += 'src="' + avatar_url + '" />'
info_form += '<br><center>'
if is_moderator(base_dir, acct_nickname):
info_form += '<b><u>' + acct_nickname + '</u></b>'
2020-12-20 12:38:47 +00:00
else:
2022-01-04 10:13:28 +00:00
info_form += acct_nickname
if is_editor(base_dir, acct_nickname):
info_form += ''
info_form += '</center></a>\n</td>\n'
2020-12-20 11:31:29 +00:00
col += 1
if col == cols:
# new row of accounts
2022-01-04 10:13:28 +00:00
info_form += '</tr>\n<tr>\n'
info_form += '</tr>\n</table>\n'
info_form += '</div>\n'
if len(accounts) > 10:
2022-01-04 10:13:28 +00:00
info_form += '</details>\n'
suspended_filename = base_dir + '/accounts/suspended.txt'
if os.path.isfile(suspended_filename):
with open(suspended_filename, 'r') as fp_sus:
suspended_str = fp_sus.read()
info_form += '<div class="container">\n'
info_form += ' <br><b>' + \
2020-11-12 17:05:38 +00:00
translate['Suspended accounts'] + '</b>'
2022-01-04 10:13:28 +00:00
info_form += ' <br>' + \
2020-11-12 17:05:38 +00:00
translate['These are currently suspended']
2022-01-04 10:13:28 +00:00
info_form += \
2020-11-12 17:05:38 +00:00
' <textarea id="message" ' + \
2021-02-28 14:26:04 +00:00
'name="suspended" style="height:200px" spellcheck="false">' + \
2022-01-04 10:13:28 +00:00
suspended_str + '</textarea>\n'
info_form += '</div>\n'
info_shown = True
blocking_filename = base_dir + '/accounts/blocking.txt'
if os.path.isfile(blocking_filename):
with open(blocking_filename, 'r') as fp_block:
blocked_str = fp_block.read()
info_form += '<div class="container">\n'
info_form += \
2020-11-12 17:05:38 +00:00
' <br><b>' + \
translate['Blocked accounts and hashtags'] + '</b>'
2022-01-04 10:13:28 +00:00
info_form += \
2020-11-12 17:05:38 +00:00
' <br>' + \
2022-01-04 10:13:28 +00:00
translate[msg_str1]
info_form += \
2020-11-12 17:05:38 +00:00
' <textarea id="message" ' + \
2021-02-28 14:26:04 +00:00
'name="blocked" style="height:700px" spellcheck="false">' + \
2022-01-04 10:13:28 +00:00
blocked_str + '</textarea>\n'
info_form += '</div>\n'
info_shown = True
filters_filename = base_dir + '/accounts/filters.txt'
if os.path.isfile(filters_filename):
with open(filters_filename, 'r') as fp_filt:
filtered_str = fp_filt.read()
info_form += '<div class="container">\n'
info_form += \
' <br><b>' + \
translate['Filtered words'] + '</b>'
2022-01-04 10:13:28 +00:00
info_form += \
' <textarea id="message" ' + \
2021-02-28 14:26:04 +00:00
'name="filtered" style="height:700px" spellcheck="true">' + \
2022-01-04 10:13:28 +00:00
filtered_str + '</textarea>\n'
info_form += '</div>\n'
info_shown = True
2022-01-04 10:13:28 +00:00
if not info_shown:
info_form += \
2020-11-12 17:05:38 +00:00
'<center><p>' + \
2022-01-04 10:13:28 +00:00
translate[msg_str2] + \
'</p></center>\n'
2022-01-04 10:13:28 +00:00
info_form += html_footer()
return info_form