From c40d756888379c6590f0eb5b4876f0b15d171e7f Mon Sep 17 00:00:00 2001 From: bashrc Date: Mon, 20 Jul 2026 14:06:28 +0100 Subject: [PATCH] Return individual featured items --- src/acceptreject.py | 41 +++++++++++++++++++--- src/collections.py | 2 ++ src/daemon_get.py | 17 +++++++++ src/daemon_get_collections.py | 66 +++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 4 deletions(-) diff --git a/src/acceptreject.py b/src/acceptreject.py index dcb583596..3566757a7 100644 --- a/src/acceptreject.py +++ b/src/acceptreject.py @@ -37,6 +37,9 @@ from src.data import is_a_file from src.data import is_a_dir from src.data import makedir from src.data import erase_file +from src.data import save_string +from src.data import append_string +from src.data import load_list def _create_quote_accept_reject(receiving_actor: str, @@ -311,24 +314,43 @@ def _accept_feature_authorization(base_dir: str, message_json: {}, return if not is_a_dir(account_dir + '/stamps'): makedir(account_dir + '/stamps') + feature_number_lookup_filename = \ + account_dir + '/stamps/feature_number_lookup.txt' if message_json['type'] == 'Reject': if not is_a_dir(account_dir + '/stamps/rejected'): makedir(account_dir + '/stamps/rejected') - rejected_filename = \ + rejected_filename: str = \ account_dir + '/stamps/rejected/' + actor.replace('/', '#') if not is_a_file(rejected_filename): save_json(message_json, rejected_filename) # remove any accepted FeaturedItem - accepted_filename = \ + accepted_filename: str = \ rejected_filename.replace('/rejected/', '/accepted/') if is_a_file(accepted_filename): erase_file(accepted_filename, 'EX: unable to remove accepted FeaturedItem ' + accepted_filename) + # remove any lookup file entry + if is_a_file(feature_number_lookup_filename): + if text_in_file(' ' + actor + '\n', + feature_number_lookup_filename): + feature_numbers_lookup: list[str] = \ + load_list(feature_number_lookup_filename, + 'EX: unable to load list ' + + feature_number_lookup_filename) + if feature_numbers_lookup: + feature_numbers_lookup_str = '' + for line_str in feature_numbers_lookup: + if ' ' + actor + '\n' not in line_str: + feature_numbers_lookup_str += line_str + save_string(feature_numbers_lookup_str, + feature_number_lookup_filename, + 'EX: unable to save ' + + feature_number_lookup_filename) else: if not is_a_dir(account_dir + '/stamps/accepted'): makedir(account_dir + '/stamps/accepted') - accepted_filename = \ + accepted_filename: str = \ account_dir + '/stamps/accepted/' + actor.replace('/', '#') # convert Accept into FeaturedItem feature_number, published = get_status_number() @@ -345,8 +367,19 @@ def _accept_feature_authorization(base_dir: str, message_json: {}, } if not is_a_file(accepted_filename): save_json(featured_item, accepted_filename) + # save a lookup of the feature number + if is_a_file(feature_number_lookup_filename): + append_string(feature_number + ' ' + actor + '\n', + feature_number_lookup_filename, + 'EX: unable to append to ' + + feature_number_lookup_filename) + else: + save_string(feature_number + ' ' + actor + '\n', + feature_number_lookup_filename, + 'EX: unable to save to ' + + feature_number_lookup_filename) # remove any rejected FeaturedItem - rejected_filename = \ + rejected_filename: str = \ accepted_filename.replace('/accepted/', '/rejected/') if is_a_file(rejected_filename): erase_file(rejected_filename, diff --git a/src/collections.py b/src/collections.py index 754173cb6..7107ee179 100644 --- a/src/collections.py +++ b/src/collections.py @@ -21,6 +21,7 @@ from src.utils import get_domain_from_actor from src.utils import url_text_to_number from src.utils import save_json from src.utils import resembles_url +from src.utils import remove_eol from src.data import is_a_file from src.data import load_list from src.data import is_a_dir @@ -230,6 +231,7 @@ def get_featured_collections_feed(base_dir: str, for text in lines: if not text: continue + text = remove_eol(text) if text.startswith('# '): # store the current collection _update_collections(collection_name, collection_items, diff --git a/src/daemon_get.py b/src/daemon_get.py index 2c72cadae..810ac1324 100644 --- a/src/daemon_get.py +++ b/src/daemon_get.py @@ -209,6 +209,7 @@ from src.daemon_get_post import show_individual_at_post from src.daemon_get_post import show_new_post from src.daemon_get_post import show_conversation_thread from src.daemon_get_collections import get_featured_collection +from src.daemon_get_collections import get_featured_collection_item from src.daemon_get_collections import get_featured_tags_collection from src.daemon_get_collections import get_following_json from src.daemon_get_collections import get_feature_authorization @@ -1873,6 +1874,22 @@ def daemon_http_get(self) -> None: self.server.yggdrasil_domain) return + if not html_getreq and \ + users_in_path and '/featured/' in self.path: + nickname = self.path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + # return the featured posts collection + get_featured_collection_item(self, calling_domain, referer_domain, + self.server.base_dir, + self.server.http_prefix, + nickname, self.server.domain, + self.server.system_language, + self.server.onion_domain, + self.server.i2p_domain, + self.server.yggdrasil_domain) + return + if not html_getreq and \ users_in_path and '/stamps/' in self.path: nickname = self.path.split('/users/')[1] diff --git a/src/daemon_get_collections.py b/src/daemon_get_collections.py index 49c0d55a9..f8bea185a 100644 --- a/src/daemon_get_collections.py +++ b/src/daemon_get_collections.py @@ -13,12 +13,15 @@ from src.httpcodes import write2 from src.httpcodes import http_404 from src.httpheaders import set_headers from src.posts import json_pin_post +from src.utils import text_in_file from src.utils import convert_domains from src.utils import get_json_content_from_accept from src.utils import acct_dir from src.utils import load_json +from src.utils import remove_eol from src.follow import get_following_feed from src.data import is_a_file +from src.data import load_list def get_feature_authorization(self, calling_domain: str, @@ -82,6 +85,69 @@ def get_feature_authorization(self, calling_domain: str, write2(self, msg) +def get_featured_collection_item(self, calling_domain: str, + referer_domain: str, + base_dir: str, + http_prefix: str, + nickname: str, domain: str, + onion_domain: str, + i2p_domain: str, + yggdrasil_domain: str) -> None: + """Returns an individual FeaturedItem from the collection + https://codeberg.org/fediverse/fep/src/branch/main/fep/7aa9/fep-7aa9.md + """ + if '/featured/' not in self.path: + return + feature_number = self.path.split('/featured/')[1] + account_dir = acct_dir(base_dir, nickname, domain) + feature_number_lookup_filename = \ + account_dir + '/stamps/feature_number_lookup.txt' + if not is_a_file(feature_number_lookup_filename): + http_404(self, 110) + return + if not text_in_file(feature_number + ' ', + feature_number_lookup_filename): + http_404(self, 111) + return + feature_numbers_lookup: list[str] = \ + load_list(feature_number_lookup_filename, + 'EX: unable to load list ' + + feature_number_lookup_filename) + if not feature_numbers_lookup: + http_404(self, 112) + return + actor = '' + for line_str in feature_numbers_lookup: + if line_str.startswith(feature_number + ' '): + actor = line_str.split(' ')[1] + actor = remove_eol(actor) + break + if not actor: + http_404(self, 113) + return + accepted_filename: str = \ + account_dir + '/stamps/accepted/' + actor.replace('/', '#') + if not is_a_file(accepted_filename): + http_404(self, 114) + return + featured_item = load_json(accepted_filename) + if not featured_item: + http_404(self, 115) + return + + msg_str = json.dumps(featured_item, ensure_ascii=False) + msg_str = convert_domains(calling_domain, referer_domain, + msg_str, http_prefix, + domain, onion_domain, i2p_domain, + yggdrasil_domain) + msg = msg_str.encode('utf-8') + msglen = len(msg) + accept_str = self.headers['Accept'] + protocol_str = get_json_content_from_accept(accept_str) + set_headers(self, protocol_str, msglen, None, calling_domain, False) + write2(self, msg) + + def get_featured_collection(self, calling_domain: str, referer_domain: str, base_dir: str,