| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  | __filename__ = "daemon_post_question.py" | 
					
						
							|  |  |  | __author__ = "Bob Mottram" | 
					
						
							|  |  |  | __license__ = "AGPL3+" | 
					
						
							| 
									
										
										
										
											2024-12-22 23:37:30 +00:00
										 |  |  | __version__ = "1.6.0" | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  | __maintainer__ = "Bob Mottram" | 
					
						
							|  |  |  | __email__ = "bob@libreserver.org" | 
					
						
							|  |  |  | __status__ = "Production" | 
					
						
							| 
									
										
										
										
											2024-12-25 14:31:14 +00:00
										 |  |  | __module_group__ = "Daemon POST" | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import errno | 
					
						
							|  |  |  | import urllib.parse | 
					
						
							|  |  |  | from socket import error as SocketError | 
					
						
							| 
									
										
										
										
											2024-08-09 12:29:22 +00:00
										 |  |  | from utils import replace_strings | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  | from utils import remove_post_from_cache | 
					
						
							|  |  |  | from utils import get_cached_post_filename | 
					
						
							|  |  |  | from utils import load_json | 
					
						
							|  |  |  | from utils import locate_post | 
					
						
							|  |  |  | from utils import get_full_domain | 
					
						
							|  |  |  | from utils import get_domain_from_actor | 
					
						
							|  |  |  | from utils import text_in_file | 
					
						
							|  |  |  | from utils import get_nickname_from_actor | 
					
						
							|  |  |  | from utils import acct_dir | 
					
						
							|  |  |  | from httpheaders import redirect_headers | 
					
						
							|  |  |  | from city import get_spoofed_city | 
					
						
							|  |  |  | from languages import get_understood_languages | 
					
						
							|  |  |  | from posts import create_direct_message_post | 
					
						
							|  |  |  | from daemon_utils import post_to_outbox | 
					
						
							|  |  |  | from inbox import populate_replies | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def receive_vote(self, calling_domain: str, cookie: str, | 
					
						
							|  |  |  |                  path: str, http_prefix: str, | 
					
						
							|  |  |  |                  domain: str, domain_full: str, port: int, | 
					
						
							|  |  |  |                  onion_domain: str, i2p_domain: str, | 
					
						
							|  |  |  |                  curr_session, proxy_type: str, | 
					
						
							|  |  |  |                  base_dir: str, city: str, | 
					
						
							|  |  |  |                  person_cache: {}, debug: bool, | 
					
						
							|  |  |  |                  system_language: str, | 
					
						
							|  |  |  |                  low_bandwidth: bool, | 
					
						
							|  |  |  |                  dm_license_url: str, | 
					
						
							|  |  |  |                  content_license_url: str, | 
					
						
							|  |  |  |                  translate: {}, max_replies: int, | 
					
						
							|  |  |  |                  project_version: str, | 
					
						
							| 
									
										
										
										
											2024-04-14 12:22:24 +00:00
										 |  |  |                  recent_posts_cache: {}, | 
					
						
							|  |  |  |                  default_timeline: str, | 
					
						
							|  |  |  |                  auto_cw_cache: {}) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |     """Receive a vote on a question via POST
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     first_post_id = '' | 
					
						
							|  |  |  |     if '?firstpost=' in path: | 
					
						
							|  |  |  |         first_post_id = path.split('?firstpost=')[1] | 
					
						
							|  |  |  |         path = path.split('?firstpost=')[0] | 
					
						
							|  |  |  |     if ';firstpost=' in path: | 
					
						
							|  |  |  |         first_post_id = path.split(';firstpost=')[1] | 
					
						
							|  |  |  |         path = path.split(';firstpost=')[0] | 
					
						
							|  |  |  |     if first_post_id: | 
					
						
							|  |  |  |         if '?' in first_post_id: | 
					
						
							|  |  |  |             first_post_id = first_post_id.split('?')[0] | 
					
						
							|  |  |  |         if ';' in first_post_id: | 
					
						
							|  |  |  |             first_post_id = first_post_id.split(';')[0] | 
					
						
							|  |  |  |         first_post_id = first_post_id.replace('/', '--') | 
					
						
							|  |  |  |         first_post_id = ';firstpost=' + first_post_id.replace('#', '--') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     last_post_id = '' | 
					
						
							|  |  |  |     if '?lastpost=' in path: | 
					
						
							|  |  |  |         last_post_id = path.split('?lastpost=')[1] | 
					
						
							|  |  |  |         path = path.split('?lastpost=')[0] | 
					
						
							|  |  |  |     if ';lastpost=' in path: | 
					
						
							|  |  |  |         last_post_id = path.split(';lastpost=')[1] | 
					
						
							|  |  |  |         path = path.split(';lastpost=')[0] | 
					
						
							|  |  |  |     if last_post_id: | 
					
						
							|  |  |  |         if '?' in last_post_id: | 
					
						
							|  |  |  |             last_post_id = last_post_id.split('?')[0] | 
					
						
							|  |  |  |         if ';' in last_post_id: | 
					
						
							|  |  |  |             last_post_id = last_post_id.split(';')[0] | 
					
						
							|  |  |  |         last_post_id = last_post_id.replace('/', '--') | 
					
						
							|  |  |  |         last_post_id = ';lastpost=' + last_post_id.replace('#', '--') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     page_number = 1 | 
					
						
							|  |  |  |     if '?page=' in path: | 
					
						
							|  |  |  |         page_number_str = path.split('?page=')[1] | 
					
						
							|  |  |  |         if '#' in page_number_str: | 
					
						
							|  |  |  |             page_number_str = page_number_str.split('#')[0] | 
					
						
							|  |  |  |         if len(page_number_str) > 5: | 
					
						
							|  |  |  |             page_number_str = "1" | 
					
						
							|  |  |  |         if page_number_str.isdigit(): | 
					
						
							|  |  |  |             page_number = int(page_number_str) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # the actor who votes | 
					
						
							|  |  |  |     users_path = path.replace('/question', '') | 
					
						
							|  |  |  |     actor = http_prefix + '://' + domain_full + users_path | 
					
						
							|  |  |  |     nickname = get_nickname_from_actor(actor) | 
					
						
							|  |  |  |     if not nickname: | 
					
						
							|  |  |  |         if calling_domain.endswith('.onion') and onion_domain: | 
					
						
							|  |  |  |             actor = 'http://' + onion_domain + users_path | 
					
						
							|  |  |  |         elif (calling_domain.endswith('.i2p') and i2p_domain): | 
					
						
							|  |  |  |             actor = 'http://' + i2p_domain + users_path | 
					
						
							|  |  |  |         actor_path_str = \ | 
					
						
							| 
									
										
										
										
											2024-04-14 12:22:24 +00:00
										 |  |  |             actor + '/' + default_timeline + \ | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |             '?page=' + str(page_number) | 
					
						
							|  |  |  |         redirect_headers(self, actor_path_str, | 
					
						
							| 
									
										
										
										
											2024-04-16 13:47:21 +00:00
										 |  |  |                          cookie, calling_domain, 303) | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |         self.server.postreq_busy = False | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # get the parameters | 
					
						
							|  |  |  |     length = int(self.headers['Content-length']) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         question_params = self.rfile.read(length).decode('utf-8') | 
					
						
							|  |  |  |     except SocketError as ex: | 
					
						
							|  |  |  |         if ex.errno == errno.ECONNRESET: | 
					
						
							|  |  |  |             print('EX: POST question_params connection was reset') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             print('EX: POST question_params socket error') | 
					
						
							|  |  |  |         self.send_response(400) | 
					
						
							|  |  |  |         self.end_headers() | 
					
						
							|  |  |  |         self.server.postreq_busy = False | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     except ValueError as ex: | 
					
						
							|  |  |  |         print('EX: POST question_params rfile.read failed, ' + str(ex)) | 
					
						
							|  |  |  |         self.send_response(400) | 
					
						
							|  |  |  |         self.end_headers() | 
					
						
							|  |  |  |         self.server.postreq_busy = False | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-09 12:29:22 +00:00
										 |  |  |     replacements = { | 
					
						
							|  |  |  |         '+': ' ', | 
					
						
							|  |  |  |         '%3F': '' | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     question_params = replace_strings(question_params, replacements) | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |     question_params = \ | 
					
						
							|  |  |  |         urllib.parse.unquote_plus(question_params.strip()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # post being voted on | 
					
						
							|  |  |  |     message_id = None | 
					
						
							|  |  |  |     if 'messageId=' in question_params: | 
					
						
							|  |  |  |         message_id = question_params.split('messageId=')[1] | 
					
						
							|  |  |  |         if '&' in message_id: | 
					
						
							|  |  |  |             message_id = message_id.split('&')[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     answer = None | 
					
						
							|  |  |  |     if 'answer=' in question_params: | 
					
						
							|  |  |  |         answer = question_params.split('answer=')[1] | 
					
						
							|  |  |  |         if '&' in answer: | 
					
						
							|  |  |  |             answer = answer.split('&')[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     _send_reply_to_question(self, base_dir, http_prefix, | 
					
						
							|  |  |  |                             nickname, domain, domain_full, port, | 
					
						
							|  |  |  |                             message_id, answer, | 
					
						
							|  |  |  |                             curr_session, proxy_type, city, | 
					
						
							|  |  |  |                             person_cache, debug, | 
					
						
							|  |  |  |                             system_language, | 
					
						
							|  |  |  |                             low_bandwidth, | 
					
						
							|  |  |  |                             dm_license_url, | 
					
						
							|  |  |  |                             content_license_url, | 
					
						
							|  |  |  |                             translate, max_replies, | 
					
						
							|  |  |  |                             project_version, | 
					
						
							| 
									
										
										
										
											2024-04-14 12:22:24 +00:00
										 |  |  |                             recent_posts_cache, | 
					
						
							|  |  |  |                             auto_cw_cache) | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |     if calling_domain.endswith('.onion') and onion_domain: | 
					
						
							|  |  |  |         actor = 'http://' + onion_domain + users_path | 
					
						
							|  |  |  |     elif (calling_domain.endswith('.i2p') and i2p_domain): | 
					
						
							|  |  |  |         actor = 'http://' + i2p_domain + users_path | 
					
						
							|  |  |  |     actor_path_str = \ | 
					
						
							| 
									
										
										
										
											2024-04-14 12:22:24 +00:00
										 |  |  |         actor + '/' + default_timeline + \ | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |         '?page=' + str(page_number) + first_post_id + last_post_id | 
					
						
							|  |  |  |     redirect_headers(self, actor_path_str, cookie, | 
					
						
							| 
									
										
										
										
											2024-04-16 13:47:21 +00:00
										 |  |  |                      calling_domain, 303) | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |     self.server.postreq_busy = False | 
					
						
							|  |  |  |     return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _send_reply_to_question(self, base_dir: str, | 
					
						
							|  |  |  |                             http_prefix: str, | 
					
						
							|  |  |  |                             nickname: str, domain: str, | 
					
						
							|  |  |  |                             domain_full: str, | 
					
						
							|  |  |  |                             port: int, | 
					
						
							|  |  |  |                             message_id: str, | 
					
						
							|  |  |  |                             answer: str, | 
					
						
							|  |  |  |                             curr_session, proxy_type: str, | 
					
						
							|  |  |  |                             city_name: str, | 
					
						
							|  |  |  |                             person_cache: {}, | 
					
						
							|  |  |  |                             debug: bool, | 
					
						
							|  |  |  |                             system_language: str, | 
					
						
							|  |  |  |                             low_bandwidth: bool, | 
					
						
							|  |  |  |                             dm_license_url: str, | 
					
						
							|  |  |  |                             content_license_url: str, | 
					
						
							|  |  |  |                             translate: {}, | 
					
						
							|  |  |  |                             max_replies: int, | 
					
						
							|  |  |  |                             project_version: str, | 
					
						
							| 
									
										
										
										
											2024-04-14 12:22:24 +00:00
										 |  |  |                             recent_posts_cache: {}, | 
					
						
							|  |  |  |                             auto_cw_cache: {}) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |     """Sends a reply to a question
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     votes_filename = \ | 
					
						
							|  |  |  |         acct_dir(base_dir, nickname, domain) + \ | 
					
						
							|  |  |  |         '/questions.txt' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if os.path.isfile(votes_filename): | 
					
						
							|  |  |  |         # have we already voted on this? | 
					
						
							|  |  |  |         if text_in_file(message_id, votes_filename): | 
					
						
							|  |  |  |             print('Already voted on message ' + message_id) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     print('Voting on message ' + message_id) | 
					
						
							|  |  |  |     print('Vote for: ' + answer) | 
					
						
							|  |  |  |     comments_enabled = True | 
					
						
							|  |  |  |     attach_image_filename = None | 
					
						
							|  |  |  |     media_type = None | 
					
						
							|  |  |  |     image_description = None | 
					
						
							|  |  |  |     video_transcript = None | 
					
						
							|  |  |  |     in_reply_to = message_id | 
					
						
							|  |  |  |     in_reply_to_atom_uri = message_id | 
					
						
							|  |  |  |     subject = None | 
					
						
							|  |  |  |     schedule_post = False | 
					
						
							|  |  |  |     event_date = None | 
					
						
							|  |  |  |     event_time = None | 
					
						
							|  |  |  |     event_end_time = None | 
					
						
							|  |  |  |     location = None | 
					
						
							|  |  |  |     conversation_id = None | 
					
						
							| 
									
										
										
										
											2024-10-06 16:22:13 +00:00
										 |  |  |     convthread_id = None | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |     buy_url = '' | 
					
						
							|  |  |  |     chat_url = '' | 
					
						
							|  |  |  |     city = get_spoofed_city(city_name, base_dir, nickname, domain) | 
					
						
							|  |  |  |     languages_understood = \ | 
					
						
							|  |  |  |         get_understood_languages(base_dir, http_prefix, | 
					
						
							|  |  |  |                                  nickname, domain_full, | 
					
						
							|  |  |  |                                  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) | 
					
						
							|  |  |  |     message_json = None | 
					
						
							|  |  |  |     if reply_to_nickname and reply_to_domain: | 
					
						
							|  |  |  |         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_direct_message_post(base_dir, nickname, domain, | 
					
						
							|  |  |  |                                        port, http_prefix, | 
					
						
							|  |  |  |                                        mentions_str + ' ' + answer, | 
					
						
							|  |  |  |                                        False, False, | 
					
						
							|  |  |  |                                        comments_enabled, | 
					
						
							|  |  |  |                                        attach_image_filename, | 
					
						
							|  |  |  |                                        media_type, image_description, | 
					
						
							|  |  |  |                                        video_transcript, city, | 
					
						
							|  |  |  |                                        in_reply_to, in_reply_to_atom_uri, | 
					
						
							|  |  |  |                                        subject, debug, | 
					
						
							|  |  |  |                                        schedule_post, | 
					
						
							|  |  |  |                                        event_date, event_time, | 
					
						
							|  |  |  |                                        event_end_time, | 
					
						
							|  |  |  |                                        location, | 
					
						
							|  |  |  |                                        system_language, | 
					
						
							| 
									
										
										
										
											2024-10-06 16:22:13 +00:00
										 |  |  |                                        conversation_id, convthread_id, | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |                                        low_bandwidth, | 
					
						
							|  |  |  |                                        dm_license_url, | 
					
						
							|  |  |  |                                        content_license_url, '', | 
					
						
							|  |  |  |                                        languages_understood, False, | 
					
						
							|  |  |  |                                        translate, buy_url, | 
					
						
							|  |  |  |                                        chat_url, | 
					
						
							| 
									
										
										
										
											2024-10-15 22:46:47 +00:00
										 |  |  |                                        auto_cw_cache, curr_session) | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |     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 post_to_outbox(self, message_json, | 
					
						
							|  |  |  |                           project_version, nickname, | 
					
						
							|  |  |  |                           curr_session, proxy_type): | 
					
						
							|  |  |  |             post_filename = \ | 
					
						
							|  |  |  |                 locate_post(base_dir, nickname, domain, message_id) | 
					
						
							|  |  |  |             if post_filename: | 
					
						
							|  |  |  |                 post_json_object = load_json(post_filename) | 
					
						
							|  |  |  |                 if post_json_object: | 
					
						
							|  |  |  |                     populate_replies(base_dir, | 
					
						
							|  |  |  |                                      http_prefix, | 
					
						
							|  |  |  |                                      domain_full, | 
					
						
							|  |  |  |                                      post_json_object, | 
					
						
							|  |  |  |                                      max_replies, | 
					
						
							|  |  |  |                                      debug) | 
					
						
							|  |  |  |                     # record the vote | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         with open(votes_filename, 'a+', | 
					
						
							| 
									
										
										
										
											2024-07-14 13:01:46 +00:00
										 |  |  |                                   encoding='utf-8') as fp_votes: | 
					
						
							|  |  |  |                             fp_votes.write(message_id + '\n') | 
					
						
							| 
									
										
										
										
											2024-03-04 10:24:50 +00:00
										 |  |  |                     except OSError: | 
					
						
							|  |  |  |                         print('EX: unable to write vote ' + | 
					
						
							|  |  |  |                               votes_filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     # ensure that the cached post is removed if it exists, | 
					
						
							|  |  |  |                     # so that it then will be recreated | 
					
						
							|  |  |  |                     cached_post_filename = \ | 
					
						
							|  |  |  |                         get_cached_post_filename(base_dir, | 
					
						
							|  |  |  |                                                  nickname, domain, | 
					
						
							|  |  |  |                                                  post_json_object) | 
					
						
							|  |  |  |                     if cached_post_filename: | 
					
						
							|  |  |  |                         if os.path.isfile(cached_post_filename): | 
					
						
							|  |  |  |                             try: | 
					
						
							|  |  |  |                                 os.remove(cached_post_filename) | 
					
						
							|  |  |  |                             except OSError: | 
					
						
							|  |  |  |                                 print('EX: _send_reply_to_question ' + | 
					
						
							|  |  |  |                                       'unable to delete ' + | 
					
						
							|  |  |  |                                       cached_post_filename) | 
					
						
							|  |  |  |                     # remove from memory cache | 
					
						
							|  |  |  |                     remove_post_from_cache(post_json_object, | 
					
						
							|  |  |  |                                            recent_posts_cache) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             print('ERROR: unable to post vote to outbox') | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print('ERROR: unable to create vote') |