Handle inReplyTo in a way that is compatible with bookwyrm

merge-requests/30/head
Bob Mottram 2023-12-24 23:42:38 +00:00
parent 85f03e69c4
commit db4f77e52d
7 changed files with 100 additions and 67 deletions

View File

@ -16,6 +16,7 @@ from utils import locate_post
from utils import load_json from utils import load_json
from utils import harmless_markup from utils import harmless_markup
from utils import get_attributed_to from utils import get_attributed_to
from utils import get_reply_to
from keys import get_instance_actor_key from keys import get_instance_actor_key
from session import get_json from session import get_json
from session import get_json_valid from session import get_json_valid
@ -190,11 +191,11 @@ def download_conversation_posts(authorized: bool, session,
if not authorized: if not authorized:
# only show a single post to non-authorized viewers # only show a single post to non-authorized viewers
break break
if not post_json_object['object'].get('inReplyTo'): post_id = get_reply_to(post_json_object['object'])
if not post_id:
if debug: if debug:
print(post_id + ' is not a reply') print(post_id + ' is not a reply')
break break
post_id = post_json_object['object']['inReplyTo']
post_id = remove_id_ending(post_id) post_id = remove_id_ending(post_id)
post_filename = \ post_filename = \
locate_post(base_dir, nickname, domain, post_id) locate_post(base_dir, nickname, domain, post_id)

View File

@ -33,6 +33,7 @@ from utils import get_nickname_from_actor
from utils import get_domain_from_actor from utils import get_domain_from_actor
from utils import is_pgp_encrypted from utils import is_pgp_encrypted
from utils import local_actor_url from utils import local_actor_url
from utils import get_reply_to
from session import create_session from session import create_session
from speaker import speakable_text from speaker import speakable_text
from speaker import get_speaker_pitch from speaker import get_speaker_pitch
@ -897,8 +898,9 @@ def _read_local_box_post(session, nickname: str, domain: str,
system_language, espeak, name_str, gender) system_language, espeak, name_str, gender)
print('') print('')
if post_json_object['object'].get('inReplyTo'): reply_id = get_reply_to(post_json_object['object'])
print('Replying to ' + post_json_object['object']['inReplyTo'] + '\n') if reply_id:
print('Replying to ' + reply_id + '\n')
if screenreader: if screenreader:
time.sleep(2) time.sleep(2)
@ -1181,7 +1183,8 @@ def _desktop_show_box(indent: str,
# append icons to the end of the name # append icons to the end of the name
space_added = False space_added = False
if post_json_object['object'].get('inReplyTo'): reply_id = get_reply_to(post_json_object['object'])
if reply_id:
if not space_added: if not space_added:
space_added = True space_added = True
name += ' ' name += ' '

View File

@ -80,6 +80,7 @@ from utils import local_actor_url
from utils import has_object_string_type from utils import has_object_string_type
from utils import valid_hash_tag from utils import valid_hash_tag
from utils import get_attributed_to from utils import get_attributed_to
from utils import get_reply_to
from categories import get_hashtag_categories from categories import get_hashtag_categories
from categories import set_hashtag_category from categories import set_hashtag_category
from httpsig import get_digest_algorithm_from_headers from httpsig import get_digest_algorithm_from_headers
@ -623,8 +624,9 @@ def inbox_permitted_message(domain: str, message_json: {},
if message_json['type'] not in always_allowed_types: if message_json['type'] not in always_allowed_types:
if not has_object_dict(message_json): if not has_object_dict(message_json):
return True return True
if message_json['object'].get('inReplyTo'): reply_id = get_reply_to(message_json['object'])
in_reply_to = message_json['object']['inReplyTo'] if reply_id:
in_reply_to = reply_id
if not isinstance(in_reply_to, str): if not isinstance(in_reply_to, str):
return False return False
if not url_permitted(in_reply_to, federation_list): if not url_permitted(in_reply_to, federation_list):
@ -737,10 +739,10 @@ def save_post_to_inbox_queue(base_dir: str, http_prefix: str,
return None return None
# is this a reply to a blocked domain or account? # is this a reply to a blocked domain or account?
if post_json_object['object'].get('inReplyTo'): reply_id = get_reply_to(post_json_object['object'])
if isinstance(post_json_object['object']['inReplyTo'], str): if reply_id:
in_reply_to = \ if isinstance(reply_id, str):
post_json_object['object']['inReplyTo'] in_reply_to = reply_id
reply_domain, _ = \ reply_domain, _ = \
get_domain_from_actor(in_reply_to) get_domain_from_actor(in_reply_to)
if reply_domain: if reply_domain:
@ -1528,9 +1530,10 @@ def _valid_post_content(base_dir: str, nickname: str, domain: str,
if message_json.get('id'): if message_json.get('id'):
print('REJECT: content filtered ' + str(message_json['id'])) print('REJECT: content filtered ' + str(message_json['id']))
return False return False
if message_json['object'].get('inReplyTo'): reply_id = get_reply_to(message_json['object'])
if isinstance(message_json['object']['inReplyTo'], str): if reply_id:
original_post_id = message_json['object']['inReplyTo'] if isinstance(reply_id, str):
original_post_id = reply_id
post_post_filename = locate_post(base_dir, nickname, domain, post_post_filename = locate_post(base_dir, nickname, domain,
original_post_id) original_post_id)
if post_post_filename: if post_post_filename:
@ -2370,7 +2373,8 @@ def _receive_zot_reaction(recent_posts_cache: {},
print('DEBUG: ' + message_json['object']['type'] + print('DEBUG: ' + message_json['object']['type'] +
' has no "content"') ' has no "content"')
return False return False
if not message_json['object'].get('inReplyTo'): reply_id = get_reply_to(message_json['object'])
if not reply_id:
if debug: if debug:
print('DEBUG: ' + message_json['object']['type'] + print('DEBUG: ' + message_json['object']['type'] +
' has no "inReplyTo"') ' has no "inReplyTo"')
@ -2384,7 +2388,7 @@ def _receive_zot_reaction(recent_posts_cache: {},
if debug: if debug:
print('DEBUG: content is too long to be an emoji reaction') print('DEBUG: content is too long to be an emoji reaction')
return False return False
if not isinstance(message_json['object']['inReplyTo'], str): if not isinstance(reply_id, str):
if debug: if debug:
print('DEBUG: ' + message_json['object']['type'] + print('DEBUG: ' + message_json['object']['type'] +
' inReplyTo is not string') ' inReplyTo is not string')
@ -2399,7 +2403,7 @@ def _receive_zot_reaction(recent_posts_cache: {},
print('DEBUG: "users" or "profile" missing from actor in ' + print('DEBUG: "users" or "profile" missing from actor in ' +
message_json['object']['type']) message_json['object']['type'])
return False return False
if '/statuses/' not in message_json['object']['inReplyTo']: if '/statuses/' not in reply_id:
if debug: if debug:
print('DEBUG: "statuses" missing from inReplyTo in ' + print('DEBUG: "statuses" missing from inReplyTo in ' +
message_json['object']['type']) message_json['object']['type'])
@ -2415,7 +2419,7 @@ def _receive_zot_reaction(recent_posts_cache: {},
handle_name = handle.split('@')[0] handle_name = handle.split('@')[0]
handle_dom = handle.split('@')[1] handle_dom = handle.split('@')[1]
post_reaction_id = message_json['object']['inReplyTo'] post_reaction_id = get_reply_to(message_json['object'])
emoji_content = remove_html(message_json['object']['content']) emoji_content = remove_html(message_json['object']['content'])
if not emoji_content: if not emoji_content:
if debug: if debug:
@ -3417,11 +3421,11 @@ def populate_replies(base_dir: str, http_prefix: str, domain: str,
return False return False
if not has_object_dict(message_json): if not has_object_dict(message_json):
return False return False
if not message_json['object'].get('inReplyTo'): reply_to = get_reply_to(message_json['object'])
if not reply_to:
return False return False
if not message_json['object'].get('to'): if not message_json['object'].get('to'):
return False return False
reply_to = message_json['object']['inReplyTo']
if not isinstance(reply_to, str): if not isinstance(reply_to, str):
return False return False
if debug: if debug:
@ -3509,10 +3513,11 @@ def _obtain_avatar_for_reply_post(session, base_dir: str, http_prefix: str,
if not has_object_dict(post_json_object): if not has_object_dict(post_json_object):
return return
if not post_json_object['object'].get('inReplyTo'): reply_id = get_reply_to(post_json_object['object'])
if not reply_id:
return return
lookup_actor = post_json_object['object']['inReplyTo'] lookup_actor = reply_id
if not lookup_actor: if not lookup_actor:
return return
@ -4225,7 +4230,7 @@ def _is_valid_dm(base_dir: str, nickname: str, domain: str, port: int,
# replies to bounce messages # replies to bounce messages
obj = post_json_object['object'] obj = post_json_object['object']
if obj_has_dict and \ if obj_has_dict and \
not obj.get('inReplyTo'): not get_reply_to(obj):
bounced_id = \ bounced_id = \
remove_id_ending(post_json_object['id']) remove_id_ending(post_json_object['id'])
bounce_chat = False bounce_chat = False
@ -4392,9 +4397,7 @@ def _create_reply_notification_file(base_dir: str, nickname: str, domain: str,
elif post_json_object['object'].get('context'): elif post_json_object['object'].get('context'):
conversation_id = post_json_object['object']['context'] conversation_id = post_json_object['object']['context']
if not post_json_object['object'].get('inReplyTo'): in_reply_to = get_reply_to(post_json_object['object'])
return is_reply_to_muted_post
in_reply_to = post_json_object['object']['inReplyTo']
if not in_reply_to: if not in_reply_to:
return is_reply_to_muted_post return is_reply_to_muted_post
if not isinstance(in_reply_to, str): if not isinstance(in_reply_to, str):

View File

@ -86,6 +86,7 @@ from utils import remove_html
from utils import dangerous_markup from utils import dangerous_markup
from utils import acct_dir from utils import acct_dir
from utils import local_actor_url from utils import local_actor_url
from utils import get_reply_to
from media import get_music_metadata from media import get_music_metadata
from media import attach_media from media import attach_media
from media import replace_you_tube from media import replace_you_tube
@ -684,17 +685,17 @@ def _get_posts(session, outbox_url: str, max_posts: int,
if this_item['summary']: if this_item['summary']:
summary = this_item['summary'] summary = this_item['summary']
if this_item.get('inReplyTo'): reply_id = get_reply_to(this_item)
if this_item['inReplyTo']: if reply_id:
if isinstance(this_item['inReplyTo'], str): if isinstance(reply_id, str):
# No replies to non-permitted domains # No replies to non-permitted domains
if not url_permitted(this_item['inReplyTo'], if not url_permitted(reply_id,
federation_list): federation_list):
if debug: if debug:
print('url not permitted ' + print('url not permitted ' +
this_item['inReplyTo']) reply_id)
continue continue
in_reply_to = this_item['inReplyTo'] in_reply_to = reply_id
if this_item.get('attachment'): if this_item.get('attachment'):
if len(this_item['attachment']) > max_attachments: if len(this_item['attachment']) > max_attachments:
@ -836,10 +837,11 @@ def get_post_domains(session, outbox_url: str, max_posts: int, debug: bool,
content_str = get_base_content_from_post(item, system_language) content_str = get_base_content_from_post(item, system_language)
if content_str: if content_str:
_update_word_frequency(content_str, word_frequency) _update_word_frequency(content_str, word_frequency)
if item['object'].get('inReplyTo'): reply_id = get_reply_to(item['object'])
if isinstance(item['object']['inReplyTo'], str): if reply_id:
if isinstance(reply_id, str):
post_domain, _ = \ post_domain, _ = \
get_domain_from_actor(item['object']['inReplyTo']) get_domain_from_actor(reply_id)
if post_domain: if post_domain:
if post_domain not in post_domains: if post_domain not in post_domains:
post_domains.append(post_domain) post_domains.append(post_domain)
@ -900,10 +902,11 @@ def _get_posts_for_blocked_domains(base_dir: str,
break break
if not has_object_dict(item): if not has_object_dict(item):
continue continue
if item['object'].get('inReplyTo'): reply_id = get_reply_to(item['object'])
if isinstance(item['object']['inReplyTo'], str): if reply_id:
if isinstance(reply_id, str):
post_domain, _ = \ post_domain, _ = \
get_domain_from_actor(item['object']['inReplyTo']) get_domain_from_actor(reply_id)
if not post_domain: if not post_domain:
continue continue
if is_blocked_domain(base_dir, post_domain): if is_blocked_domain(base_dir, post_domain):

View File

@ -14,6 +14,7 @@ from utils import save_json
from utils import has_object_dict from utils import has_object_dict
from utils import text_in_file from utils import text_in_file
from utils import dangerous_markup from utils import dangerous_markup
from utils import get_reply_to
def is_vote(base_dir: str, nickname: str, domain: str, def is_vote(base_dir: str, nickname: str, domain: str,
@ -24,9 +25,10 @@ def is_vote(base_dir: str, nickname: str, domain: str,
if has_object_dict(post_json_object): if has_object_dict(post_json_object):
post_obj = post_json_object['object'] post_obj = post_json_object['object']
if not post_obj.get('inReplyTo'): reply_id = get_reply_to(post_obj)
if not reply_id:
return False return False
if not isinstance(post_obj['inReplyTo'], str): if not isinstance(reply_id, str):
return False return False
if not post_obj.get('name'): if not post_obj.get('name'):
return False return False
@ -35,7 +37,7 @@ def is_vote(base_dir: str, nickname: str, domain: str,
print('VOTE: ' + str(post_obj)) print('VOTE: ' + str(post_obj))
# is the replied to post a Question? # is the replied to post a Question?
in_reply_to = post_obj['inReplyTo'] in_reply_to = reply_id
question_post_filename = \ question_post_filename = \
locate_post(base_dir, nickname, domain, in_reply_to) locate_post(base_dir, nickname, domain, in_reply_to)
if not question_post_filename: if not question_post_filename:
@ -105,7 +107,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str,
post_obj = reply_json['object'] post_obj = reply_json['object']
reply_vote = post_obj['name'] reply_vote = post_obj['name']
in_reply_to = post_obj['inReplyTo'] in_reply_to = get_reply_to(post_obj)
question_post_filename = \ question_post_filename = \
locate_post(base_dir, nickname, domain, in_reply_to) locate_post(base_dir, nickname, domain, in_reply_to)
if not question_post_filename: if not question_post_filename:

View File

@ -2138,15 +2138,16 @@ def _is_reply_to_blog_post(base_dir: str, nickname: str, domain: str,
""" """
if not has_object_dict(post_json_object): if not has_object_dict(post_json_object):
return False return False
if not post_json_object['object'].get('inReplyTo'): reply_id = get_reply_to(post_json_object['object'])
if not reply_id:
return False return False
if not isinstance(post_json_object['object']['inReplyTo'], str): if not isinstance(reply_id, str):
return False return False
blogs_index_filename = \ blogs_index_filename = \
acct_dir(base_dir, nickname, domain) + '/tlblogs.index' acct_dir(base_dir, nickname, domain) + '/tlblogs.index'
if not os.path.isfile(blogs_index_filename): if not os.path.isfile(blogs_index_filename):
return False return False
post_id = remove_id_ending(post_json_object['object']['inReplyTo']) post_id = remove_id_ending(reply_id)
post_id = post_id.replace('/', '#') post_id = post_id.replace('/', '#')
if text_in_file(post_id, blogs_index_filename): if text_in_file(post_id, blogs_index_filename):
return True return True
@ -3477,9 +3478,10 @@ def is_reply(post_json_object: {}, actor: str) -> bool:
'EncryptedMessage', 'EncryptedMessage',
'ChatMessage', 'Article'): 'ChatMessage', 'Article'):
return False return False
if post_json_object['object'].get('inReplyTo'): reply_id = get_reply_to(post_json_object['object'])
if isinstance(post_json_object['object']['inReplyTo'], str): if reply_id:
if post_json_object['object']['inReplyTo'].startswith(actor): if isinstance(reply_id, str):
if reply_id.startswith(actor):
return True return True
if not post_json_object['object'].get('tag'): if not post_json_object['object'].get('tag'):
return False return False
@ -4840,3 +4842,13 @@ def get_media_url_from_video(post_json_object: {}) -> (str, str, str, str):
media_type = media_link['mediaType'] media_type = media_link['mediaType']
media_url = remove_html(media_link['href']) media_url = remove_html(media_link['href'])
return media_type, media_url, media_torrent, media_magnet return media_type, media_url, media_torrent, media_magnet
def get_reply_to(post_json_object: {}) -> str:
"""Returns the reply to link from a post
"""
if post_json_object.get('inReplyTo'):
return post_json_object['inReplyTo']
if post_json_object.get('inReplyToBook'):
return post_json_object['inReplyToBook']
return ''

View File

@ -73,6 +73,7 @@ from utils import local_actor_url
from utils import is_unlisted_post from utils import is_unlisted_post
from utils import language_right_to_left from utils import language_right_to_left
from utils import get_attributed_to from utils import get_attributed_to
from utils import get_reply_to
from content import format_mixed_right_to_left from content import format_mixed_right_to_left
from content import replace_remote_hashtags from content import replace_remote_hashtags
from content import detect_dogwhistles from content import detect_dogwhistles
@ -678,8 +679,9 @@ def _get_edit_icon_html(base_dir: str, nickname: str, domain_full: str,
return edit_str return edit_str
reply_to = '' reply_to = ''
if post_json_object['object'].get('inReplyTo'): reply_id = get_reply_to(post_json_object['object'])
reply_to = ';replyTo=' + post_json_object['object']['inReplyTo'] if reply_id:
reply_to = ';replyTo=' + reply_id
first_post_str = '' first_post_str = ''
if first_post_id: if first_post_id:
@ -1482,7 +1484,7 @@ def _reply_to_unknown_html(translate: {},
"""Returns the html title for a reply to an unknown handle """Returns the html title for a reply to an unknown handle
""" """
replying_to_str = _replying_to_with_scope(post_json_object, translate) replying_to_str = _replying_to_with_scope(post_json_object, translate)
post_id = post_json_object['object']['inReplyTo'] post_id = get_reply_to(post_json_object['object'])
post_link = '/users/' + nickname + '?convthread=' + \ post_link = '/users/' + nickname + '?convthread=' + \
post_id.replace('/', '--') post_id.replace('/', '--')
return ' <img loading="lazy" decoding="async" title="' + \ return ' <img loading="lazy" decoding="async" title="' + \
@ -1512,7 +1514,7 @@ def _reply_with_unknown_path_html(translate: {},
eg. does not contain /statuses/ eg. does not contain /statuses/
""" """
replying_to_str = _replying_to_with_scope(post_json_object, translate) replying_to_str = _replying_to_with_scope(post_json_object, translate)
post_id = post_json_object['object']['inReplyTo'] post_id = get_reply_to(post_json_object['object'])
post_link = '/users/' + nickname + '?convthread=' + \ post_link = '/users/' + nickname + '?convthread=' + \
post_id.replace('/', '--') post_id.replace('/', '--')
return ' <img loading="lazy" decoding="async" title="' + \ return ' <img loading="lazy" decoding="async" title="' + \
@ -1577,7 +1579,8 @@ def _get_post_title_reply_html(base_dir: str,
obj_json = post_json_object['object'] obj_json = post_json_object['object']
# not a reply # not a reply
if not obj_json.get('inReplyTo'): reply_id = get_reply_to(obj_json)
if not reply_id:
return (title_str, reply_avatar_image_in_post, return (title_str, reply_avatar_image_in_post,
container_class_icons, container_class) container_class_icons, container_class)
@ -1585,7 +1588,7 @@ def _get_post_title_reply_html(base_dir: str,
container_class = 'container darker' container_class = 'container darker'
# reply to self # reply to self
if obj_json['inReplyTo'].startswith(post_actor): if reply_id.startswith(post_actor):
title_str += _reply_to_yourself_html(translate) title_str += _reply_to_yourself_html(translate)
return (title_str, reply_avatar_image_in_post, return (title_str, reply_avatar_image_in_post,
container_class_icons, container_class) container_class_icons, container_class)
@ -1593,8 +1596,8 @@ def _get_post_title_reply_html(base_dir: str,
# has a reply # has a reply
reply_actor = None reply_actor = None
in_reply_to = None in_reply_to = None
if '/statuses/' not in obj_json['inReplyTo']: if '/statuses/' not in reply_id:
reply_url = obj_json['inReplyTo'] reply_url = reply_id
post_domain = reply_url post_domain = reply_url
prefixes = get_protocol_prefixes() prefixes = get_protocol_prefixes()
for prefix in prefixes: for prefix in prefixes:
@ -1637,9 +1640,10 @@ def _get_post_title_reply_html(base_dir: str,
return (title_str, reply_avatar_image_in_post, return (title_str, reply_avatar_image_in_post,
container_class_icons, container_class) container_class_icons, container_class)
if obj_json.get('inReplyTo'): reply_id = get_reply_to(obj_json)
if isinstance(obj_json['inReplyTo'], str): if reply_id:
in_reply_to = obj_json['inReplyTo'] if isinstance(reply_id, str):
in_reply_to = reply_id
if in_reply_to and not reply_actor: if in_reply_to and not reply_actor:
reply_actor = in_reply_to.split('/statuses/')[0] reply_actor = in_reply_to.split('/statuses/')[0]
reply_nickname = get_nickname_from_actor(reply_actor) reply_nickname = get_nickname_from_actor(reply_actor)
@ -3081,10 +3085,15 @@ def html_individual_post(recent_posts_cache: {}, max_recent_posts: int,
# show the previous posts # show the previous posts
if has_object_dict(post_json_object): if has_object_dict(post_json_object):
while post_json_object['object'].get('inReplyTo'): post_id = True
while post_id:
if not post_json_object:
break
post_id = get_reply_to(post_json_object['object'])
if not post_id:
break
post_filename = \ post_filename = \
locate_post(base_dir, nickname, domain, locate_post(base_dir, nickname, domain, post_id)
post_json_object['object']['inReplyTo'])
if not post_filename: if not post_filename:
break break
post_json_object = load_json(post_filename) post_json_object = load_json(post_filename)