From bd39240b875aea7fccd2148ef6d61caac11afb75 Mon Sep 17 00:00:00 2001
From: bashrc
Date: Fri, 21 Aug 2026 13:20:03 +0100
Subject: [PATCH] Show labels on profiles
---
src/daemon_utils.py | 6 +++-
src/utils.py | 58 ++++++++++++++++++++++++++++++++++++
src/webapp_person_options.py | 5 +++-
src/webapp_profile.py | 22 ++++++++++----
src/webapp_utils.py | 22 ++++++++++++++
5 files changed, 106 insertions(+), 7 deletions(-)
diff --git a/src/daemon_utils.py b/src/daemon_utils.py
index 4e4f4755c..2e718cb85 100644
--- a/src/daemon_utils.py
+++ b/src/daemon_utils.py
@@ -28,6 +28,7 @@ from src.posts import add_to_field
from src.status import actor_status_expired
from src.status import get_actor_status
from src.mitm import detect_mitm
+from src.utils import get_labels_from_json
from src.utils import valid_nickname
from src.utils import is_yggdrasil_url
from src.utils import data_dir
@@ -723,6 +724,7 @@ def show_person_options(self, calling_domain: str, path: str,
moved_to: str = ''
repo_url = None
status = None
+ labels_list: list[str] = []
actor_json = \
get_person_from_cache(base_dir,
options_actor,
@@ -775,6 +777,7 @@ def show_person_options(self, calling_domain: str, path: str,
also_known_as = remove_html(actor_json['alsoKnownAs'])
repo_url = get_repo_url(actor_json)
status = get_actor_status(actor_json)
+ labels_list = get_labels_from_json(actor_json)
if status:
if actor_status_expired(actor_json['sm:status']):
status: str = ''
@@ -846,7 +849,8 @@ def show_person_options(self, calling_domain: str, path: str,
art_site_url,
self.server.mitm_servers,
status,
- self.server.system_language)
+ self.server.system_language,
+ labels_list)
if msg:
msg = msg.encode('utf-8')
msglen = len(msg)
diff --git a/src/utils.py b/src/utils.py
index a3cdd1acc..68ca0918c 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -4454,3 +4454,61 @@ def url_text_to_number(url: str) -> str:
"""
hash_result = get_sha_256(url)
return base64.b64encode(hash_result).decode('utf-8')
+
+
+def get_labels_from_json(json_object: {}) -> []:
+ """Returns labels attached to an actor or post
+ """
+ tags_list: list[dict] = []
+ if 'attachment' in json_object:
+ if isinstance(json_object['attachment'], list):
+ tags_list = json_object['attachment']
+ obj: dict = json_object
+ if has_object_dict(json_object):
+ obj = json_object['object']
+ if 'tag' in obj:
+ if isinstance(obj['tag'], list):
+ tags_list = obj['tag']
+ if not tags_list:
+ return []
+ labels: list[str] = []
+ for tag_dict in tags_list:
+ if not isinstance(tag_dict, dict):
+ continue
+ if 'type' not in tag_dict or 'name' not in tag_dict:
+ continue
+ if 'value' not in tag_dict or 'href' not in tag_dict:
+ continue
+ if not isinstance(tag_dict['type'], str):
+ continue
+ if not isinstance(tag_dict['name'], str):
+ continue
+ if tag_dict['type'] == 'PropertyValue' and \
+ 'value' in tag_dict:
+ if not isinstance(tag_dict['value'], str):
+ continue
+ if tag_dict['name'] == 'Labels':
+ labels_list = tag_dict['value'].split(',')
+ for label_str in labels_list:
+ label_str = label_str.strip()
+ label_str = remove_html(label_str)
+ if label_str:
+ labels.append(label_str)
+ elif tag_dict['name'] == 'Label':
+ label_str = tag_dict['value'].strip()
+ label_str = remove_html(label_str)
+ if label_str:
+ if 'href' in tag_dict:
+ if isinstance(tag_dict['href'], str):
+ if resembles_url(tag_dict['href']):
+ label_str += '###' + tag_dict['href']
+ labels.append(label_str)
+ elif tag_dict['type'] == 'Label':
+ label_str = remove_html(tag_dict['name'])
+ if label_str:
+ if 'href' in tag_dict:
+ if isinstance(tag_dict['href'], str):
+ if resembles_url(tag_dict['href']):
+ label_str += '###' + tag_dict['href']
+ labels.append(label_str)
+ return labels
diff --git a/src/webapp_person_options.py b/src/webapp_person_options.py
index 1dbb43162..142e05b10 100644
--- a/src/webapp_person_options.py
+++ b/src/webapp_person_options.py
@@ -30,6 +30,7 @@ from src.follow import is_following_actor
from src.followingCalendar import receiving_calendar_events
from src.notifyOnPost import notify_when_person_posts
from src.person import get_person_notes
+from src.webapp_utils import labels_list_html
from src.webapp_utils import mitm_warning_html
from src.webapp_utils import html_header_with_external_style
from src.webapp_utils import html_footer
@@ -178,7 +179,8 @@ def html_person_options(default_timeline: str,
art_site_url: str,
mitm_servers: [],
status: str,
- system_language: str) -> str:
+ system_language: str,
+ labels_list: []) -> str:
"""Show options for a person: view/follow/block/report
"""
options_link_str: str = ''
@@ -414,6 +416,7 @@ def html_person_options(default_timeline: str,
options_str += \
' ' + status_str + ': ' + \
remove_html(status) + '
\n'
+ options_str += labels_list_html(labels_list)
if pronouns:
options_str += \
' ' + translate['Pronouns'] + \
diff --git a/src/webapp_profile.py b/src/webapp_profile.py
index c50ae2f4c..995e94da4 100644
--- a/src/webapp_profile.py
+++ b/src/webapp_profile.py
@@ -22,6 +22,7 @@ from src.textmode import text_mode_removals
from src.unicodetext import uninvert_text
from src.unicodetext import standardize_text
from src.occupation import get_occupation_name
+from src.utils import get_labels_from_json
from src.utils import get_preferred_username
from src.utils import is_private_browser
from src.utils import replace_embedded_map_with_link
@@ -103,6 +104,7 @@ from src.follow import get_follower_domains
from src.follow import is_following_actor
from src.webapp_frontscreen import html_front_screen
from src.textmode import text_mode_browser
+from src.webapp_utils import labels_list_html
from src.webapp_utils import get_display_name_prefix
from src.webapp_utils import html_following_dropdown
from src.webapp_utils import edit_number_field
@@ -397,6 +399,7 @@ def html_profile_after_search(authorized: bool,
ricochet_address: str = get_ricochet_address(profile_json)
briar_address: str = get_briar_address(profile_json)
xmpp_address: str = get_xmpp_address(profile_json)
+ labels_list: list[str] = get_labels_from_json(profile_json)
moved_to: str = ''
if profile_json.get('movedTo') or profile_json.get('copiedTo'):
@@ -568,7 +571,8 @@ def html_profile_after_search(authorized: bool,
youtube, peertube, loops, pixelfed,
discord, music_site_url,
art_site_url,
- donate_url)
+ donate_url,
+ labels_list)
follow_is_permitted: bool = True
if not profile_json.get('followers'):
@@ -785,7 +789,8 @@ def _get_profile_header(base_dir: str, http_prefix: str, nickname: str,
no_of_books: int,
authorized: bool,
birth_date: str,
- premium: bool) -> str:
+ premium: bool,
+ labels_list: []) -> str:
"""The header of the profile screen, containing background
image and avatar
"""
@@ -812,6 +817,8 @@ def _get_profile_header(base_dir: str, http_prefix: str, nickname: str,
occupation_str += \
' ' + occupation_name + '
\n'
+ labels_str = labels_list_html(labels_list)
+
html_str += '
' + display_name + '\n
\n'
if premium:
@@ -821,7 +828,7 @@ def _get_profile_header(base_dir: str, http_prefix: str, nickname: str,
if pronouns:
html_str += ' ' + pronouns + '
\n'
- html_str += occupation_str
+ html_str += occupation_str + labels_str
# show if the actor is proxied
if not actor_proxied:
@@ -991,7 +998,8 @@ def _get_profile_header_after_search(base_dir: str,
discord: str,
music_site_url: str,
art_site_url: str,
- donate_url: str) -> str:
+ donate_url: str,
+ labels_list: []) -> str:
"""The header of a searched for handle, containing background
image and avatar
"""
@@ -1098,6 +1106,7 @@ def _get_profile_header_after_search(base_dir: str,
other_accounts_html += '
\n'
if ctr > 0:
html_str += other_accounts_html
+ html_str += labels_list_html(labels_list)
if is_valid_date(birth_date):
birth_date: str = remove_html(birth_date)
html_str += \
@@ -1761,6 +1770,8 @@ def html_profile(signing_priv_key_pem: str,
if profile_json.get('vcard:bday'):
birth_date = profile_json['vcard:bday']
+ labels_list: list[str] = get_labels_from_json(profile_json)
+
profile_header_str: str = \
_get_profile_header(base_dir, http_prefix,
nickname,
@@ -1777,7 +1788,8 @@ def html_profile(signing_priv_key_pem: str,
occupation_name,
actor_proxied, actor,
no_of_books, authorized,
- birth_date, premium)
+ birth_date, premium,
+ labels_list)
# keyboard navigation
user_path_str: str = '/users/' + nickname
diff --git a/src/webapp_utils.py b/src/webapp_utils.py
index cbc435ad7..0e4273aa5 100644
--- a/src/webapp_utils.py
+++ b/src/webapp_utils.py
@@ -2563,3 +2563,25 @@ def get_display_name_prefix(actor_type: str,
nickname in chatbot_nicknames()):
display_name_prefix = '[' + translate['Bot'] + '] '
return display_name_prefix
+
+
+def labels_list_html(labels_list: []) -> str:
+ """Returns html for a list of labels for an actor or post
+ """
+ labels_str = ''
+ for label in labels_list:
+ label_url = ''
+ if '###' in label:
+ label_url = label.split('###')[1]
+ label = label.split('###')[0]
+ if labels_str:
+ labels_str += ' '
+ if not label_url:
+ labels_str += '' + label + ''
+ else:
+ labels_str += '' + \
+ label + ''
+ if labels_str:
+ labels_str = '' + labels_str + '
\n'
+ return labels_str