__filename__ = "daemon_post_remove.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.5.0" __maintainer__ = "Bob Mottram" __email__ = "bob@libreserver.org" __status__ = "Production" __module_group__ = "Core POST" import os import errno import urllib.parse from socket import error as SocketError from utils import save_json from utils import load_json from utils import acct_dir from utils import get_instance_url from utils import get_domain_from_actor from utils import local_actor_url from utils import get_config_param from utils import get_nickname_from_actor from reading import remove_reading_event from httpheaders import redirect_headers from posts import is_moderator from shares import remove_shared_item2 from shares import add_shares_to_actor from cache import store_person_in_cache from cache import remove_person_from_cache from cache import get_person_from_cache from person import get_actor_update_json from daemon_utils import post_to_outbox_thread from daemon_utils import post_to_outbox from happening import remove_calendar_event def remove_reading_status(self, calling_domain: str, cookie: str, path: str, base_dir: str, http_prefix: str, domain_full: str, onion_domain: str, i2p_domain: str, debug: bool, books_cache: {}) -> None: """Remove a reading status from the profile screen """ users_path = path.split('/removereadingstatus')[0] origin_path_str = http_prefix + '://' + domain_full + users_path reader_nickname = get_nickname_from_actor(origin_path_str) if not reader_nickname: self.send_response(400) self.end_headers() self.server.postreq_busy = False return length = int(self.headers['Content-length']) try: remove_reading_status_params = \ self.rfile.read(length).decode('utf-8') except SocketError as ex: if ex.errno == errno.ECONNRESET: print('EX: POST remove_reading_status_params ' + 'connection was reset') else: print('EX: POST remove_reading_status_params socket error') self.send_response(400) self.end_headers() self.server.postreq_busy = False return except ValueError as ex: print('EX: POST remove_reading_status_params rfile.read failed, ' + str(ex)) self.send_response(400) self.end_headers() self.server.postreq_busy = False return if '&submitRemoveReadingStatus=' in remove_reading_status_params: reading_actor = \ urllib.parse.unquote_plus(remove_reading_status_params) reading_actor = reading_actor.split('actor=')[1] if '&' in reading_actor: reading_actor = reading_actor.split('&')[0] if reading_actor == origin_path_str: post_secs_since_epoch = \ urllib.parse.unquote_plus(remove_reading_status_params) post_secs_since_epoch = \ post_secs_since_epoch.split('publishedtimesec=')[1] if '&' in post_secs_since_epoch: post_secs_since_epoch = post_secs_since_epoch.split('&')[0] book_event_type = \ urllib.parse.unquote_plus(remove_reading_status_params) book_event_type = \ book_event_type.split('bookeventtype=')[1] if '&' in book_event_type: book_event_type = book_event_type.split('&')[0] remove_reading_event(base_dir, reading_actor, post_secs_since_epoch, book_event_type, books_cache, debug) if calling_domain.endswith('.onion') and onion_domain: origin_path_str = 'http://' + onion_domain + users_path elif (calling_domain.endswith('.i2p') and i2p_domain): origin_path_str = 'http://' + i2p_domain + users_path redirect_headers(self, origin_path_str, cookie, calling_domain, 303) self.server.postreq_busy = False def remove_share(self, calling_domain: str, cookie: str, authorized: bool, path: str, base_dir: str, http_prefix: str, domain_full: str, onion_domain: str, i2p_domain: str, curr_session, proxy_type: str, person_cache: {}, max_shares_on_profile: int, project_version: str) -> None: """Removes a shared item """ users_path = path.split('/rmshare')[0] origin_path_str = http_prefix + '://' + domain_full + users_path length = int(self.headers['Content-length']) try: remove_share_confirm_params = \ self.rfile.read(length).decode('utf-8') except SocketError as ex: if ex.errno == errno.ECONNRESET: print('EX: POST remove_share_confirm_params ' + 'connection was reset') else: print('EX: POST remove_share_confirm_params socket error') self.send_response(400) self.end_headers() self.server.postreq_busy = False return except ValueError as ex: print('EX: POST remove_share_confirm_params ' + 'rfile.read failed, ' + str(ex)) self.send_response(400) self.end_headers() self.server.postreq_busy = False return if '&submitYes=' in remove_share_confirm_params and authorized: remove_share_confirm_params = \ remove_share_confirm_params.replace('+', ' ').strip() remove_share_confirm_params = \ urllib.parse.unquote_plus(remove_share_confirm_params) share_actor = remove_share_confirm_params.split('actor=')[1] if '&' in share_actor: share_actor = share_actor.split('&')[0] admin_nickname = get_config_param(base_dir, 'admin') admin_actor = \ local_actor_url(http_prefix, admin_nickname, domain_full) actor = origin_path_str actor_nickname = get_nickname_from_actor(actor) if not actor_nickname: self.send_response(400) self.end_headers() self.server.postreq_busy = False return if actor == share_actor or actor == admin_actor or \ is_moderator(base_dir, actor_nickname): item_id = remove_share_confirm_params.split('itemID=')[1] if '&' in item_id: item_id = item_id.split('&')[0] share_nickname = get_nickname_from_actor(share_actor) share_domain, _ = \ get_domain_from_actor(share_actor) if share_nickname and share_domain: remove_shared_item2(base_dir, share_nickname, share_domain, item_id, 'shares') # remove shared items from the actor attachments # https://codeberg.org/fediverse/fep/ # src/branch/main/fep/0837/fep-0837.md actor = \ get_instance_url(calling_domain, http_prefix, domain_full, onion_domain, i2p_domain) + \ '/users/' + share_nickname actor_json = get_person_from_cache(base_dir, actor, person_cache) if not actor_json: actor_filename = \ acct_dir(base_dir, share_nickname, share_domain) + '.json' if os.path.isfile(actor_filename): actor_json = load_json(actor_filename, 1, 1) if actor_json: if add_shares_to_actor(base_dir, share_nickname, share_domain, actor_json, max_shares_on_profile): remove_person_from_cache(base_dir, actor, person_cache) store_person_in_cache(base_dir, actor, actor_json, person_cache, True) actor_filename = acct_dir(base_dir, share_nickname, share_domain) + '.json' save_json(actor_json, actor_filename) # send profile update to followers update_actor_json = \ get_actor_update_json(actor_json) print('Sending actor update ' + 'after change to attached shares 2: ' + str(update_actor_json)) post_to_outbox(self, update_actor_json, project_version, share_nickname, curr_session, proxy_type) if calling_domain.endswith('.onion') and onion_domain: origin_path_str = 'http://' + onion_domain + users_path elif (calling_domain.endswith('.i2p') and i2p_domain): origin_path_str = 'http://' + i2p_domain + users_path redirect_headers(self, origin_path_str + '/tlshares', cookie, calling_domain, 303) self.server.postreq_busy = False def remove_wanted(self, calling_domain: str, cookie: str, authorized: bool, path: str, base_dir: str, http_prefix: str, domain_full: str, onion_domain: str, i2p_domain: str) -> None: """Removes a wanted item """ users_path = path.split('/rmwanted')[0] origin_path_str = http_prefix + '://' + domain_full + users_path length = int(self.headers['Content-length']) try: remove_share_confirm_params = \ self.rfile.read(length).decode('utf-8') except SocketError as ex: if ex.errno == errno.ECONNRESET: print('EX: POST remove_share_confirm_params ' + 'connection was reset') else: print('EX: POST remove_share_confirm_params socket error') self.send_response(400) self.end_headers() self.server.postreq_busy = False return except ValueError as ex: print('EX: POST remove_share_confirm_params ' + 'rfile.read failed, ' + str(ex)) self.send_response(400) self.end_headers() self.server.postreq_busy = False return if '&submitYes=' in remove_share_confirm_params and authorized: remove_share_confirm_params = \ remove_share_confirm_params.replace('+', ' ').strip() remove_share_confirm_params = \ urllib.parse.unquote_plus(remove_share_confirm_params) share_actor = remove_share_confirm_params.split('actor=')[1] if '&' in share_actor: share_actor = share_actor.split('&')[0] admin_nickname = get_config_param(base_dir, 'admin') admin_actor = \ local_actor_url(http_prefix, admin_nickname, domain_full) actor = origin_path_str actor_nickname = get_nickname_from_actor(actor) if not actor_nickname: self.send_response(400) self.end_headers() self.server.postreq_busy = False return if actor == share_actor or actor == admin_actor or \ is_moderator(base_dir, actor_nickname): item_id = remove_share_confirm_params.split('itemID=')[1] if '&' in item_id: item_id = item_id.split('&')[0] share_nickname = get_nickname_from_actor(share_actor) share_domain, _ = \ get_domain_from_actor(share_actor) if share_nickname and share_domain: remove_shared_item2(base_dir, share_nickname, share_domain, item_id, 'wanted') if calling_domain.endswith('.onion') and onion_domain: origin_path_str = 'http://' + onion_domain + users_path elif (calling_domain.endswith('.i2p') and i2p_domain): origin_path_str = 'http://' + i2p_domain + users_path redirect_headers(self, origin_path_str + '/tlwanted', cookie, calling_domain, 303) self.server.postreq_busy = False def receive_remove_post(self, calling_domain: str, cookie: str, path: str, base_dir: str, http_prefix: str, domain: str, domain_full: str, onion_domain: str, i2p_domain: str, curr_session, proxy_type: str) -> None: """Endpoint for removing posts after confirmation """ page_number = 1 users_path = path.split('/rmpost')[0] origin_path_str = \ http_prefix + '://' + \ domain_full + users_path length = int(self.headers['Content-length']) try: remove_post_confirm_params = \ self.rfile.read(length).decode('utf-8') except SocketError as ex: if ex.errno == errno.ECONNRESET: print('EX: POST remove_post_confirm_params ' + 'connection was reset') else: print('EX: POST remove_post_confirm_params socket error') self.send_response(400) self.end_headers() self.server.postreq_busy = False return except ValueError as ex: print('EX: POST remove_post_confirm_params ' + 'rfile.read failed, ' + str(ex)) self.send_response(400) self.end_headers() self.server.postreq_busy = False return if '&submitYes=' in remove_post_confirm_params: remove_post_confirm_params = \ urllib.parse.unquote_plus(remove_post_confirm_params) if 'messageId=' in remove_post_confirm_params: remove_message_id = \ remove_post_confirm_params.split('messageId=')[1] elif 'eventid=' in remove_post_confirm_params: remove_message_id = \ remove_post_confirm_params.split('eventid=')[1] else: self.send_response(400) self.end_headers() self.server.postreq_busy = False return if '&' in remove_message_id: remove_message_id = remove_message_id.split('&')[0] print('remove_message_id: ' + remove_message_id) if 'pageNumber=' in remove_post_confirm_params: page_number_str = \ remove_post_confirm_params.split('pageNumber=')[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) year_str = None if 'year=' in remove_post_confirm_params: year_str = remove_post_confirm_params.split('year=')[1] if '&' in year_str: year_str = year_str.split('&')[0] month_str = None if 'month=' in remove_post_confirm_params: month_str = remove_post_confirm_params.split('month=')[1] if '&' in month_str: month_str = month_str.split('&')[0] if '/statuses/' in remove_message_id: remove_post_actor = remove_message_id.split('/statuses/')[0] print('origin_path_str: ' + origin_path_str) print('remove_post_actor: ' + remove_post_actor) if origin_path_str in remove_post_actor: to_list = [ 'https://www.w3.org/ns/activitystreams#Public', remove_post_actor ] delete_json = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': remove_post_actor, 'object': remove_message_id, 'to': to_list, 'cc': [remove_post_actor + '/followers'], 'type': 'Delete' } self.post_to_nickname = \ get_nickname_from_actor(remove_post_actor) if self.post_to_nickname: if month_str and year_str: if len(month_str) <= 3 and \ len(year_str) <= 5 and \ month_str.isdigit() and \ year_str.isdigit(): year_int = int(year_str) month_int = int(month_str) remove_calendar_event(base_dir, self.post_to_nickname, domain, year_int, month_int, remove_message_id) post_to_outbox_thread(self, delete_json, curr_session, proxy_type) if calling_domain.endswith('.onion') and onion_domain: origin_path_str = 'http://' + onion_domain + users_path elif (calling_domain.endswith('.i2p') and i2p_domain): origin_path_str = 'http://' + i2p_domain + users_path if page_number == 1: redirect_headers(self, origin_path_str + '/outbox', cookie, calling_domain, 303) else: page_number_str = str(page_number) actor_path_str = \ origin_path_str + '/outbox?page=' + page_number_str redirect_headers(self, actor_path_str, cookie, calling_domain, 303) self.server.postreq_busy = False