From 5c8c67550928cb585a44740706f9b75d8cc9f4e2 Mon Sep 17 00:00:00 2001 From: bashrc Date: Wed, 22 Jul 2026 17:55:52 +0100 Subject: [PATCH] Implementing featured tags --- src/daemon_get_collections.py | 9 +- src/daemon_post_profile.py | 26 ++--- src/person.py | 201 +++++++++++++++------------------- src/tests.py | 20 ---- src/webapp_profile.py | 31 +++++- 5 files changed, 125 insertions(+), 162 deletions(-) diff --git a/src/daemon_get_collections.py b/src/daemon_get_collections.py index 25ebe80de..0ea55f47c 100644 --- a/src/daemon_get_collections.py +++ b/src/daemon_get_collections.py @@ -13,6 +13,7 @@ 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 remove_html from src.utils import text_in_file from src.utils import convert_domains from src.utils import get_json_content_from_accept @@ -21,7 +22,6 @@ 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 is_a_dir from src.data import load_list from src.data import load_string @@ -207,7 +207,7 @@ def get_featured_tags_collection(self, calling_domain: str, } featured_tags_filename: str = \ account_dir + '/featured_hashtags.txt' - if is_a_dir(account_dir) and is_a_file(featured_tags_filename): + if is_a_file(featured_tags_filename): hashtags_str: str = \ load_string(featured_tags_filename, 'EX: unable to load featured hashtags ' + @@ -221,7 +221,10 @@ def get_featured_tags_collection(self, calling_domain: str, tag = tag.strip().replace('#', '') if not tag: continue - url = http_prefix + '://' + domain_full + '/tagged/' + tag + tag = remove_html(tag) + if not tag: + continue + url = http_prefix + '://' + domain_full + '/tags/' + tag tag_dict = { 'href': url, 'name': '#' + tag, diff --git a/src/daemon_post_profile.py b/src/daemon_post_profile.py index ac24537b2..b5f1c5a57 100644 --- a/src/daemon_post_profile.py +++ b/src/daemon_post_profile.py @@ -1503,7 +1503,6 @@ def _profile_post_bio(actor_json: {}, fields: {}, check_name_and_bio: bool) -> bool: """ HTTP POST change user bio """ - featured_tags = get_featured_hashtags(actor_json) + ' ' actor_json['tag']: list[dict] = [] if fields.get('bio'): if fields['bio'] != actor_json['summary']: @@ -1519,11 +1518,6 @@ def _profile_post_bio(actor_json: {}, fields: {}, domain_full, bio_str, [], actor_tags, translate) - if actor_tags: - for _, tag in actor_tags.items(): - if tag['name'] + ' ' in featured_tags: - continue - actor_json['tag'].append(tag) actor_changed = True else: if check_name_and_bio: @@ -1531,7 +1525,6 @@ def _profile_post_bio(actor_json: {}, fields: {}, else: if check_name_and_bio: redirect_path = '/welcome_profile' - set_featured_hashtags(actor_json, featured_tags, True) return actor_changed, redirect_path @@ -1575,22 +1568,20 @@ def _profile_post_alsoknownas(actor_json: {}, fields: {}, return actor_changed -def _profile_post_featured_hashtags(actor_json: {}, fields: {}, - actor_changed: bool) -> bool: +def _profile_post_featured_hashtags(base_dir: str, nickname: str, domain: str, + fields: {}) -> None: """ HTTP POST featured hashtags on edit profile screen """ - featured_hashtags = get_featured_hashtags(actor_json) + featured_hashtags = get_featured_hashtags(base_dir, nickname, domain) if fields.get('featuredHashtags'): fields['featuredHashtags'] = remove_html(fields['featuredHashtags']) if featured_hashtags != fields['featuredHashtags']: - set_featured_hashtags(actor_json, + set_featured_hashtags(base_dir, nickname, domain, fields['featuredHashtags']) - actor_changed = True else: if featured_hashtags: - set_featured_hashtags(actor_json, '') - actor_changed = True - return actor_changed + set_featured_hashtags(base_dir, nickname, domain, '') + return def _profile_post_occupation(actor_json: {}, fields: {}, @@ -3184,9 +3175,8 @@ def profile_edit(self, calling_domain: str, cookie: str, _profile_post_occupation(actor_json, fields, actor_changed) - actor_changed = \ - _profile_post_featured_hashtags(actor_json, fields, - actor_changed) + _profile_post_featured_hashtags(base_dir, nickname, domain, + fields) actor_changed = \ _profile_post_alsoknownas(actor_json, fields, diff --git a/src/person.py b/src/person.py index bd3aae7d8..46f15572a 100644 --- a/src/person.py +++ b/src/person.py @@ -41,6 +41,7 @@ from src.media import process_meta_data from src.flags import is_image_file from src.timeFunctions import date_utcnow from src.timeFunctions import get_current_time_int +from src.utils import resembles_url from src.utils import get_preferred_username from src.utils import string_starts_with from src.utils import is_yggdrasil_address @@ -612,6 +613,8 @@ def _create_person_base(base_dir: str, nickname: str, domain: str, port: int, if new_person.get('roles'): del new_person['roles'] del new_person['tag'] + del new_person['featuredCollections'] + del new_person['featuredTags'] del new_person['availability'] del new_person['followers'] del new_person['following'] @@ -2292,86 +2295,90 @@ def valid_sending_actor(session, base_dir: str, return True -def get_featured_hashtags(actor_json: {}) -> str: +def get_featured_hashtags(base_dir: str, nickname: str, domain: str) -> str: """returns a string containing featured hashtags """ - result: str = '' - if not actor_json.get('tag'): - return result - if not isinstance(actor_json['tag'], list): - return result - ctr: int = 0 - for tag_dict in actor_json['tag']: - if not tag_dict.get('type'): - continue - if not isinstance(tag_dict['type'], str): - continue - if not tag_dict['type'].endswith('Hashtag'): - continue - if not tag_dict.get('name'): - continue - if not isinstance(tag_dict['name'], str): - continue - if not tag_dict.get('href'): - continue - if not isinstance(tag_dict['href'], str): - continue - tag_name = tag_dict['name'] - if not tag_name: - continue - if tag_name.startswith('#'): - tag_name = tag_name[1:] - if not tag_name: - continue - tag_url = remove_html(tag_dict['href']) - if '://' not in tag_url: - continue - if not valid_hash_tag(tag_name): - continue - result += '#' + tag_name + ' ' - ctr += 1 - if ctr >= 10: - break - return result.strip() + account_dir: str = acct_dir(base_dir, nickname, domain) + featured_tags_filename: str = account_dir + '/featured_hashtags.txt' + if is_a_file(featured_tags_filename): + hashtags_str: str = \ + load_string(featured_tags_filename, + 'EX: unable to load featured hashtags ' + + featured_tags_filename) + if hashtags_str: + return hashtags_str.strip() + return '' -def get_featured_hashtags_as_html(actor_json: {}, - profile_description: str) -> str: +def get_featured_hashtags_as_html_remote(signing_priv_key_pem: str, + session, tags_url: str, + as_header: {}, + http_prefix: str, + mitm_servers: [], + host_domain: str, + debug: bool) -> str: """returns a html string containing featured hashtags + on remote instance """ result: str = '' - if not actor_json.get('tag'): - return result - if not isinstance(actor_json['tag'], list): - return result ctr: int = 0 - for tag_dict in actor_json['tag']: - if not tag_dict.get('type'): - continue - if not isinstance(tag_dict['type'], str): - continue - if not tag_dict['type'].endswith('Hashtag'): - continue - if not tag_dict.get('name'): - continue - if not isinstance(tag_dict['name'], str): - continue - if not tag_dict.get('href'): - continue - if not isinstance(tag_dict['href'], str): - continue - tag_name = tag_dict['name'] + quiet: bool = True + featured_hashtags_json = \ + get_json(signing_priv_key_pem, session, tags_url, as_header, + None, debug, mitm_servers, __version__, http_prefix, + host_domain, 20, quiet) + if get_json_valid(featured_hashtags_json): + if 'items' not in featured_hashtags_json: + return '' + if not isinstance(featured_hashtags_json['items'], list): + return '' + for tag_dict in featured_hashtags_json['items']: + if 'type' not in tag_dict: + continue + if 'name' not in tag_dict: + continue + if 'href' not in tag_dict: + continue + if not isinstance(tag_dict['type'], str): + continue + if not isinstance(tag_dict['name'], str): + continue + if not isinstance(tag_dict['href'], str): + continue + tag_url = remove_html(tag_dict['href']) + if not resembles_url(tag_url): + continue + tag_name = remove_html(tag_dict['name']) + tag_name = tag_name.replace('#', '') + if not tag_name or not tag_url: + continue + result += \ + ' ' + ctr += 1 + if ctr >= 10: + break + return result + + +def get_featured_hashtags_as_html(base_dir: str, nickname: str, domain: str, + http_prefix: str, domain_full: str) -> str: + """returns a html string containing featured hashtags + """ + hashtags_str = get_featured_hashtags(base_dir, nickname, domain) + separator = ' ' + if ',' in hashtags_str: + separator = ',' + hashtags_list: list[str] = hashtags_str.split(separator) + + result: str = '' + ctr: int = 0 + for tag in hashtags_list: + tag_name = tag.strip().replace('#', '') if not tag_name: continue - if tag_name.startswith('#'): - tag_name = tag_name[1:] - if not tag_name: - continue - if '/tags/' + tag_name + '"' in profile_description: - continue - if ' #' + tag_name in profile_description: - continue - tag_url = remove_html(tag_dict['href']) + tag_url = http_prefix + '://' + domain_full + '/tags/' + tag_name if '://' not in tag_url: continue if not valid_hash_tag(tag_name): @@ -2389,53 +2396,17 @@ def get_featured_hashtags_as_html(actor_json: {}, return result -def set_featured_hashtags(actor_json: {}, hashtags: str, - append: bool = False) -> None: +def set_featured_hashtags(base_dir: str, nickname: str, domain: str, + hashtags: str) -> None: """sets featured hashtags """ - separator_str = ' ' - separators = (',', ' ') - for separator_str in separators: - if separator_str in hashtags: - break - tag_list = hashtags.split(separator_str) - result: list[str] = [] - tags_used: list[str] = [] - actor_id = actor_json['id'] - actor_domain = actor_id.split('://')[1] - if '/' in actor_domain: - actor_domain = actor_domain.split('/')[0] - actor_url = \ - actor_id.split('://')[0] + '://' + actor_domain - for tag_str in tag_list: - if not tag_str: - continue - if not tag_str.startswith('#'): - tag_str = '#' + tag_str - if tag_str in tags_used: - continue - url = actor_url + '/tags/' + tag_str.replace('#', '') - result.append({ - "name": tag_str, - "type": "Hashtag", - "href": url - }) - tags_used.append(tag_str) - if len(result) >= 10: - break - # add any non-hashtags to the result - if actor_json.get('tag'): - for tag_dict in actor_json['tag']: - if not tag_dict.get('type'): - continue - if not isinstance(tag_dict['type'], str): - continue - if tag_dict['type'] != 'Hashtag': - result.append(tag_dict) - if not append: - actor_json['tag'] = result - else: - actor_json['tag'] += result + account_dir: str = acct_dir(base_dir, nickname, domain) + if not is_a_dir(account_dir): + return + featured_tags_filename: str = account_dir + '/featured_hashtags.txt' + save_string(hashtags, featured_tags_filename, + 'EX: set_featured_hashtags unabel to save ' + + featured_tags_filename) def update_memorial_flags(base_dir: str, person_cache: {}) -> None: diff --git a/src/tests.py b/src/tests.py index b35417a45..fd4418265 100644 --- a/src/tests.py +++ b/src/tests.py @@ -124,8 +124,6 @@ from src.follow import add_follower_of_person from src.follow import unfollow_account from src.follow import unfollower_of_account from src.follow import send_follow_request -from src.person import set_featured_hashtags -from src.person import get_featured_hashtags from src.person import create_person from src.person import create_group from src.person import set_display_nickname @@ -8885,23 +8883,6 @@ def _test_xor_hashes(): assert result == expected -def _test_featured_tags() -> None: - print('featured_tags') - actor_json = { - "id": "https://somesite/users/somenick" - } - featured_tags = '#dog #cat' - set_featured_hashtags(actor_json, featured_tags) - assert actor_json.get('tag') - assert len(actor_json['tag']) == 2 - result = get_featured_hashtags(actor_json) - if result != featured_tags: - pprint(actor_json) - print('result: ' + result) - print('expected: ' + featured_tags) - assert result == featured_tags - - def _test_remove_tag() -> None: print('remove_tag') test_html = 'This is a test' @@ -9945,7 +9926,6 @@ def run_all_tests(): _test_is_right_to_left() _test_format_mixed_rtl() _test_remove_tag() - _test_featured_tags() _test_xor_hashes() _test_convert_markdown() _test_remove_style() diff --git a/src/webapp_profile.py b/src/webapp_profile.py index 61c7c918b..702fff2ff 100644 --- a/src/webapp_profile.py +++ b/src/webapp_profile.py @@ -61,6 +61,7 @@ from src.languages import get_actor_languages from src.skills import get_skills from src.theme import get_themes_list from src.person import get_featured_hashtags_as_html +from src.person import get_featured_hashtags_as_html_remote from src.person import get_featured_hashtags from src.person import person_box_json from src.person import get_actor_json @@ -444,8 +445,27 @@ def html_profile_after_search(authorized: bool, add_emoji_to_display_name(session, base_dir, http_prefix, nickname, domain, profile_description, False, translate) - featured_hashtags: str = \ - get_featured_hashtags_as_html(profile_json, profile_description) + domain_full: str = get_full_domain(domain, port) + featured_hashtags: str = '' + if profile_json.get('featuredTags'): + if isinstance(profile_json['featuredTags'], str): + if search_domain == domain: + # account on this instance + featured_hashtags = \ + get_featured_hashtags_as_html(base_dir, + search_nickname, domain, + http_prefix, domain_full) + else: + # account on remote instance + tags_url: str = profile_json['featuredTags'] + featured_hashtags = \ + get_featured_hashtags_as_html_remote(signing_priv_key_pem, + session, tags_url, + as_header, + http_prefix, + mitm_servers, + domain, + debug) outbox_url: str = None if not profile_json.get('outbox'): if debug: @@ -550,8 +570,6 @@ def html_profile_after_search(authorized: bool, art_site_url, donate_url) - domain_full: str = get_full_domain(domain, port) - follow_is_permitted: bool = True if not profile_json.get('followers'): # no followers collection specified within actor @@ -1302,7 +1320,8 @@ def html_profile(signing_priv_key_pem: str, if profile_description: profile_description = standardize_text(profile_description) featured_hashtags: str = \ - get_featured_hashtags_as_html(profile_json, profile_description) + get_featured_hashtags_as_html(base_dir, nickname, domain, + http_prefix, domain_full) posts_button: str = 'button' following_button: str = 'button' moved_button: str = 'button' @@ -3717,7 +3736,7 @@ def html_edit_profile(server, translate: {}, elif actor_json.get('copiedTo'): if isinstance(actor_json['copiedTo'], str): moved_to = remove_html(actor_json['copiedTo']) - featured_hashtags = get_featured_hashtags(actor_json) + featured_hashtags = get_featured_hashtags(base_dir, nickname, domain) donate_url = get_donation_url(actor_json) website_url = get_website(actor_json, translate) gemini_link = get_gemini_link(actor_json)