| 
									
										
										
										
											2020-04-04 10:12:34 +00:00
										 |  |  | __filename__ = "question.py" | 
					
						
							|  |  |  | __author__ = "Bob Mottram" | 
					
						
							|  |  |  | __license__ = "AGPL3+" | 
					
						
							| 
									
										
										
										
											2023-01-21 23:03:30 +00:00
										 |  |  | __version__ = "1.4.0" | 
					
						
							| 
									
										
										
										
											2020-04-04 10:12:34 +00:00
										 |  |  | __maintainer__ = "Bob Mottram" | 
					
						
							| 
									
										
										
										
											2021-09-10 16:14:50 +00:00
										 |  |  | __email__ = "bob@libreserver.org" | 
					
						
							| 
									
										
										
										
											2020-04-04 10:12:34 +00:00
										 |  |  | __status__ = "Production" | 
					
						
							| 
									
										
										
										
											2021-06-15 15:08:12 +00:00
										 |  |  | __module_group__ = "ActivityPub" | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2021-12-26 20:36:08 +00:00
										 |  |  | from utils import locate_post | 
					
						
							| 
									
										
										
										
											2021-12-26 15:13:34 +00:00
										 |  |  | from utils import load_json | 
					
						
							| 
									
										
										
										
											2021-12-26 14:47:21 +00:00
										 |  |  | from utils import save_json | 
					
						
							| 
									
										
										
										
											2021-12-26 10:57:03 +00:00
										 |  |  | from utils import has_object_dict | 
					
						
							| 
									
										
										
										
											2022-06-10 11:43:33 +00:00
										 |  |  | from utils import text_in_file | 
					
						
							| 
									
										
										
										
											2023-02-28 18:01:24 +00:00
										 |  |  | from utils import dangerous_markup | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-04 10:12:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  | def is_vote(base_dir: str, nickname: str, domain: str, | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |             post_json_object: {}, debug: bool) -> bool: | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     """ is the given post a vote on a Question?
 | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |     if debug: | 
					
						
							|  |  |  |         print('VOTE: ' + str(post_obj)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     # is the replied to post a Question? | 
					
						
							|  |  |  |     in_reply_to = post_obj['inReplyTo'] | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     question_post_filename = \ | 
					
						
							|  |  |  |         locate_post(base_dir, nickname, domain, in_reply_to) | 
					
						
							|  |  |  |     if not question_post_filename: | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: question does not exist ' + in_reply_to) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     question_json = load_json(question_post_filename) | 
					
						
							|  |  |  |     if not question_json: | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: invalid json ' + question_post_filename) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     if not has_object_dict(question_json): | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: question without object ' + | 
					
						
							|  |  |  |                   question_post_filename) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     if not question_json['object'].get('type'): | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: question without type ' + | 
					
						
							|  |  |  |                   question_post_filename) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     if question_json['type'] != 'Question': | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: not a question ' + | 
					
						
							|  |  |  |                   question_post_filename) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # does the question have options? | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     if not question_json['object'].get('oneOf'): | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: question has no options ' + | 
					
						
							|  |  |  |                   question_post_filename) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     if not isinstance(question_json['object']['oneOf'], list): | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: question options is not a list ' + | 
					
						
							|  |  |  |                   question_post_filename) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-30 10:11:08 +00:00
										 |  |  |     # does the reply name field match any possible question option? | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     reply_vote = post_json_object['name'] | 
					
						
							|  |  |  |     found_answer_json = None | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     for possible_answer in question_json['object']['oneOf']: | 
					
						
							|  |  |  |         if not possible_answer.get('name'): | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |             continue | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |         if possible_answer['name'] == reply_vote: | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |             found_answer_json = possible_answer | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |             break | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     if not found_answer_json: | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('VOTE REJECT: question answer not found ' + | 
					
						
							|  |  |  |                   question_post_filename + ' ' + reply_vote) | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |         return False | 
					
						
							|  |  |  |     return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def question_update_votes(base_dir: str, nickname: str, domain: str, | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |                           reply_json: {}, debug: bool) -> ({}, str): | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     """ For a given reply update the votes on a question
 | 
					
						
							|  |  |  |     Returns the question json object if the vote totals were changed | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-01-12 11:58:41 +00:00
										 |  |  |     if not is_vote(base_dir, nickname, domain, reply_json, debug): | 
					
						
							| 
									
										
										
										
											2021-11-03 21:53:30 +00:00
										 |  |  |         return None, None | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |     # update the voters file | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     voters_file_separator = ';;;' | 
					
						
							|  |  |  |     voters_filename = question_post_filename.replace('.json', '.voters') | 
					
						
							|  |  |  |     if not os.path.isfile(voters_filename): | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |         # create a new voters file | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |             with open(voters_filename, 'w+', | 
					
						
							|  |  |  |                       encoding='utf-8') as voters_file: | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                 voters_file.write(reply_json['actor'] + | 
					
						
							|  |  |  |                                   voters_file_separator + | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |                                   reply_vote + '\n') | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |         except OSError: | 
					
						
							|  |  |  |             print('EX: unable to write voters file ' + voters_filename) | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2022-06-10 11:43:33 +00:00
										 |  |  |         if not text_in_file(reply_json['actor'], voters_filename): | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |             # append to the voters file | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |                 with open(voters_filename, 'a+', | 
					
						
							|  |  |  |                           encoding='utf-8') as voters_file: | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                     voters_file.write(reply_json['actor'] + | 
					
						
							|  |  |  |                                       voters_file_separator + | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |                                       reply_vote + '\n') | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |             except OSError: | 
					
						
							|  |  |  |                 print('EX: unable to append to voters file ' + voters_filename) | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |         else: | 
					
						
							|  |  |  |             # change an entry in the voters file | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |             with open(voters_filename, 'r', | 
					
						
							|  |  |  |                       encoding='utf-8') as voters_file: | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                 lines = voters_file.readlines() | 
					
						
							| 
									
										
										
										
											2020-04-04 10:12:34 +00:00
										 |  |  |                 newlines = [] | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                 save_voters_file = False | 
					
						
							|  |  |  |                 for vote_line in lines: | 
					
						
							|  |  |  |                     if vote_line.startswith(reply_json['actor'] + | 
					
						
							|  |  |  |                                             voters_file_separator): | 
					
						
							|  |  |  |                         new_vote_line = reply_json['actor'] + \ | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |                             voters_file_separator + reply_vote + '\n' | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                         if vote_line == new_vote_line: | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |                             break | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                         save_voters_file = True | 
					
						
							|  |  |  |                         newlines.append(new_vote_line) | 
					
						
							| 
									
										
										
										
											2019-11-29 19:22:11 +00:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                         newlines.append(vote_line) | 
					
						
							|  |  |  |                 if save_voters_file: | 
					
						
							|  |  |  |                     try: | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |                         with open(voters_filename, 'w+', | 
					
						
							|  |  |  |                                   encoding='utf-8') as voters_file: | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |                             for vote_line in newlines: | 
					
						
							|  |  |  |                                 voters_file.write(vote_line) | 
					
						
							|  |  |  |                     except OSError: | 
					
						
							|  |  |  |                         print('EX: unable to write voters file2 ' + | 
					
						
							|  |  |  |                               voters_filename) | 
					
						
							| 
									
										
										
										
											2019-11-29 19:22:11 +00:00
										 |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2021-11-03 21:53:30 +00:00
										 |  |  |                     return None, None | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |     # update the vote counts | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     question_totals_changed = False | 
					
						
							|  |  |  |     for possible_answer in question_json['object']['oneOf']: | 
					
						
							|  |  |  |         if not possible_answer.get('name'): | 
					
						
							| 
									
										
										
										
											2019-11-29 18:46:21 +00:00
										 |  |  |             continue | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |         total_items = 0 | 
					
						
							| 
									
										
										
										
											2022-06-09 14:46:30 +00:00
										 |  |  |         with open(voters_filename, 'r', encoding='utf-8') as voters_file: | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |             lines = voters_file.readlines() | 
					
						
							|  |  |  |             for vote_line in lines: | 
					
						
							|  |  |  |                 if vote_line.endswith(voters_file_separator + | 
					
						
							|  |  |  |                                       possible_answer['name'] + '\n'): | 
					
						
							|  |  |  |                     total_items += 1 | 
					
						
							|  |  |  |         if possible_answer['replies']['totalItems'] != total_items: | 
					
						
							|  |  |  |             possible_answer['replies']['totalItems'] = total_items | 
					
						
							|  |  |  |             question_totals_changed = True | 
					
						
							|  |  |  |     if not question_totals_changed: | 
					
						
							| 
									
										
										
										
											2021-11-03 21:53:30 +00:00
										 |  |  |         return None, None | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-29 19:22:11 +00:00
										 |  |  |     # save the question with altered totals | 
					
						
							| 
									
										
										
										
											2021-12-31 11:23:00 +00:00
										 |  |  |     save_json(question_json, question_post_filename) | 
					
						
							|  |  |  |     return question_json, question_post_filename | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 09:34:39 +00:00
										 |  |  | def is_html_question(html_str: str) -> bool: | 
					
						
							|  |  |  |     """ is the given html string a Question?
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if 'input type="radio" name="answer"' in html_str: | 
					
						
							|  |  |  |         return True | 
					
						
							|  |  |  |     return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  | def is_question(post_json_object: {}) -> bool: | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  |     """ is the given post a question?
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     if post_json_object['type'] != 'Create' and \ | 
					
						
							|  |  |  |        post_json_object['type'] != 'Update': | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     if not has_object_dict(post_json_object): | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     if not post_json_object['object'].get('type'): | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     if post_json_object['object']['type'] != 'Question': | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     if not post_json_object['object'].get('oneOf'): | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2023-01-12 10:45:39 +00:00
										 |  |  |     if not isinstance(post_json_object['object']['oneOf'], list): | 
					
						
							| 
									
										
										
										
											2020-11-09 15:40:24 +00:00
										 |  |  |         return False | 
					
						
							|  |  |  |     return True | 
					
						
							| 
									
										
										
										
											2023-02-28 18:01:24 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def dangerous_question(question_json: {}, | 
					
						
							|  |  |  |                        allow_local_network_access: bool) -> bool: | 
					
						
							|  |  |  |     """does the given question contain dangerous markup?
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if question_json.get('oneOf'): | 
					
						
							|  |  |  |         question_options = question_json['oneOf'] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         question_options = question_json['object']['oneOf'] | 
					
						
							|  |  |  |     for option in question_options: | 
					
						
							|  |  |  |         if option.get('name'): | 
					
						
							| 
									
										
										
										
											2023-05-18 11:15:18 +00:00
										 |  |  |             if dangerous_markup(option['name'], | 
					
						
							|  |  |  |                                 allow_local_network_access, []): | 
					
						
							| 
									
										
										
										
											2023-02-28 18:01:24 +00:00
										 |  |  |                 return True | 
					
						
							|  |  |  |     return False |