__filename__ = "webapp_moderation.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.5.0" __maintainer__ = "Bob Mottram" __email__ = "bob@libreserver.org" __status__ = "Production" __module_group__ = "Moderation" import os from utils import get_url_from_post from utils import remove_html from utils import is_artist from utils import is_account_dir from utils import get_full_domain from utils import is_editor from utils import load_json from utils import get_nickname_from_actor from utils import get_domain_from_actor from utils import get_config_param from utils import local_actor_url from utils import remove_eol from posts import download_follow_collection from posts import get_public_post_info from posts import is_moderator from webapp_timeline import html_timeline # from webapp_utils import get_person_avatar_url from webapp_utils import get_banner_file from webapp_utils import get_content_warning_button from webapp_utils import html_header_with_external_style from webapp_utils import html_footer from blocking import get_global_block_reason from blocking import is_blocked_domain from blocking import is_blocked from session import create_session def html_moderation(default_timeline: str, recent_posts_cache: {}, max_recent_posts: int, translate: {}, page_number: int, items_per_page: int, session, base_dir: str, wf_request: {}, person_cache: {}, nickname: str, domain: str, port: int, inbox_json: {}, 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, authorized: bool, moderation_action_str: str, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, access_keys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str, timezone: str, bold_reading: bool, dogwhistles: {}, ua_str: str, min_images_for_accounts: [], reverse_sequence: bool, buy_sites: {}, auto_cw_cache: {}) -> str: """Show the moderation feed as html This is what you see when selecting the "mod" timeline """ artist = is_artist(base_dir, nickname) return html_timeline(default_timeline, recent_posts_cache, max_recent_posts, translate, page_number, items_per_page, session, base_dir, wf_request, person_cache, nickname, domain, port, inbox_json, 'moderation', 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, authorized, moderation_action_str, theme, peertube_instances, allow_local_network_access, text_mode_banner, access_keys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled, timezone, bold_reading, dogwhistles, ua_str, min_images_for_accounts, reverse_sequence, None, buy_sites, auto_cw_cache) def html_account_info(translate: {}, base_dir: str, http_prefix: str, nickname: str, domain: str, search_handle: str, debug: bool, system_language: str, signing_priv_key_pem: str, back_url: str, block_federated: []) -> str: """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 """ signing_priv_key_pem = None msg_str1 = 'This account interacts with the following instances' info_form = '' css_filename = base_dir + '/epicyon-profile.css' if os.path.isfile(base_dir + '/epicyon.css'): css_filename = base_dir + '/epicyon.css' instance_title = \ get_config_param(base_dir, 'instanceTitle') info_form = \ html_header_with_external_style(css_filename, instance_title, None) search_nickname = get_nickname_from_actor(search_handle) if not search_nickname: return '' search_domain, search_port = get_domain_from_actor(search_handle) if not search_domain: return '' search_handle = search_nickname + '@' + search_domain search_actor = \ local_actor_url(http_prefix, search_nickname, search_domain) if not back_url: back_url = '/users/' + nickname + '/moderation' info_form += \ '

' + \ translate['Account Information'] + ': ' + search_handle + '


\n' info_form += translate[msg_str1] + '


\n' proxy_type = 'tor' if not os.path.isfile('/usr/bin/tor'): proxy_type = None if domain.endswith('.i2p'): proxy_type = None session = create_session(proxy_type) 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) # get a list of any blocked followers followers_list = \ download_follow_collection(signing_priv_key_pem, 'followers', session, http_prefix, search_actor, 1, 5, debug) blocked_followers = [] for follower_actor in followers_list: follower_nickname = get_nickname_from_actor(follower_actor) if not follower_nickname: return '' follower_domain, follower_port = get_domain_from_actor(follower_actor) if not follower_domain: return '' follower_domain_full = get_full_domain(follower_domain, follower_port) if is_blocked(base_dir, nickname, domain, follower_nickname, follower_domain_full, None, block_federated): blocked_followers.append(follower_actor) # get a list of any blocked following following_list = \ download_follow_collection(signing_priv_key_pem, 'following', session, http_prefix, search_actor, 1, 5, debug) blocked_following = [] for following_actor in following_list: following_nickname = get_nickname_from_actor(following_actor) if not following_nickname: return '' following_domain, following_port = \ get_domain_from_actor(following_actor) if not following_domain: return '' following_domain_full = \ get_full_domain(following_domain, following_port) if is_blocked(base_dir, nickname, domain, following_nickname, following_domain_full, None, block_federated): blocked_following.append(following_actor) info_form += '
\n' users_path = '/users/' + nickname + '/accountinfo' ctr = 1 for post_domain, blocked_post_urls in domain_dict.items(): info_form += '' + \ post_domain + ' ' if is_blocked_domain(base_dir, post_domain, None, block_federated): blocked_posts_links = '' url_ctr = 0 for url in blocked_post_urls: if url_ctr > 0: blocked_posts_links += '
' blocked_posts_links += \ '' + \ url + '' 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) ctr += 1 info_form += \ '' info_form += ' ' + \ blocked_posts_html + '\n' else: info_form += \ '' if post_domain != domain: info_form += '' info_form += '\n' info_form += '
\n' info_form += '
\n' if blocked_following: blocked_following.sort() info_form += '
\n' info_form += '

' + translate['Blocked following'] + '

\n' info_form += \ '

' + \ translate['Receives posts from the following accounts'] + \ ':

