__filename__ = "webapp_post.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.5.0" __maintainer__ = "Bob Mottram" __email__ = "bob@libreserver.org" __status__ = "Production" __module_group__ = "Web Interface" import os import time import urllib.parse from dateutil.parser import parse from auth import create_password from git import is_git_patch from cache import get_person_from_cache from bookmarks import bookmarked_by_person from announce import announced_by_person from announce import no_of_announces from like import liked_by_person from like import no_of_likes from follow import is_following_actor from posts import post_is_muted from posts import get_person_box from posts import download_announce from posts import populate_replies_json from utils import get_quote_toot_url from utils import get_post_attachments from utils import get_url_from_post from utils import date_from_string_format from utils import remove_markup_tag from utils import ap_proxy_type from utils import remove_style_within_html from utils import license_link_from_name from utils import dont_speak_hashtags from utils import remove_eol from utils import disallow_announce from utils import disallow_reply from utils import convert_published_to_local_timezone from utils import remove_hash_from_post_id from utils import remove_html from utils import get_actor_languages_list from utils import get_base_content_from_post from utils import get_content_from_post from utils import get_language_from_post from utils import get_summary_from_post from utils import has_object_dict from utils import update_announce_collection from utils import is_pgp_encrypted from utils import is_dm from utils import is_reminder from utils import is_chat_message from utils import reject_post_id from utils import is_recent_post from utils import get_config_param from utils import get_full_domain from utils import is_editor from utils import locate_post from utils import load_json from utils import get_cached_post_directory from utils import get_cached_post_filename from utils import get_protocol_prefixes from utils import is_news_post from utils import is_blog_post from utils import get_display_name from utils import display_name_is_emoji from utils import is_public_post from utils import is_followers_post from utils import update_recent_posts_cache from utils import remove_id_ending from utils import get_nickname_from_actor from utils import get_domain_from_actor from utils import acct_dir from utils import local_actor_url from utils import is_unlisted_post from utils import language_right_to_left from utils import get_attributed_to from utils import get_reply_to from utils import get_actor_from_post from utils import resembles_url from content import remove_link_trackers_from_content from content import format_mixed_right_to_left from content import replace_remote_hashtags from content import detect_dogwhistles from content import create_edits_html from content import bold_reading_string from content import limit_repeated_words from content import replace_emoji_from_tags from content import html_replace_quote_marks from content import html_replace_email_quote from content import remove_text_formatting from content import remove_long_words from content import get_mentions_from_html from content import switch_words from content import add_auto_cw from person import is_person_snoozed from person import get_person_avatar_url from webapp_utils import text_mode_browser from webapp_utils import get_buy_links from webapp_utils import get_banner_file from webapp_utils import get_avatar_image_url from webapp_utils import update_avatar_image_cache from webapp_utils import load_individual_post_as_html_from_cache from webapp_utils import add_emoji_to_display_name from webapp_utils import post_contains_public from webapp_utils import get_content_warning_button from webapp_utils import get_post_attachments_as_html from webapp_utils import html_header_with_external_style from webapp_utils import html_footer from webapp_utils import get_broken_link_substitute from webapp_media import add_embedded_elements from webapp_question import insert_question from webfinger import webfinger_handle from speaker import update_speaker from languages import auto_translate_post from cwlists import add_cw_from_lists from blocking import is_blocked from reaction import html_emoji_reactions from maps import html_open_street_map from maps import set_map_preferences_coords from maps import set_map_preferences_url from maps import geocoords_from_map_link from maps import get_location_from_post from session import get_json_valid from session import get_json # maximum length for display name within html posts MAX_DISPLAY_NAME_LENGTH = 42 def _enforce_max_display_name_length(display_name: str) -> str: """Ensures that the display name does not get too long """ # enforce maximum length for the display name if len(display_name) <= MAX_DISPLAY_NAME_LENGTH: return display_name if ':' in display_name: display_name_short = display_name.split(':')[0].strip() if len(display_name_short) > 2: display_name = display_name_short if len(display_name) > MAX_DISPLAY_NAME_LENGTH: display_name = display_name[:MAX_DISPLAY_NAME_LENGTH] return display_name def _html_post_metadata_open_graph(domain: str, post_json_object: {}, system_language: str) -> str: """Returns html OpenGraph metadata for a post """ metadata = \ " \n" metadata += \ " \n" metadata += \ " \n" metadata += \ " \n" obj_json = post_json_object if has_object_dict(post_json_object): obj_json = post_json_object['object'] if obj_json.get('id'): metadata += " \n" if obj_json.get('summary'): metadata += " \n" if obj_json.get('attributedTo'): attrib_str = get_attributed_to(obj_json['attributedTo']) if attrib_str: attrib = attrib_str actor_nick = get_nickname_from_actor(attrib) actor_domain, _ = get_domain_from_actor(attrib) if actor_nick and actor_domain: actor_handle = actor_nick + '@' + actor_domain metadata += \ " \n" metadata += \ " \n" if obj_json.get('url'): url_str = get_url_from_post(obj_json['url']) obj_url = remove_html(url_str) metadata += \ " \n" if obj_json.get('published'): metadata += " \n" metadata += \ " \n" post_attachments = get_post_attachments(obj_json) if not post_attachments or obj_json.get('sensitive'): if 'content' in obj_json and not obj_json.get('sensitive'): obj_content = obj_json['content'] if 'contentMap' in obj_json: if obj_json['contentMap'].get(system_language): obj_content = obj_json['contentMap'][system_language] description = remove_html(obj_content) metadata += \ " \n" metadata += \ " \n" return metadata # metadata for attachment for attach_json in post_attachments: if not isinstance(attach_json, dict): continue if not attach_json.get('mediaType'): continue if not attach_json.get('url'): continue if not attach_json.get('name'): continue description = None if attach_json['mediaType'].startswith('image/'): description = 'Attached: 1 image' elif attach_json['mediaType'].startswith('video/'): description = 'Attached: 1 video' elif attach_json['mediaType'].startswith('audio/'): description = 'Attached: 1 audio' if description: if 'content' in obj_json and not obj_json.get('sensitive'): obj_content = obj_json['content'] if 'contentMap' in obj_json: if obj_json['contentMap'].get(system_language): obj_content = obj_json['contentMap'][system_language] description += '\n\n' + remove_html(obj_content) metadata += \ " \n" metadata += \ " \n" url_str = get_url_from_post(attach_json['url']) attach_url = remove_html(url_str) metadata += \ " \n" metadata += \ " \n" if attach_json.get('width'): metadata += \ " \n" if attach_json.get('height'): metadata += \ " \n" metadata += \ " \n" if attach_json['mediaType'].startswith('image/'): metadata += \ " \n" return metadata def _log_post_timing(enable_timing_log: bool, post_start_time, debug_id: str) -> None: """Create a log of timings for performance tuning """ if not enable_timing_log: return time_diff = int((time.time() - post_start_time) * 1000) if time_diff > 100: print('TIMING INDIV ' + debug_id + ' = ' + str(time_diff)) def prepare_html_post_nickname(nickname: str, post_html: str) -> str: """html posts stored in memory are for all accounts on the instance and they're indexed by id. However, some incoming posts may be destined for multiple accounts (followers). This creates a problem where the icon links whose urls begin with href="/users/nickname? need to be changed for different nicknames to display correctly within their timelines. This function changes the nicknames for the icon links. """ # replace the nickname users_str = ' href="/users/' if users_str not in post_html: return post_html user_found = True post_str = post_html new_post_str = '' while user_found: if users_str not in post_str: new_post_str += post_str break # the next part, after href="/users/nickname? next_str = post_str.split(users_str, 1)[1] if '?' in next_str: next_str = next_str.split('?', 1)[1] else: new_post_str += post_str break # append the previous text to the result new_post_str += post_str.split(users_str)[0] new_post_str += users_str + nickname + '?' # post is now the next part post_str = next_str return new_post_str def replace_link_variable(link: str, variable_name: str, value: str, separator: str) -> str: """Replaces a variable within the given link """ full_var = separator + variable_name + '=' if full_var not in link: return link curr_str = link result = '' while full_var in curr_str: prefix = curr_str.split(full_var, 1)[0] + full_var next_str = curr_str.split(full_var, 1)[1] if separator in next_str: next_str = next_str.split(separator, 1)[1] result += prefix + value + separator curr_str = next_str else: result += prefix + value curr_str = '' return result + curr_str def _prepare_media_post_from_html_cache(post_html: str, translate: {}, media_type: str) -> str: """ replaces the