From 212bdacdb3111ea7e5d2f8fe490c4fdeca7842a0 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Thu, 12 Jan 2023 10:45:39 +0000 Subject: [PATCH] Votes on questions are a type of DM See https://socialhub.activitypub.rocks/t/votes-on-question-activities/2880 --- daemon.py | 47 +++++++++++---------- inbox.py | 52 ++++++++++++----------- question.py | 118 ++++++++++++++++++++++++++++++++-------------------- 3 files changed, 127 insertions(+), 90 deletions(-) diff --git a/daemon.py b/daemon.py index cdde0d956..36f7f44f9 100644 --- a/daemon.py +++ b/daemon.py @@ -674,30 +674,35 @@ class PubServer(BaseHTTPRequestHandler): nickname, self.server.domain_full, self.server.person_cache) + reply_to_nickname = get_nickname_from_actor(in_reply_to) + reply_to_domain, reply_to_port = get_domain_from_actor(in_reply_to) + reply_to_domain_full = get_full_domain(reply_to_domain, reply_to_port) + mentions_str = '@' + reply_to_nickname + '@' + reply_to_domain_full message_json = \ - create_public_post(self.server.base_dir, - nickname, - self.server.domain, self.server.port, - self.server.http_prefix, - answer, False, False, - comments_enabled, - attach_image_filename, media_type, - image_description, city, - in_reply_to, - in_reply_to_atom_uri, - subject, - schedule_post, - event_date, - event_time, event_end_time, - location, False, - self.server.system_language, - conversation_id, - self.server.low_bandwidth, - self.server.content_license_url, - languages_understood, - self.server.translate) + create_direct_message_post(self.server.base_dir, nickname, + self.server.domain, self.server.port, + self.server.http_prefix, + mentions_str + ' ' + answer, + False, False, + comments_enabled, + attach_image_filename, + media_type, image_description, city, + in_reply_to, in_reply_to_atom_uri, + subject, self.server.debug, + schedule_post, + event_date, event_time, + event_end_time, + location, self.server.system_language, + conversation_id, + self.server.low_bandwidth, + self.server.content_license_url, + languages_understood, False, + self.server.translate) if message_json: + # NOTE: content and contentMap are not required, but we will keep + # them in there so that the post does not get filtered out by + # inbox processing. # name field contains the answer message_json['object']['name'] = answer if self._post_to_outbox(message_json, diff --git a/inbox.py b/inbox.py index 0986f627e..d1f9ba4d3 100644 --- a/inbox.py +++ b/inbox.py @@ -119,6 +119,7 @@ from posts import send_signed_json from posts import send_to_followers_thread from webapp_post import individual_post_as_html from question import question_update_votes +from question import is_vote from media import replace_you_tube from media import replace_twitter from git import is_git_patch @@ -4424,8 +4425,33 @@ def _inbox_after_initial(server, inbox_start_time, post_json_object = message_json['post'] else: post_json_object = message_json - nickname = handle.split('@')[0] + + if is_vote(base_dir, nickname, domain, post_json_object): + _receive_question_vote(server, base_dir, nickname, domain, + http_prefix, handle, debug, + post_json_object, recent_posts_cache, + session, session_onion, session_i2p, + onion_domain, i2p_domain, port, + federation_list, send_threads, post_log, + cached_webfingers, person_cache, + signing_priv_key_pem, + max_recent_posts, translate, + allow_deletion, + yt_replace_domain, + twitter_replacement_domain, + peertube_instances, + allow_local_network_access, + theme_name, system_language, + max_like_count, + cw_lists, lists_enabled, + bold_reading, dogwhistles, + server.min_images_for_accounts) + fitness_performance(inbox_start_time, server.fitness, + 'INBOX', '_receive_question_vote', + debug) + inbox_start_time = time.time() + json_obj = None domain_full = get_full_domain(domain, port) if _valid_post_content(base_dir, nickname, domain, @@ -4495,30 +4521,6 @@ def _inbox_after_initial(server, inbox_start_time, debug) inbox_start_time = time.time() - _receive_question_vote(server, base_dir, nickname, domain, - http_prefix, handle, debug, - post_json_object, recent_posts_cache, - session, session_onion, session_i2p, - onion_domain, i2p_domain, port, - federation_list, send_threads, post_log, - cached_webfingers, person_cache, - signing_priv_key_pem, - max_recent_posts, translate, - allow_deletion, - yt_replace_domain, - twitter_replacement_domain, - peertube_instances, - allow_local_network_access, - theme_name, system_language, - max_like_count, - cw_lists, lists_enabled, - bold_reading, dogwhistles, - server.min_images_for_accounts) - fitness_performance(inbox_start_time, server.fitness, - 'INBOX', '_receive_question_vote', - debug) - inbox_start_time = time.time() - is_reply_to_muted_post = False if not is_group: diff --git a/question.py b/question.py index 04b7561c4..755bc04b9 100644 --- a/question.py +++ b/question.py @@ -15,52 +15,80 @@ from utils import has_object_dict from utils import text_in_file +def is_vote(base_dir: str, nickname: str, domain: str, + post_json_object: {}) -> bool: + """ is the given post a vote on a Question? + """ + post_obj = post_json_object + if has_object_dict(post_json_object): + post_obj = post_json_object['object'] + + if not post_obj.get('inReplyTo'): + return False + if not isinstance(post_obj['inReplyTo'], str): + return False + if not post_obj.get('name'): + return False + + # is the replied to post a Question? + in_reply_to = post_obj['inReplyTo'] + question_post_filename = \ + locate_post(base_dir, nickname, domain, in_reply_to) + if not question_post_filename: + return False + question_json = load_json(question_post_filename) + if not question_json: + return False + if not has_object_dict(question_json): + return False + if not question_json['object'].get('type'): + return False + if question_json['type'] != 'Question': + return False + + # does the question have options? + if not question_json['object'].get('oneOf'): + return False + if not isinstance(question_json['object']['oneOf'], list): + return False + + # does the reply name field match any possible question option? + reply_vote = post_json_object['name'] + found_answer_json = None + for possible_answer in question_json['object']['oneOf']: + if not possible_answer.get('name'): + continue + if possible_answer['name'] == reply_vote: + found_answer_json = possible_answer + break + if not found_answer_json: + return False + return True + + def question_update_votes(base_dir: str, nickname: str, domain: str, reply_json: {}) -> ({}, str): """ For a given reply update the votes on a question Returns the question json object if the vote totals were changed """ - if not has_object_dict(reply_json): + if not is_vote(base_dir, nickname, domain, reply_json): return None, None - if not reply_json['object'].get('inReplyTo'): - return None, None - if not reply_json['object']['inReplyTo']: - return None, None - if not isinstance(reply_json['object']['inReplyTo'], str): - return None, None - if not reply_json['object'].get('name'): - return None, None - in_reply_to = reply_json['object']['inReplyTo'] + + post_obj = reply_json + if has_object_dict(reply_json): + post_obj = reply_json['object'] + reply_vote = post_obj['name'] + + in_reply_to = post_obj['inReplyTo'] question_post_filename = \ locate_post(base_dir, nickname, domain, in_reply_to) if not question_post_filename: return None, None + question_json = load_json(question_post_filename) if not question_json: return None, None - if not has_object_dict(question_json): - return None, None - if not question_json['object'].get('type'): - return None, None - if question_json['type'] != 'Question': - return None, None - if not question_json['object'].get('oneOf'): - return None, None - if not isinstance(question_json['object']['oneOf'], list): - return None, None - if not question_json['object'].get('content'): - return None, None - reply_vote = reply_json['object']['name'] - # does the reply name field match any possible question option? - found_answer = None, None - for possible_answer in question_json['object']['oneOf']: - if not possible_answer.get('name'): - continue - if possible_answer['name'] == reply_vote: - found_answer = possible_answer - break - if not found_answer: - return None, None + # update the voters file voters_file_separator = ';;;' voters_filename = question_post_filename.replace('.json', '.voters') @@ -71,7 +99,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str, encoding='utf-8') as voters_file: voters_file.write(reply_json['actor'] + voters_file_separator + - found_answer + '\n') + reply_vote + '\n') except OSError: print('EX: unable to write voters file ' + voters_filename) else: @@ -82,7 +110,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str, encoding='utf-8') as voters_file: voters_file.write(reply_json['actor'] + voters_file_separator + - found_answer + '\n') + reply_vote + '\n') except OSError: print('EX: unable to append to voters file ' + voters_filename) else: @@ -96,7 +124,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str, if vote_line.startswith(reply_json['actor'] + voters_file_separator): new_vote_line = reply_json['actor'] + \ - voters_file_separator + found_answer + '\n' + voters_file_separator + reply_vote + '\n' if vote_line == new_vote_line: break save_voters_file = True @@ -114,6 +142,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str, voters_filename) else: return None, None + # update the vote counts question_totals_changed = False for possible_answer in question_json['object']['oneOf']: @@ -131,25 +160,26 @@ def question_update_votes(base_dir: str, nickname: str, domain: str, question_totals_changed = True if not question_totals_changed: return None, None + # save the question with altered totals save_json(question_json, question_post_filename) return question_json, question_post_filename -def is_question(post_object_json: {}) -> bool: +def is_question(post_json_object: {}) -> bool: """ is the given post a question? """ - if post_object_json['type'] != 'Create' and \ - post_object_json['type'] != 'Update': + if post_json_object['type'] != 'Create' and \ + post_json_object['type'] != 'Update': return False - if not has_object_dict(post_object_json): + if not has_object_dict(post_json_object): return False - if not post_object_json['object'].get('type'): + if not post_json_object['object'].get('type'): return False - if post_object_json['object']['type'] != 'Question': + if post_json_object['object']['type'] != 'Question': return False - if not post_object_json['object'].get('oneOf'): + if not post_json_object['object'].get('oneOf'): return False - if not isinstance(post_object_json['object']['oneOf'], list): + if not isinstance(post_json_object['object']['oneOf'], list): return False return True