diff --git a/blocking.py b/blocking.py index 6387b4a79..66891993a 100644 --- a/blocking.py +++ b/blocking.py @@ -15,7 +15,7 @@ from session import get_json_valid from session import create_session from flags import is_evil from flags import is_quote_toot -from utils import get_quote_toot_url +from quote import get_quote_toot_url from utils import get_user_paths from utils import contains_statuses from utils import data_dir diff --git a/flags.py b/flags.py index 72ecb4bed..6a5c8ec17 100644 --- a/flags.py +++ b/flags.py @@ -27,7 +27,7 @@ from utils import date_from_string_format from utils import get_reply_to from utils import text_in_file from utils import get_group_paths -from utils import get_quote_toot_url +from quote import get_quote_toot_url def is_featured_writer(base_dir: str, nickname: str, domain: str) -> bool: diff --git a/inbox.py b/inbox.py index 590bec2ca..3374c97dd 100644 --- a/inbox.py +++ b/inbox.py @@ -22,9 +22,9 @@ from flags import is_group_account from flags import has_group_type from flags import is_quote_toot from flags import url_permitted +from quote import quote_toots_allowed from utils import save_mitm_servers from utils import harmless_markup -from utils import quote_toots_allowed from utils import lines_in_file from utils import date_epoch from utils import date_utcnow diff --git a/inbox_receive.py b/inbox_receive.py index ddca921ac..7ab0fe945 100644 --- a/inbox_receive.py +++ b/inbox_receive.py @@ -12,7 +12,7 @@ import time from flags import is_recent_post from flags import is_quote_toot from status import actor_status_expired -from utils import get_quote_toot_url +from quote import get_quote_toot_url from utils import get_actor_from_post_id from utils import contains_invalid_actor_url_chars from utils import get_attributed_to diff --git a/outbox.py b/outbox.py index 62ad4df8f..1e8e15c86 100644 --- a/outbox.py +++ b/outbox.py @@ -17,8 +17,8 @@ from posts import send_to_followers_thread from posts import send_to_named_addresses_thread from flags import is_featured_writer from flags import is_quote_toot +from quote import quote_toots_allowed from utils import data_dir -from utils import quote_toots_allowed from utils import get_post_attachments from utils import get_attributed_to from utils import contains_invalid_actor_url_chars diff --git a/quote.py b/quote.py new file mode 100644 index 000000000..d188e337a --- /dev/null +++ b/quote.py @@ -0,0 +1,108 @@ +__filename__ = "quote.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.6.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@libreserver.org" +__status__ = "Production" +__module_group__ = "Core" + +import os +from utils import acct_dir +from utils import resembles_url +from utils import remove_html +from utils import text_in_file + + +def get_quote_toot_url(post_json_object: str) -> str: + """ Returns the url for a quote toot + This suffers from a general lack of protocol consensus + """ + # adhoc quote toot implementations + object_quote_url_fields = ( + 'quoteUri', 'quoteUrl', 'quoteReply', 'toot:quoteReply', + '_misskey_quote' + ) + for fieldname in object_quote_url_fields: + if not post_json_object['object'].get(fieldname): + continue + quote_url = post_json_object['object'][fieldname] + if isinstance(quote_url, str): + if resembles_url(quote_url): + return remove_html(quote_url) + + # as defined by FEP-dd4b + # https://codeberg.org/fediverse/fep/src/branch/main/fep/dd4b/fep-dd4b.md + if ((post_json_object.get('content') or + post_json_object.get('contentMap')) and + (not post_json_object['object'].get('content') and + not post_json_object['object'].get('contentMap')) and + post_json_object['object'].get('id')): + quote_url = post_json_object['object']['id'] + if isinstance(quote_url, str): + if resembles_url(quote_url): + return remove_html(quote_url) + + # Other ActivityPub implementation - adding a Link tag + if not post_json_object['object'].get('tag'): + return '' + + if not isinstance(post_json_object['object']['tag'], list): + return '' + + for item in post_json_object['object']['tag']: + if not isinstance(item, dict): + continue + if item.get('rel'): + mk_quote = False + if isinstance(item['rel'], list): + for rel_str in item['rel']: + if not isinstance(rel_str, str): + continue + if '_misskey_quote' in rel_str: + mk_quote = True + elif isinstance(item['rel'], str): + if '_misskey_quote' in item['rel']: + mk_quote = True + if mk_quote and item.get('href'): + if isinstance(item['href'], str): + if resembles_url(item['href']): + return remove_html(item['href']) + if not item.get('type'): + continue + if not item.get('mediaType'): + continue + if not isinstance(item['type'], str): + continue + if item['type'] != 'Link': + continue + if not isinstance(item['mediaType'], str): + continue + if 'json' not in item['mediaType']: + continue + if item.get('href'): + if isinstance(item['href'], str): + if resembles_url(item['href']): + return remove_html(item['href']) + return '' + + +def quote_toots_allowed(base_dir: str, nickname: str, domain: str, + sender_nickname: str, sender_domain: str) -> bool: + """ Returns true if quote toots are allowed by the given account + for the given sender + """ + account_dir = acct_dir(base_dir, nickname, domain) + quotes_enabled_filename = account_dir + '/.allowQuotes' + if os.path.isfile(quotes_enabled_filename): + # check blocks on individual sending accounts + quotes_blocked_filename = account_dir + '/quotesblocked.txt' + if sender_nickname is None: + return True + if os.path.isfile(quotes_blocked_filename): + sender_handle = sender_nickname + '@' + sender_domain + if text_in_file(sender_handle, quotes_blocked_filename, False): + # quote toots not permitted from this sender + return False + return True + return False diff --git a/utils.py b/utils.py index c2415f86b..f57966690 100644 --- a/utils.py +++ b/utils.py @@ -4684,100 +4684,6 @@ def save_reverse_timeline(base_dir: str, reverse_sequence: []) -> []: break -def get_quote_toot_url(post_json_object: str) -> str: - """ Returns the url for a quote toot - This suffers from a general lack of protocol consensus - """ - # adhoc quote toot implementations - object_quote_url_fields = ( - 'quoteUri', 'quoteUrl', 'quoteReply', 'toot:quoteReply', - '_misskey_quote' - ) - for fieldname in object_quote_url_fields: - if not post_json_object['object'].get(fieldname): - continue - quote_url = post_json_object['object'][fieldname] - if isinstance(quote_url, str): - if resembles_url(quote_url): - return remove_html(quote_url) - - # as defined by FEP-dd4b - # https://codeberg.org/fediverse/fep/src/branch/main/fep/dd4b/fep-dd4b.md - if ((post_json_object.get('content') or - post_json_object.get('contentMap')) and - (not post_json_object['object'].get('content') and - not post_json_object['object'].get('contentMap')) and - post_json_object['object'].get('id')): - quote_url = post_json_object['object']['id'] - if isinstance(quote_url, str): - if resembles_url(quote_url): - return remove_html(quote_url) - - # Other ActivityPub implementation - adding a Link tag - if not post_json_object['object'].get('tag'): - return '' - - if not isinstance(post_json_object['object']['tag'], list): - return '' - - for item in post_json_object['object']['tag']: - if not isinstance(item, dict): - continue - if item.get('rel'): - mk_quote = False - if isinstance(item['rel'], list): - for rel_str in item['rel']: - if not isinstance(rel_str, str): - continue - if '_misskey_quote' in rel_str: - mk_quote = True - elif isinstance(item['rel'], str): - if '_misskey_quote' in item['rel']: - mk_quote = True - if mk_quote and item.get('href'): - if isinstance(item['href'], str): - if resembles_url(item['href']): - return remove_html(item['href']) - if not item.get('type'): - continue - if not item.get('mediaType'): - continue - if not isinstance(item['type'], str): - continue - if item['type'] != 'Link': - continue - if not isinstance(item['mediaType'], str): - continue - if 'json' not in item['mediaType']: - continue - if item.get('href'): - if isinstance(item['href'], str): - if resembles_url(item['href']): - return remove_html(item['href']) - return '' - - -def quote_toots_allowed(base_dir: str, nickname: str, domain: str, - sender_nickname: str, sender_domain: str) -> bool: - """ Returns true if quote toots are allowed by the given account - for the given sender - """ - account_dir = acct_dir(base_dir, nickname, domain) - quotes_enabled_filename = account_dir + '/.allowQuotes' - if os.path.isfile(quotes_enabled_filename): - # check blocks on individual sending accounts - quotes_blocked_filename = account_dir + '/quotesblocked.txt' - if sender_nickname is None: - return True - if os.path.isfile(quotes_blocked_filename): - sender_handle = sender_nickname + '@' + sender_domain - if text_in_file(sender_handle, quotes_blocked_filename, False): - # quote toots not permitted from this sender - return False - return True - return False - - def license_link_from_name(license_name: str) -> str: """Returns the license link from its name """ diff --git a/webapp_person_options.py b/webapp_person_options.py index ce01ad1c7..ef7b5e2a1 100644 --- a/webapp_person_options.py +++ b/webapp_person_options.py @@ -14,8 +14,8 @@ from person import is_person_snoozed from posts import is_moderator from flags import is_featured_writer from flags import is_dormant +from quote import quote_toots_allowed from utils import data_dir -from utils import quote_toots_allowed from utils import get_full_domain from utils import get_config_param from utils import remove_html diff --git a/webapp_post.py b/webapp_post.py index d64893948..3c53e7314 100644 --- a/webapp_post.py +++ b/webapp_post.py @@ -36,12 +36,12 @@ from flags import is_recent_post from flags import is_chat_message from flags import is_pgp_encrypted from textmode import text_mode_removals +from quote import get_quote_toot_url from utils import save_json from utils import remove_header_tags from utils import get_actor_from_post_id from utils import contains_statuses from utils import data_dir -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