\n' for actor in blocked_following: following_nickname = get_nickname_from_actor(actor) if not following_nickname: return '' following_domain, following_port = get_domain_from_actor(actor) if not following_domain: return '' following_domain_full = \ get_full_domain(following_domain, following_port) info_form += '' + \ following_nickname + '@' + following_domain_full + \ '

\n' info_form += '
\n' if blocked_followers: blocked_followers.sort() info_form += '
\n' info_form += '

' + translate['Blocked followers'] + '

\n' info_form += \ '

' + \ translate['Sends out posts to the following accounts'] + \ ':

\n' for actor in blocked_followers: follower_nickname = get_nickname_from_actor(actor) if not follower_nickname: return '' follower_domain, follower_port = get_domain_from_actor(actor) if not follower_domain: return '' follower_domain_full = \ get_full_domain(follower_domain, follower_port) info_form += '' + \ follower_nickname + '@' + \ follower_domain_full + '

\n' info_form += '
\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 += '
\n' info_form += '

' + translate['Word frequencies'] + '

\n' word_swarm = '' ctr = 0 for word, count in word_frequency.items(): if count >= minimum_word_count: if ctr > 0: word_swarm += ' ' if count < max_count - int(max_count / 4): word_swarm += word else: if count != max_count: word_swarm += '' + word + '' else: word_swarm += '' + word + '' ctr += 1 info_form += word_swarm info_form += '
\n' info_form += html_footer() return info_form def html_moderation_info(translate: {}, base_dir: str, nickname: str, domain: str, theme: str, access_keys: {}) -> str: msg_str1 = \ 'These are globally blocked for all accounts on this instance' msg_str2 = \ 'Any blocks or suspensions made by moderators will be shown here.' info_form = '' css_filename = base_dir + '/epicyon-profile.css' if os.path.isfile(base_dir + '/epicyon.css'): css_filename = base_dir + '/epicyon.css' instance_title = \ get_config_param(base_dir, 'instanceTitle') info_form = html_header_with_external_style(css_filename, instance_title, None) # show banner banner_file, _ = \ get_banner_file(base_dir, nickname, domain, theme) moderation_link = '/users/' + nickname + '/moderation' info_form += \ '
\n\n' info_form += \ '\n' + \ '
\n
\n' info_form += \ '

' + \ translate['Moderation Information'] + \ '


' info_shown = False accounts = [] for _, dirs, _ in os.walk(base_dir + '/accounts'): for acct in dirs: if not is_account_dir(acct): continue accounts.append(acct) break accounts.sort() cols = 5 if len(accounts) > 10: info_form += '
' + translate['Show Accounts'] info_form += '\n' info_form += '
\n' info_form += '\n' info_form += ' \n' for col in range(cols): info_form += ' \n' info_form += ' \n' info_form += '\n' col = 0 for acct in accounts: acct_nickname = acct.split('@')[0] account_dir = os.path.join(base_dir + '/accounts', acct) actor_json = load_json(account_dir + '.json') if not actor_json: continue actor = actor_json['id'] avatar_url = '' ext = '' if actor_json.get('icon'): if actor_json['icon'].get('url'): url_str = get_url_from_post(actor_json['icon']['url']) avatar_url = remove_html(url_str) if '.' in avatar_url: ext = '.' + avatar_url.split('.')[-1] acct_url = \ '/users/' + nickname + '?options=' + actor + ';1;' + \ '/members/' + acct_nickname + ext info_form += '\n' col += 1 if col == cols: # new row of accounts info_form += '\n\n' info_form += '\n
\n' info_form += '' info_form += '
' if is_moderator(base_dir, acct_nickname): info_form += '' + acct_nickname + '' else: info_form += acct_nickname if is_editor(base_dir, acct_nickname): info_form += ' ✍' info_form += '
\n
\n' info_form += '
\n' if len(accounts) > 10: info_form += '
\n' suspended_filename = base_dir + '/accounts/suspended.txt' if os.path.isfile(suspended_filename): with open(suspended_filename, 'r', encoding='utf-8') as fp_sus: suspended_str = fp_sus.read() info_form += '
\n' info_form += '
' + \ translate['Suspended accounts'] + '' info_form += '
' + \ translate['These are currently suspended'] info_form += \ ' \n' info_form += '
\n' info_shown = True blocking_filename = base_dir + '/accounts/blocking.txt' if os.path.isfile(blocking_filename): blocking_reasons_filename = \ base_dir + '/accounts/blocking_reasons.txt' blocking_reasons_exist = False if os.path.isfile(blocking_reasons_filename): blocking_reasons_exist = True with open(blocking_filename, 'r', encoding='utf-8') as fp_block: blocked_lines = fp_block.readlines() blocked_str = '' if blocked_lines: blocked_lines.sort() for line in blocked_lines: if not line: continue line = remove_eol(line).strip() if blocking_reasons_exist: reason = \ get_global_block_reason(line, blocking_reasons_filename) if reason: blocked_str += \ line + ' - ' + reason + '\n' continue blocked_str += line + '\n' info_form += '
\n' info_form += \ '
' + \ translate['Blocked accounts and hashtags'] + '' info_form += \ '
' + \ translate[msg_str1] info_form += \ ' \n' info_form += '
\n' info_shown = True filters_filename = base_dir + '/accounts/filters.txt' if os.path.isfile(filters_filename): with open(filters_filename, 'r', encoding='utf-8') as fp_filt: filtered_str = fp_filt.read() info_form += '
\n' info_form += \ '
' + \ translate['Filtered words'] + '' info_form += \ ' \n' info_form += '
\n' info_shown = True if not info_shown: info_form += \ '

' + \ translate[msg_str2] + \ '

\n' info_form += html_footer() return info_form