Merge branch 'main' of gitlab.com:bashrc2/epicyon

merge-requests/30/head
Bob Mottram 2022-03-24 17:07:49 +00:00
commit 962bfccac0
46 changed files with 877 additions and 258 deletions

View File

@ -187,13 +187,14 @@ def create_announce(session, base_dir: str, federation_list: [],
group_account = False
if has_users_path(object_url):
announce_nickname = get_nickname_from_actor(object_url)
announce_domain, announce_port = get_domain_from_actor(object_url)
if '/' + str(announce_nickname) + '/' in object_url:
announce_actor = \
object_url.split('/' + announce_nickname + '/')[0] + \
'/' + announce_nickname
if has_group_type(base_dir, announce_actor, person_cache):
group_account = True
if announce_nickname:
announce_domain, announce_port = get_domain_from_actor(object_url)
if '/' + str(announce_nickname) + '/' in object_url:
announce_actor = \
object_url.split('/' + announce_nickname + '/')[0] + \
'/' + announce_nickname
if has_group_type(base_dir, announce_actor, person_cache):
group_account = True
if announce_nickname and announce_domain:
send_signed_json(new_announce, session, base_dir,

View File

@ -68,6 +68,8 @@ def outbox_availability(base_dir: str, nickname: str, message_json: {},
return False
actor_nickname = get_nickname_from_actor(message_json['actor'])
if not actor_nickname:
return False
if actor_nickname != nickname:
return False
domain, _ = get_domain_from_actor(message_json['actor'])

View File

@ -46,6 +46,8 @@ def undo_bookmarks_collection_entry(recent_posts_cache: {},
# remove any cached version of this post so that the
# bookmark icon is changed
nickname = get_nickname_from_actor(actor)
if not nickname:
return
cached_post_filename = \
get_cached_post_filename(base_dir, nickname,
domain, post_json_object)
@ -166,6 +168,8 @@ def update_bookmarks_collection(recent_posts_cache: {},
# remove any cached version of this post so that the
# bookmark icon is changed
nickname = get_nickname_from_actor(actor)
if not nickname:
return
cached_post_filename = \
get_cached_post_filename(base_dir, nickname,
domain, post_json_object)

View File

@ -7,6 +7,8 @@ __email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
import math
import html
import os
import email.parser
import urllib.parse
@ -701,7 +703,7 @@ def replace_content_duplicates(content: str) -> str:
return content
def remove_text_formatting(content: str) -> str:
def remove_text_formatting(content: str, bold_reading: bool) -> str:
"""Removes markup for bold, italics, etc
"""
if is_pgp_encrypted(content) or contains_pgp_public_key(content):
@ -709,6 +711,9 @@ def remove_text_formatting(content: str) -> str:
if '<' not in content:
return content
for markup in REMOVE_MARKUP:
if bold_reading:
if markup == 'b':
continue
content = content.replace('<' + markup + '>', '')
content = content.replace('</' + markup + '>', '')
content = content.replace('<' + markup.upper() + '>', '')
@ -1323,3 +1328,62 @@ def contains_invalid_local_links(content: str) -> bool:
if '?' + inv_str + '=' in content:
return True
return False
def bold_reading_string(text: str) -> str:
"""Returns bold reading formatted text
"""
text = html.unescape(text)
add_paragraph_markup = False
if '<p>' in text:
text = text.replace('</p>', '\n').replace('<p>', '')
add_paragraph_markup = True
paragraphs = text.split('\n')
parag_ctr = 0
new_text = ''
for parag in paragraphs:
words = parag.split(' ')
new_parag = ''
reading_markup = False
for wrd in words:
if '<' in wrd:
reading_markup = True
if reading_markup and '>' in wrd:
reading_markup = False
wrd_len = len(wrd)
if not reading_markup and wrd_len > 1 and \
'<' not in wrd and '>' not in wrd and \
'&' not in wrd and '=' not in wrd and \
not wrd.startswith(':'):
prefix = ''
postfix = ''
if wrd.startswith('"'):
prefix = '"'
wrd = wrd[1:]
if wrd.endswith('"'):
postfix = '"'
wrd = wrd[:wrd_len - 1]
initial_chars = int(math.ceil(wrd_len / 2.0))
new_parag += \
prefix + '<b>' + wrd[:initial_chars] + '</b>' + \
wrd[initial_chars:] + postfix + ' '
else:
new_parag += wrd + ' '
parag_ctr += 1
new_parag = new_parag.strip()
if not new_parag:
continue
if parag_ctr < len(paragraphs):
if not add_paragraph_markup:
new_text += new_parag + '\n'
else:
new_text += '<p>' + new_parag + '</p>'
else:
if not add_paragraph_markup:
new_text += new_parag
else:
new_text += '<p>' + new_parag + '</p>'
return new_text

490
daemon.py

File diff suppressed because it is too large Load Diff

View File

@ -683,7 +683,7 @@ def _read_local_box_post(session, nickname: str, domain: str,
translate: {}, your_actor: str,
domain_full: str, person_cache: {},
signing_priv_key_pem: str,
blocked_cache: {}) -> {}:
blocked_cache: {}, bold_reading: bool) -> {}:
"""Reads a post from the given timeline
Returns the post json
"""
@ -724,7 +724,7 @@ def _read_local_box_post(session, nickname: str, domain: str,
system_language,
domain_full, person_cache,
signing_priv_key_pem,
blocked_cache)
blocked_cache, bold_reading)
if post_json_object2:
if has_object_dict(post_json_object2):
if post_json_object2['object'].get('attributedTo') and \
@ -1356,6 +1356,8 @@ def run_desktop_client(base_dir: str, proxy_type: str, http_prefix: str,
"""Runs the desktop and screen reader client,
which announces new inbox items
"""
bold_reading = False
# TODO: this should probably be retrieved somehow from the server
signing_priv_key_pem = None
@ -1674,7 +1676,7 @@ def run_desktop_client(base_dir: str, proxy_type: str, http_prefix: str,
espeak, translate, your_actor,
domain_full, person_cache,
signing_priv_key_pem,
blocked_cache)
blocked_cache, bold_reading)
print('')
say_str = 'Press Enter to continue...'
say_str2 = _highlight_text(say_str)
@ -2464,7 +2466,8 @@ def run_desktop_client(base_dir: str, proxy_type: str, http_prefix: str,
system_language,
domain_full, person_cache,
signing_priv_key_pem,
blocked_cache)
blocked_cache,
bold_reading)
if post_json_object2:
post_json_object = post_json_object2
if post_json_object:

118
inbox.py
View File

@ -297,7 +297,8 @@ def _inbox_store_post_to_html_cache(recent_posts_cache: {},
cw_lists: {},
lists_enabled: str,
timezone: str,
mitm: bool) -> None:
mitm: bool,
bold_reading: bool) -> None:
"""Converts the json post into html and stores it in a cache
This enables the post to be quickly displayed later
"""
@ -322,7 +323,8 @@ def _inbox_store_post_to_html_cache(recent_posts_cache: {},
peertube_instances, allow_local_network_access,
theme_name, system_language, max_like_count,
not_dm, True, True, False, True, False,
cw_lists, lists_enabled, timezone, mitm)
cw_lists, lists_enabled, timezone, mitm,
bold_reading)
def valid_inbox(base_dir: str, nickname: str, domain: str) -> bool:
@ -1047,7 +1049,8 @@ def _receive_like(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str) -> bool:
lists_enabled: str,
bold_reading: bool) -> bool:
"""Receives a Like activity within the POST section of HTTPServer
"""
if message_json['type'] != 'Like':
@ -1155,7 +1158,8 @@ def _receive_like(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm)
lists_enabled, timezone, mitm,
bold_reading)
return True
@ -1174,7 +1178,8 @@ def _receive_undo_like(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str) -> bool:
lists_enabled: str,
bold_reading: bool) -> bool:
"""Receives an undo like activity within the POST section of HTTPServer
"""
if message_json['type'] != 'Undo':
@ -1272,7 +1277,8 @@ def _receive_undo_like(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm)
lists_enabled, timezone, mitm,
bold_reading)
return True
@ -1292,7 +1298,7 @@ def _receive_reaction(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str) -> bool:
lists_enabled: str, bold_reading: bool) -> bool:
"""Receives an emoji reaction within the POST section of HTTPServer
"""
if message_json['type'] != 'EmojiReact':
@ -1422,7 +1428,8 @@ def _receive_reaction(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm)
lists_enabled, timezone, mitm,
bold_reading)
return True
@ -1443,7 +1450,8 @@ def _receive_undo_reaction(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str) -> bool:
lists_enabled: str,
bold_reading: bool) -> bool:
"""Receives an undo emoji reaction within the POST section of HTTPServer
"""
if message_json['type'] != 'Undo':
@ -1559,7 +1567,8 @@ def _receive_undo_reaction(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm)
lists_enabled, timezone, mitm,
bold_reading)
return True
@ -1577,7 +1586,7 @@ def _receive_bookmark(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: {}) -> bool:
lists_enabled: {}, bold_reading: bool) -> bool:
"""Receives a bookmark activity within the POST section of HTTPServer
"""
if not message_json.get('type'):
@ -1673,7 +1682,8 @@ def _receive_bookmark(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm)
lists_enabled, timezone, mitm,
bold_reading)
return True
@ -1693,7 +1703,7 @@ def _receive_undo_bookmark(recent_posts_cache: {},
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int, cw_lists: {},
lists_enabled: str) -> bool:
lists_enabled: str, bold_reading: bool) -> bool:
"""Receives an undo bookmark activity within the POST section of HTTPServer
"""
if not message_json.get('type'):
@ -1791,7 +1801,7 @@ def _receive_undo_bookmark(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists, lists_enabled,
timezone, mitm)
timezone, mitm, bold_reading)
return True
@ -1887,7 +1897,7 @@ def _receive_announce(recent_posts_cache: {},
allow_deletion: bool,
peertube_instances: [],
max_like_count: int, cw_lists: {},
lists_enabled: str) -> bool:
lists_enabled: str, bold_reading: bool) -> bool:
"""Receives an announce activity within the POST section of HTTPServer
"""
if message_json['type'] != 'Announce':
@ -1941,6 +1951,9 @@ def _receive_announce(recent_posts_cache: {},
# is the announce actor blocked?
nickname = handle.split('@')[0]
actor_nickname = get_nickname_from_actor(message_json['actor'])
if not actor_nickname:
print('WARN: _receive_announce no actor_nickname')
return False
actor_domain, _ = get_domain_from_actor(message_json['actor'])
if is_blocked(base_dir, nickname, domain, actor_nickname, actor_domain):
print('Receive announce blocked for actor: ' +
@ -1948,13 +1961,16 @@ def _receive_announce(recent_posts_cache: {},
return False
# also check the actor for the url being announced
announcedActorNickname = get_nickname_from_actor(message_json['object'])
announced_actor_nickname = get_nickname_from_actor(message_json['object'])
if not announced_actor_nickname:
print('WARN: _receive_announce no announced_actor_nickname')
return False
announcedActorDomain, announcedActorPort = \
get_domain_from_actor(message_json['object'])
if is_blocked(base_dir, nickname, domain,
announcedActorNickname, announcedActorDomain):
announced_actor_nickname, announcedActorDomain):
print('Receive announce object blocked for actor: ' +
announcedActorNickname + '@' + announcedActorDomain)
announced_actor_nickname + '@' + announcedActorDomain)
return False
# is this post in the outbox of the person?
@ -2004,7 +2020,8 @@ def _receive_announce(recent_posts_cache: {},
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm)
lists_enabled, timezone, mitm,
bold_reading)
if not announce_html:
print('WARN: Unable to generate html for announce ' +
str(message_json))
@ -2024,7 +2041,7 @@ def _receive_announce(recent_posts_cache: {},
system_language,
domain_full, person_cache,
signing_priv_key_pem,
blocked_cache)
blocked_cache, bold_reading)
if not post_json_object:
print('WARN: unable to download announce: ' + str(message_json))
not_in_onion = True
@ -2788,6 +2805,8 @@ def _inbox_update_calendar(base_dir: str, handle: str,
actor = post_json_object['actor']
actor_nickname = get_nickname_from_actor(actor)
if not actor_nickname:
return
actor_domain, _ = get_domain_from_actor(actor)
handle_nickname = handle.split('@')[0]
handle_domain = handle.split('@')[1]
@ -3103,7 +3122,8 @@ def _receive_question_vote(server, base_dir: str, nickname: str, domain: str,
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int,
cw_lists: {}, lists_enabled: bool) -> None:
cw_lists: {}, lists_enabled: bool,
bold_reading: bool) -> None:
"""Updates the votes on a Question/poll
"""
# if this is a reply to a question then update the votes
@ -3155,7 +3175,8 @@ def _receive_question_vote(server, base_dir: str, nickname: str, domain: str,
show_individual_post_icons,
manually_approve_followers,
False, True, False, cw_lists,
lists_enabled, timezone, mitm)
lists_enabled, timezone, mitm,
bold_reading)
# add id to inbox index
inbox_update_index('inbox', base_dir, handle,
@ -3254,6 +3275,8 @@ def _low_frequency_post_notification(base_dir: str, http_prefix: str,
if not isinstance(attributed_to, str):
return
from_nickname = get_nickname_from_actor(attributed_to)
if not from_nickname:
return
from_domain, from_port = get_domain_from_actor(attributed_to)
from_domain_full = get_full_domain(from_domain, from_port)
if notify_when_person_posts(base_dir, nickname, domain,
@ -3282,6 +3305,8 @@ def _check_for_git_patches(base_dir: str, nickname: str, domain: str,
if not isinstance(attributed_to, str):
return 0
from_nickname = get_nickname_from_actor(attributed_to)
if not from_nickname:
return 0
from_domain, from_port = get_domain_from_actor(attributed_to)
from_domain_full = get_full_domain(from_domain, from_port)
if receive_git_patch(base_dir, nickname, domain,
@ -3325,7 +3350,7 @@ def _inbox_after_initial(server,
cw_lists: {}, lists_enabled: str,
content_license_url: str,
languages_understood: [],
mitm: bool) -> bool:
mitm: bool, bold_reading: bool) -> bool:
""" Anything which needs to be done after initial checks have passed
"""
# if this is a clearnet instance then replace any onion/i2p
@ -3350,6 +3375,8 @@ def _inbox_after_initial(server,
post_is_dm = False
is_group = _group_handle(base_dir, handle)
handle_name = handle.split('@')[0]
if _receive_like(recent_posts_cache,
session, handle, is_group,
base_dir, http_prefix,
@ -3368,7 +3395,8 @@ def _inbox_after_initial(server,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled):
max_like_count, cw_lists, lists_enabled,
bold_reading):
if debug:
print('DEBUG: Like accepted from ' + actor)
return False
@ -3390,7 +3418,8 @@ def _inbox_after_initial(server,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled):
max_like_count, cw_lists, lists_enabled,
bold_reading):
if debug:
print('DEBUG: Undo like accepted from ' + actor)
return False
@ -3413,7 +3442,8 @@ def _inbox_after_initial(server,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled):
max_like_count, cw_lists, lists_enabled,
bold_reading):
if debug:
print('DEBUG: Reaction accepted from ' + actor)
return False
@ -3435,7 +3465,8 @@ def _inbox_after_initial(server,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled):
max_like_count, cw_lists, lists_enabled,
bold_reading):
if debug:
print('DEBUG: Undo reaction accepted from ' + actor)
return False
@ -3457,7 +3488,8 @@ def _inbox_after_initial(server,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled):
max_like_count, cw_lists, lists_enabled,
bold_reading):
if debug:
print('DEBUG: Bookmark accepted from ' + actor)
return False
@ -3479,7 +3511,8 @@ def _inbox_after_initial(server,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count, cw_lists, lists_enabled):
max_like_count, cw_lists, lists_enabled,
bold_reading):
if debug:
print('DEBUG: Undo bookmark accepted from ' + actor)
return False
@ -3505,7 +3538,8 @@ def _inbox_after_initial(server,
max_recent_posts,
allow_deletion,
peertube_instances,
max_like_count, cw_lists, lists_enabled):
max_like_count, cw_lists, lists_enabled,
bold_reading):
if debug:
print('DEBUG: Announce accepted from ' + actor)
@ -3603,7 +3637,8 @@ def _inbox_after_initial(server,
allow_local_network_access,
theme_name, system_language,
max_like_count,
cw_lists, lists_enabled)
cw_lists, lists_enabled,
bold_reading)
is_reply_to_muted_post = False
@ -3646,7 +3681,8 @@ def _inbox_after_initial(server,
twitter_replacement_domain,
allow_local_network_access,
recent_posts_cache, debug, system_language,
domain_full, person_cache, signing_priv_key_pem):
domain_full, person_cache, signing_priv_key_pem,
bold_reading):
# media index will be updated
update_index_list.append('tlmedia')
if is_blog_post(post_json_object):
@ -3706,11 +3742,10 @@ def _inbox_after_initial(server,
print('Saving inbox post as html to cache')
html_cache_start_time = time.time()
handle_name = handle.split('@')[0]
allow_local_net_access = allow_local_network_access
show_pub_date_only = show_published_date_only
timezone = get_account_timezone(base_dir,
handle_name, domain)
timezone = \
get_account_timezone(base_dir, handle_name, domain)
_inbox_store_post_to_html_cache(recent_posts_cache,
max_recent_posts,
translate, base_dir,
@ -3732,7 +3767,8 @@ def _inbox_after_initial(server,
signing_priv_key_pem,
cw_lists,
lists_enabled,
timezone, mitm)
timezone, mitm,
bold_reading)
if debug:
time_diff = \
str(int((time.time() - html_cache_start_time) *
@ -3741,8 +3777,6 @@ def _inbox_after_initial(server,
boxname + ' post as html to cache in ' +
time_diff + ' mS')
handle_name = handle.split('@')[0]
# is this an edit of a previous post?
# in Mastodon "delete and redraft"
# NOTE: this must be done before update_conversation is called
@ -4770,6 +4804,11 @@ def run_inbox_queue(server,
mitm = False
if queue_json.get('mitm'):
mitm = True
bold_reading = False
bold_reading_filename = \
base_dir + '/accounts/' + handle + '/.boldReading'
if os.path.isfile(bold_reading_filename):
bold_reading = True
_inbox_after_initial(server,
recent_posts_cache,
max_recent_posts,
@ -4801,7 +4840,8 @@ def run_inbox_queue(server,
default_reply_interval_hrs,
cw_lists, lists_enabled,
content_license_url,
languages_understood, mitm)
languages_understood, mitm,
bold_reading)
if debug:
pprint(queue_json['post'])
print('Queue: Queue post accepted')

View File

@ -219,6 +219,10 @@ def post_message_to_outbox(session, translate: {},
domain, port,
message_json)
bold_reading = False
if server.bold_reading.get(post_to_nickname):
bold_reading = True
# check that the outgoing post doesn't contain any markup
# which can be used to implement exploits
if has_object_dict(message_json):
@ -430,7 +434,8 @@ def post_message_to_outbox(session, translate: {},
recent_posts_cache, debug,
system_language,
domain_full, person_cache,
signing_priv_key_pem):
signing_priv_key_pem,
bold_reading):
inbox_update_index('tlmedia', base_dir,
post_to_nickname + '@' + domain,
saved_filename, debug)
@ -485,7 +490,8 @@ def post_message_to_outbox(session, translate: {},
manually_approve_followers,
False, True, use_cache_only,
cw_lists, lists_enabled,
timezone, mitm)
timezone, mitm,
bold_reading)
if outbox_announce(recent_posts_cache,
base_dir, message_json, debug):

View File

@ -3683,7 +3683,8 @@ def is_image_media(session, base_dir: str, http_prefix: str,
recent_posts_cache: {}, debug: bool,
system_language: str,
domain_full: str, person_cache: {},
signing_priv_key_pem: str) -> bool:
signing_priv_key_pem: str,
bold_reading: bool) -> bool:
"""Returns true if the given post has attached image media
"""
if post_json_object['type'] == 'Announce':
@ -3699,7 +3700,7 @@ def is_image_media(session, base_dir: str, http_prefix: str,
system_language,
domain_full, person_cache,
signing_priv_key_pem,
blocked_cache)
blocked_cache, bold_reading)
if post_json_announce:
post_json_object = post_json_announce
if post_json_object['type'] != 'Create':
@ -4765,7 +4766,7 @@ def download_announce(session, base_dir: str, http_prefix: str,
system_language: str,
domain_full: str, person_cache: {},
signing_priv_key_pem: str,
blocked_cache: {}) -> {}:
blocked_cache: {}, bold_reading: bool) -> {}:
"""Download the post referenced by an announce
"""
if not post_json_object.get('object'):
@ -4815,6 +4816,9 @@ def download_announce(session, base_dir: str, http_prefix: str,
'Accept': accept_str
}
actor_nickname = get_nickname_from_actor(post_json_object['actor'])
if not actor_nickname:
print('WARN: download_announce no actor_nickname')
return None
actor_domain, actor_port = \
get_domain_from_actor(post_json_object['actor'])
if not actor_domain:
@ -4969,7 +4973,7 @@ def download_announce(session, base_dir: str, http_prefix: str,
content_str = limit_repeated_words(content_str, 6)
# remove text formatting, such as bold/italics
content_str = remove_text_formatting(content_str)
content_str = remove_text_formatting(content_str, bold_reading)
# set the content after santitization
announced_json['content'] = content_str

View File

@ -541,6 +541,8 @@ def html_emoji_reactions(post_json_object: {}, interactive: bool,
emoji_content = item['content']
emoji_actor = item['actor']
emoji_nickname = get_nickname_from_actor(emoji_actor)
if not emoji_nickname:
return ''
emoji_domain, _ = get_domain_from_actor(emoji_actor)
emoji_handle = emoji_nickname + '@' + emoji_domain
if emoji_actor == actor:

View File

@ -158,6 +158,8 @@ def outbox_skills(base_dir: str, nickname: str, message_json: {},
return False
actor_nickname = get_nickname_from_actor(message_json['actor'])
if not actor_nickname:
return False
if actor_nickname != nickname:
return False
domain, _ = get_domain_from_actor(message_json['actor'])

View File

@ -129,6 +129,7 @@ from inbox import json_post_allows_comments
from inbox import valid_inbox
from inbox import valid_inbox_filenames
from categories import guess_hashtag_category
from content import bold_reading_string
from content import safe_web_text
from content import words_similarity
from content import get_price_from_string
@ -3727,10 +3728,10 @@ def _test_recent_posts_cache():
def _test_remove_txt_formatting():
print('test_remove_txt_formatting')
test_str = '<p>Text without formatting</p>'
result_str = remove_text_formatting(test_str)
result_str = remove_text_formatting(test_str, False)
assert result_str == test_str
test_str = '<p>Text <i>with</i> <h3>formatting</h3></p>'
result_str = remove_text_formatting(test_str)
result_str = remove_text_formatting(test_str, False)
assert result_str == '<p>Text with formatting</p>'
@ -6687,6 +6688,72 @@ def _test_published_to_local_timezone() -> None:
assert local_time_str == 'Sat Feb 26, 05:15'
def _test_bold_reading() -> None:
print('bold_reading')
text = "This is a test of emboldening."
text_bold = bold_reading_string(text)
expected = \
"<b>Th</b>is <b>i</b>s a <b>te</b>st <b>o</b>f " + \
"<b>embold</b>ening."
if text_bold != expected:
print(text_bold)
assert text_bold == expected
text = "<p>This is a test of emboldening with paragraph.<p>"
text_bold = bold_reading_string(text)
expected = \
"<p><b>Th</b>is <b>i</b>s a <b>te</b>st <b>o</b>f " + \
"<b>embold</b>ening <b>wi</b>th <b>parag</b>raph.</p>"
if text_bold != expected:
print(text_bold)
assert text_bold == expected
text = \
"<p>This is a test of emboldening</p>" + \
"<p>With more than one paragraph.<p>"
text_bold = bold_reading_string(text)
expected = \
"<p><b>Th</b>is <b>i</b>s a <b>te</b>st <b>o</b>f " + \
"<b>embold</b>ening</p><p><b>Wi</b>th <b>mo</b>re " + \
"<b>th</b>an <b>on</b>e <b>parag</b>raph.</p>"
if text_bold != expected:
print(text_bold)
assert text_bold == expected
text = '<p>This is a test <a class="some class" ' + \
'href="some_url"><label>with markup containing spaces</label></a><p>'
text_bold = bold_reading_string(text)
expected = \
'<p><b>Th</b>is <b>i</b>s a <b>te</b>st ' + \
'<a class="some class" href="some_url"><label>with ' + \
'<b>mar</b>kup <b>conta</b>ining spaces</label></a></p>'
if text_bold != expected:
print(text_bold)
assert text_bold == expected
text = "There&apos;s the quoted text here"
text_bold = bold_reading_string(text)
expected = \
"<b>Ther</b>e's <b>th</b>e <b>quo</b>ted <b>te</b>xt <b>he</b>re"
if text_bold != expected:
print(text_bold)
assert text_bold == expected
text = '<p><span class=\"h-card\"><a ' + \
'href=\"https://something.social/@someone\" ' + \
'class=\"u-url mention\">@<span>Someone or other' + \
'</span></a></span> some text</p>'
text_bold = bold_reading_string(text)
expected = \
'<p><span class="h-card">' + \
'<a href="https://something.social/@someone" ' + \
'class="u-url mention">@<span>Someone <b>o</b>r other' + \
'</span></a></span> <b>so</b>me <b>te</b>xt</p>'
if text_bold != expected:
print(text_bold)
assert text_bold == expected
def run_all_tests():
base_dir = os.getcwd()
print('Running tests...')
@ -6703,6 +6770,7 @@ def run_all_tests():
'message_json', 'liked_post_json'])
_test_checkbox_names()
_test_functions()
_test_bold_reading()
_test_published_to_local_timezone()
_test_safe_webtext()
_test_get_link_from_rss_item()

View File

@ -518,5 +518,6 @@
"Register": "يسجل",
"Web Bots Allowed": "مسموح روبوتات الويب",
"Known Search Bots": "روبوتات بحث الويب المعروفة",
"mitm": "يمكن قراءة الرسالة أو تعديلها من قبل طرف ثالث"
"mitm": "يمكن قراءة الرسالة أو تعديلها من قبل طرف ثالث",
"Bold reading": "قراءة جريئة"
}

View File

@ -518,5 +518,6 @@
"Register": "Registra't",
"Web Bots Allowed": "Bots web permesos",
"Known Search Bots": "Bots de cerca web coneguts",
"mitm": "El missatge podria haver estat llegit o modificat per un tercer"
"mitm": "El missatge podria haver estat llegit o modificat per un tercer",
"Bold reading": "Lectura atrevida"
}

View File

@ -518,5 +518,6 @@
"Register": "Cofrestrwch",
"Web Bots Allowed": "Web Bots a Ganiateir",
"Known Search Bots": "Bots Chwilio Gwe Hysbys",
"mitm": "Gallai'r neges fod wedi cael ei darllen neu ei haddasu gan drydydd parti"
"mitm": "Gallai'r neges fod wedi cael ei darllen neu ei haddasu gan drydydd parti",
"Bold reading": "Darllen beiddgar"
}

View File

@ -518,5 +518,6 @@
"Register": "Registrieren",
"Web Bots Allowed": "Webbots erlaubt",
"Known Search Bots": "Bekannte Bots für die Websuche",
"mitm": "Die Nachricht könnte von einem Dritten gelesen oder geändert worden sein"
"mitm": "Die Nachricht könnte von einem Dritten gelesen oder geändert worden sein",
"Bold reading": "Mutige Lektüre"
}

View File

@ -518,5 +518,6 @@
"Register": "Register",
"Web Bots Allowed": "Web Search Bots Allowed",
"Known Search Bots": "Known Web Search Bots",
"mitm": "Message could have been read or modified by a third party"
"mitm": "Message could have been read or modified by a third party",
"Bold reading": "Bold reading"
}

View File

@ -518,5 +518,6 @@
"Register": "Registrarse",
"Web Bots Allowed": "Bots web permitidos",
"Known Search Bots": "Bots de búsqueda web conocidos",
"mitm": "El mensaje podría haber sido leído o modificado por un tercero"
"mitm": "El mensaje podría haber sido leído o modificado por un tercero",
"Bold reading": "Lectura en negrita"
}

View File

@ -518,5 +518,6 @@
"Register": "S'inscrire",
"Web Bots Allowed": "Robots Web autorisés",
"Known Search Bots": "Robots de recherche Web connus",
"mitm": "Le message a pu être lu ou modifié par un tiers"
"mitm": "Le message a pu être lu ou modifié par un tiers",
"Bold reading": "Lecture audacieuse"
}

View File

@ -518,5 +518,6 @@
"Register": "Clár",
"Web Bots Allowed": "Róbónna Gréasáin Ceadaithe",
"Known Search Bots": "Róbónna Cuardach Gréasáin Aitheanta",
"mitm": "D'fhéadfadh tríú páirtí an teachtaireacht a léamh nó a mhodhnú"
"mitm": "D'fhéadfadh tríú páirtí an teachtaireacht a léamh nó a mhodhnú",
"Bold reading": "Léamh trom"
}

View File

@ -518,5 +518,6 @@
"Register": "रजिस्टर करें",
"Web Bots Allowed": "वेब बॉट्स की अनुमति है",
"Known Search Bots": "ज्ञात वेब खोज बॉट्स",
"mitm": "संदेश किसी तीसरे पक्ष द्वारा पढ़ा या संशोधित किया जा सकता था"
"mitm": "संदेश किसी तीसरे पक्ष द्वारा पढ़ा या संशोधित किया जा सकता था",
"Bold reading": "बोल्ड रीडिंग"
}

View File

@ -518,5 +518,6 @@
"Register": "Registrati",
"Web Bots Allowed": "Web bot consentiti",
"Known Search Bots": "Bot di ricerca Web noti",
"mitm": "Il messaggio potrebbe essere stato letto o modificato da terzi"
"mitm": "Il messaggio potrebbe essere stato letto o modificato da terzi",
"Bold reading": "Lettura audace"
}

View File

@ -518,5 +518,6 @@
"Register": "登録",
"Web Bots Allowed": "許可されたWebボット",
"Known Search Bots": "既知のWeb検索ボット",
"mitm": "メッセージが第三者によって読み取られたり変更されたりした可能性があります"
"mitm": "メッセージが第三者によって読み取られたり変更されたりした可能性があります",
"Bold reading": "大胆な読書"
}

View File

@ -518,5 +518,6 @@
"Register": "등록",
"Web Bots Allowed": "웹 봇 허용",
"Known Search Bots": "알려진 웹 검색 봇",
"mitm": "제3자가 메시지를 읽거나 수정했을 수 있습니다."
"mitm": "제3자가 메시지를 읽거나 수정했을 수 있습니다.",
"Bold reading": "굵은 글씨"
}

View File

@ -518,5 +518,6 @@
"Register": "Fêhrist",
"Web Bots Allowed": "Web Bots Destûrdar in",
"Known Search Bots": "Botên Lêgerîna Webê yên naskirî",
"mitm": "Peyam dikaribû ji hêla aliyek sêyemîn ve were xwendin an guhertin"
"mitm": "Peyam dikaribû ji hêla aliyek sêyemîn ve were xwendin an guhertin",
"Bold reading": "Xwendina qelew"
}

View File

@ -514,5 +514,6 @@
"Register": "Register",
"Web Bots Allowed": "Web Search Bots Allowed",
"Known Search Bots": "Known Web Search Bots",
"mitm": "Message could have been read or modified by a third party"
"mitm": "Message could have been read or modified by a third party",
"Bold reading": "Bold reading"
}

View File

@ -518,5 +518,6 @@
"Register": "Zarejestrować",
"Web Bots Allowed": "Dozwolone boty internetowe",
"Known Search Bots": "Znane boty wyszukiwania w sieci",
"mitm": "Wiadomość mogła zostać przeczytana lub zmodyfikowana przez osobę trzecią"
"mitm": "Wiadomość mogła zostać przeczytana lub zmodyfikowana przez osobę trzecią",
"Bold reading": "Odważne czytanie"
}

View File

@ -518,5 +518,6 @@
"Register": "Registro",
"Web Bots Allowed": "Webbots permitidos",
"Known Search Bots": "Bots de pesquisa na Web conhecidos",
"mitm": "A mensagem pode ter sido lida ou modificada por terceiros"
"mitm": "A mensagem pode ter sido lida ou modificada por terceiros",
"Bold reading": "Leitura em negrito"
}

View File

@ -518,5 +518,6 @@
"Register": "регистр",
"Web Bots Allowed": "Веб-боты разрешены",
"Known Search Bots": "Известные боты веб-поиска",
"mitm": "Сообщение могло быть прочитано или изменено третьим лицом"
"mitm": "Сообщение могло быть прочитано или изменено третьим лицом",
"Bold reading": "Смелое чтение"
}

View File

@ -518,5 +518,6 @@
"Register": "Sajili",
"Web Bots Allowed": "Mtandao wa Boti Unaruhusiwa",
"Known Search Bots": "Vijibu vya Utafutaji wa Wavuti vinavyojulikana",
"mitm": "Ujumbe ungeweza kusomwa au kurekebishwa na mtu mwingine"
"mitm": "Ujumbe ungeweza kusomwa au kurekebishwa na mtu mwingine",
"Bold reading": "Kusoma kwa ujasiri"
}

View File

@ -518,5 +518,6 @@
"Register": "Реєстрація",
"Web Bots Allowed": "Веб-боти дозволені",
"Known Search Bots": "Відомі пошукові роботи в Інтернеті",
"mitm": "Повідомлення могло бути прочитане або змінене третьою стороною"
"mitm": "Повідомлення могло бути прочитане або змінене третьою стороною",
"Bold reading": "Сміливе читання"
}

View File

@ -518,5 +518,6 @@
"Register": "登记",
"Web Bots Allowed": "允许网络机器人",
"Known Search Bots": "已知的网络搜索机器人",
"mitm": "消息可能已被第三方阅读或修改"
"mitm": "消息可能已被第三方阅读或修改",
"Bold reading": "大胆阅读"
}

View File

@ -2343,6 +2343,8 @@ def undo_likes_collection_entry(recent_posts_cache: {},
# remove any cached version of this post so that the
# like icon is changed
nickname = get_nickname_from_actor(actor)
if not nickname:
return
cached_post_filename = \
get_cached_post_filename(base_dir, nickname,
domain, post_json_object)
@ -2409,6 +2411,8 @@ def undo_reaction_collection_entry(recent_posts_cache: {},
# remove any cached version of this post so that the
# like icon is changed
nickname = get_nickname_from_actor(actor)
if not nickname:
return
cached_post_filename = \
get_cached_post_filename(base_dir, nickname,
domain, post_json_object)
@ -2476,6 +2480,8 @@ def undo_announce_collection_entry(recent_posts_cache: {},
# remove any cached version of this announce so that the announce
# icon is changed
nickname = get_nickname_from_actor(actor)
if not nickname:
return
cached_post_filename = \
get_cached_post_filename(base_dir, nickname, domain,
post_json_object)
@ -3474,6 +3480,24 @@ def load_account_timezones(base_dir: str) -> {}:
return account_timezone
def load_bold_reading(base_dir: str) -> {}:
"""Returns a dictionary containing the bold reading status for each account
"""
bold_reading = {}
for subdir, dirs, files in os.walk(base_dir + '/accounts'):
for acct in dirs:
if '@' not in acct:
continue
if acct.startswith('inbox@') or acct.startswith('Actor@'):
continue
bold_reading_filename = \
base_dir + '/accounts/' + acct + '/.boldReading'
if os.path.isfile(bold_reading_filename):
nickname = acct.split('@')[0]
bold_reading[nickname] = True
return bold_reading
def get_account_timezone(base_dir: str, nickname: str, domain: str) -> str:
"""Returns the timezone for the given account
"""

View File

@ -42,6 +42,8 @@ def html_calendar_delete_confirm(css_cache: {}, translate: {}, base_dir: str,
"""Shows a screen asking to confirm the deletion of a calendar event
"""
nickname = get_nickname_from_actor(path)
if not nickname:
return None
actor = local_actor_url(http_prefix, nickname, domain_full)
domain, _ = get_domain_from_actor(actor)
message_id = actor + '/statuses/' + post_id
@ -304,6 +306,8 @@ def html_calendar(person_cache: {}, css_cache: {}, translate: {},
month_number = curr_date.month
nickname = get_nickname_from_actor(actor)
if not nickname:
return ''
set_custom_background(base_dir, 'calendar-background',
'calendar-background')

View File

@ -24,7 +24,7 @@ from webapp_utils import html_footer
from webapp_post import individual_post_as_html
def html_confirm_delete(css_cache: {},
def html_confirm_delete(server, css_cache: {},
recent_posts_cache: {}, max_recent_posts: int,
translate, page_number: int,
session, base_dir: str, message_id: str,
@ -45,6 +45,8 @@ def html_confirm_delete(css_cache: {},
return None
actor = message_id.split('/statuses/')[0]
nickname = get_nickname_from_actor(actor)
if not nickname:
return None
domain, port = get_domain_from_actor(actor)
domain_full = get_full_domain(domain, port)
@ -69,6 +71,9 @@ def html_confirm_delete(css_cache: {},
mitm = False
if os.path.isfile(post_filename.replace('.json', '') + '.mitm'):
mitm = True
bold_reading = False
if server.bold_reading.get(nickname):
bold_reading = True
delete_post_str += \
individual_post_as_html(signing_priv_key_pem,
True, recent_posts_cache, max_recent_posts,
@ -84,7 +89,8 @@ def html_confirm_delete(css_cache: {},
peertube_instances, allow_local_network_access,
theme_name, system_language, max_like_count,
False, False, False, False, False, False,
cw_lists, lists_enabled, timezone, mitm)
cw_lists, lists_enabled, timezone, mitm,
bold_reading)
delete_post_str += '<center>'
delete_post_str += \
' <p class="followText">' + \
@ -119,6 +125,8 @@ def html_confirm_remove_shared_item(css_cache: {}, translate: {},
"""Shows a screen asking to confirm the removal of a shared item
"""
nickname = get_nickname_from_actor(actor)
if not nickname:
return None
domain, port = get_domain_from_actor(actor)
domain_full = get_full_domain(domain, port)
shares_file = \
@ -207,10 +215,11 @@ def html_confirm_follow(css_cache: {}, translate: {}, base_dir: str,
follow_str += ' <a href="' + follow_actor + '">\n'
follow_str += \
' <img loading="lazy" src="' + follow_profile_url + '"/></a>\n'
follow_str += \
' <p class="followText">' + translate['Follow'] + ' ' + \
get_nickname_from_actor(follow_actor) + \
'@' + follow_domain + ' ?</p>\n'
follow_actor_nick = get_nickname_from_actor(follow_actor)
if follow_actor_nick:
follow_str += \
' <p class="followText">' + translate['Follow'] + ' ' + \
follow_actor_nick + '@' + follow_domain + ' ?</p>\n'
follow_str += ' <form method="POST" action="' + \
origin_path_str + '/followconfirm">\n'
follow_str += ' <input type="hidden" name="actor" value="' + \
@ -255,10 +264,11 @@ def html_confirm_unfollow(css_cache: {}, translate: {}, base_dir: str,
follow_str += ' <a href="' + follow_actor + '">\n'
follow_str += \
' <img loading="lazy" src="' + follow_profile_url + '"/></a>\n'
follow_str += \
' <p class="followText">' + translate['Stop following'] + \
' ' + get_nickname_from_actor(follow_actor) + \
'@' + follow_domain + ' ?</p>\n'
follow_actor_nick = get_nickname_from_actor(follow_actor)
if follow_actor_nick:
follow_str += \
' <p class="followText">' + translate['Stop following'] + \
' ' + follow_actor_nick + '@' + follow_domain + ' ?</p>\n'
follow_str += ' <form method="POST" action="' + \
origin_path_str + '/unfollowconfirm">\n'
follow_str += ' <input type="hidden" name="actor" value="' + \
@ -300,9 +310,11 @@ def html_confirm_unblock(css_cache: {}, translate: {}, base_dir: str,
block_str += ' <a href="' + block_actor + '">\n'
block_str += \
' <img loading="lazy" src="' + block_profile_url + '"/></a>\n'
block_str += \
' <p class="blockText">' + translate['Stop blocking'] + ' ' + \
get_nickname_from_actor(block_actor) + '@' + block_domain + ' ?</p>\n'
block_actor_nick = get_nickname_from_actor(block_actor)
if block_actor_nick:
block_str += \
' <p class="blockText">' + translate['Stop blocking'] + ' ' + \
block_actor_nick + '@' + block_domain + ' ?</p>\n'
block_str += ' <form method="POST" action="' + \
origin_path_str + '/unblockconfirm">\n'
block_str += ' <input type="hidden" name="actor" value="' + \

View File

@ -212,7 +212,7 @@ def html_new_post(css_cache: {}, media_instance: bool, translate: {},
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
boxName: str,
reply_is_chat: bool) -> str:
reply_is_chat: bool, bold_reading: bool) -> str:
"""New post screen
"""
reply_str = ''
@ -286,7 +286,8 @@ def html_new_post(css_cache: {}, media_instance: bool, translate: {},
False, False, False,
False, False, False,
cw_lists, lists_enabled,
timezone, False)
timezone, False,
bold_reading)
reply_str = '<input type="hidden" ' + \
'name="replyTo" value="' + inReplyTo + '">\n'

View File

@ -37,7 +37,8 @@ def _html_front_screen_posts(recent_posts_cache: {}, max_recent_posts: int,
theme_name: str, system_language: str,
max_like_count: int,
signing_priv_key_pem: str, cw_lists: {},
lists_enabled: str) -> str:
lists_enabled: str,
bold_reading: bool) -> str:
"""Shows posts on the front screen of a news instance
These should only be public blog posts from the features timeline
which is the blog timeline of the news actor
@ -87,7 +88,8 @@ def _html_front_screen_posts(recent_posts_cache: {}, max_recent_posts: int,
False, False, False,
True, False, False,
cw_lists, lists_enabled,
timezone, False)
timezone, False,
bold_reading)
if post_str:
profile_str += post_str + separator_str
ctr += 1
@ -121,6 +123,7 @@ def html_front_screen(signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str) -> str:
"""Show the news instance front screen
"""
bold_reading = False
nickname = profile_json['preferredUsername']
if not nickname:
return ""
@ -191,7 +194,8 @@ def html_front_screen(signing_priv_key_pem: str,
theme, system_language,
max_like_count,
signing_priv_key_pem,
cw_lists, lists_enabled) + license_str
cw_lists, lists_enabled,
bold_reading) + license_str
# Footer which is only used for system accounts
profile_footer_str = ' </td>\n'

View File

@ -200,6 +200,8 @@ def html_search_hashtag_category(css_cache: {}, translate: {},
actor = path.split('/category/')[0]
category_str = path.split('/category/')[1].strip()
search_nickname = get_nickname_from_actor(actor)
if not search_nickname:
return ''
set_custom_background(base_dir, 'search-background', 'follow-background')

View File

@ -41,6 +41,7 @@ def html_likers_of_post(base_dir: str, nickname: str,
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
boxName: str, default_timeline: str,
bold_reading: bool,
dict_name: str = 'likes') -> str:
"""Returns html for a screen showing who liked a post
"""
@ -105,7 +106,7 @@ def html_likers_of_post(base_dir: str, nickname: str,
False, False, False,
False, False, False,
cw_lists, lists_enabled,
timezone, mitm)
timezone, mitm, bold_reading)
# show likers beneath the post
obj = post_json_object
@ -142,6 +143,8 @@ def html_likers_of_post(base_dir: str, nickname: str,
liker_name, False)
else:
liker_name = get_nickname_from_actor(liker_actor)
if not liker_name:
liker_name = 'unknown'
if likers_list:
likers_list += ' '
liker_avatar_url = \

View File

@ -55,7 +55,7 @@ def html_moderation(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the moderation feed as html
This is what you see when selecting the "mod" timeline
"""
@ -81,7 +81,7 @@ def html_moderation(css_cache: {}, default_timeline: str,
text_mode_banner, access_keys, system_language,
max_like_count, shared_items_federated_domains,
signing_priv_key_pem, cw_lists, lists_enabled,
timezone)
timezone, bold_reading)
def html_account_info(css_cache: {}, translate: {},
@ -107,6 +107,8 @@ def html_account_info(css_cache: {}, translate: {},
html_header_with_external_style(css_filename, instance_title, None)
search_nickname = get_nickname_from_actor(search_handle)
if not search_nickname:
return ''
search_domain, search_port = get_domain_from_actor(search_handle)
search_handle = search_nickname + '@' + search_domain
@ -146,6 +148,8 @@ def html_account_info(css_cache: {}, translate: {},
blocked_followers = []
for follower_actor in followers_list:
follower_nickname = get_nickname_from_actor(follower_actor)
if not follower_nickname:
return ''
follower_domain, follower_port = get_domain_from_actor(follower_actor)
follower_domain_full = get_full_domain(follower_domain, follower_port)
if is_blocked(base_dir, nickname, domain,
@ -160,6 +164,8 @@ def html_account_info(css_cache: {}, translate: {},
blocked_following = []
for following_actor in following_list:
following_nickname = get_nickname_from_actor(following_actor)
if not following_nickname:
return ''
following_domain, following_port = \
get_domain_from_actor(following_actor)
following_domain_full = \
@ -224,6 +230,8 @@ def html_account_info(css_cache: {}, translate: {},
':</p>\n'
for actor in blocked_following:
following_nickname = get_nickname_from_actor(actor)
if not following_nickname:
return ''
following_domain, following_port = get_domain_from_actor(actor)
following_domain_full = \
get_full_domain(following_domain, following_port)
@ -243,6 +251,8 @@ def html_account_info(css_cache: {}, translate: {},
':</p>\n'
for actor in blocked_followers:
follower_nickname = get_nickname_from_actor(actor)
if not follower_nickname:
return ''
follower_domain, follower_port = get_domain_from_actor(actor)
follower_domain_full = \
get_full_domain(follower_domain, follower_port)

View File

@ -66,6 +66,8 @@ def html_person_options(default_timeline: str,
"""Show options for a person: view/follow/block/report
"""
options_domain, options_port = get_domain_from_actor(options_actor)
if not options_domain:
return None
options_domain_full = get_full_domain(options_domain, options_port)
if os.path.isfile(base_dir + '/accounts/options-background-custom.jpg'):
@ -97,6 +99,8 @@ def html_person_options(default_timeline: str,
dormant_months)
options_nickname = get_nickname_from_actor(options_actor)
if not options_nickname:
return None
options_domain_full = get_full_domain(options_domain, options_port)
follows_you = \
is_follower_of_person(base_dir,
@ -140,7 +144,10 @@ def html_person_options(default_timeline: str,
options_str += ' <a href="' + options_actor + '">\n'
options_str += ' <img loading="lazy" src="' + options_profile_url + \
'" alt="" ' + get_broken_link_substitute() + '/></a>\n'
handle = get_nickname_from_actor(options_actor) + '@' + options_domain
handle_nick = get_nickname_from_actor(options_actor)
if not handle_nick:
return None
handle = handle_nick + '@' + options_domain
handle_shown = handle
if locked_account:
handle_shown += '🔒'
@ -151,7 +158,7 @@ def html_person_options(default_timeline: str,
options_str += \
' <p class="optionsText">' + translate['Options for'] + \
' @' + handle_shown + '</p>\n'
if follows_you:
if follows_you and authorized:
options_str += \
' <p class="optionsText">' + translate['Follows you'] + '</p>\n'
if moved_to:

View File

@ -58,6 +58,7 @@ from utils import get_domain_from_actor
from utils import acct_dir
from utils import local_actor_url
from utils import is_unlisted_post
from content import bold_reading_string
from content import limit_repeated_words
from content import replace_emoji_from_tags
from content import html_replace_quote_marks
@ -104,11 +105,12 @@ def _html_post_metadata_open_graph(domain: str, post_json_object: {}) -> str:
if isinstance(obj_json['attributedTo'], str):
attrib = obj_json['attributedTo']
actor_nick = get_nickname_from_actor(attrib)
actor_domain, _ = get_domain_from_actor(attrib)
actor_handle = actor_nick + '@' + actor_domain
metadata += \
" <meta content=\"@" + actor_handle + \
"\" property=\"og:title\" />\n"
if actor_nick:
actor_domain, _ = get_domain_from_actor(attrib)
actor_handle = actor_nick + '@' + actor_domain
metadata += \
" <meta content=\"@" + actor_handle + \
"\" property=\"og:title\" />\n"
if obj_json.get('url'):
metadata += \
" <meta content=\"" + obj_json['url'] + \
@ -410,6 +412,8 @@ def _get_reply_icon_html(base_dir: str, nickname: str, domain: str,
# check that the alternative replyTo url is not blocked
block_nickname = \
get_nickname_from_actor(post_json_object['object']['replyTo'])
if not block_nickname:
return reply_str
block_domain, _ = \
get_domain_from_actor(post_json_object['object']['replyTo'])
if not is_blocked(base_dir, nickname, domain,
@ -1436,7 +1440,7 @@ def individual_post_as_html(signing_priv_key_pem: str,
cw_lists: {},
lists_enabled: str,
timezone: str,
mitm: bool) -> str:
mitm: bool, bold_reading: bool) -> str:
""" Shows a single post as html
"""
if not post_json_object:
@ -1516,6 +1520,8 @@ def individual_post_as_html(signing_priv_key_pem: str,
if domain_full not in post_actor:
# lookup the correct webfinger for the post_actor
post_actor_nickname = get_nickname_from_actor(post_actor)
if not post_actor_nickname:
return ''
post_actor_domain, post_actor_port = get_domain_from_actor(post_actor)
post_actor_domain_full = \
get_full_domain(post_actor_domain, post_actor_port)
@ -1598,7 +1604,7 @@ def individual_post_as_html(signing_priv_key_pem: str,
system_language,
domain_full, person_cache,
signing_priv_key_pem,
blocked_cache)
blocked_cache, bold_reading)
if not post_json_announce:
# if the announce could not be downloaded then mark it as rejected
announced_post_id = remove_id_ending(post_json_object['id'])
@ -1884,7 +1890,9 @@ def individual_post_as_html(signing_priv_key_pem: str,
published_link = message_id
# blog posts should have no /statuses/ in their link
post_is_blog = False
if is_blog_post(post_json_object):
post_is_blog = True
# is this a post to the local domain?
if '://' + domain in message_id:
published_link = message_id.replace('/statuses/', '/')
@ -1947,7 +1955,9 @@ def individual_post_as_html(signing_priv_key_pem: str,
system_language: ''
}
displaying_ciphertext = False
if post_json_object['object'].get('cipherText'):
displaying_ciphertext = True
post_json_object['object']['content'] = \
e2e_edecrypt_message_from_device(post_json_object['object'])
post_json_object['object']['contentMap'][system_language] = \
@ -1979,9 +1989,16 @@ def individual_post_as_html(signing_priv_key_pem: str,
if not is_pgp_encrypted(content_str):
if not is_patch:
# Add bold text
if bold_reading and \
not displaying_ciphertext and \
not post_is_blog:
content_str = bold_reading_string(content_str)
object_content = \
remove_long_words(content_str, 40, [])
object_content = remove_text_formatting(object_content)
object_content = \
remove_text_formatting(object_content, bold_reading)
object_content = limit_repeated_words(object_content, 6)
object_content = \
switch_words(base_dir, nickname, domain, object_content)
@ -2114,7 +2131,8 @@ def html_individual_post(css_cache: {},
theme_name: str, system_language: str,
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str, mitm: bool) -> str:
timezone: str, mitm: bool,
bold_reading: bool) -> str:
"""Show an individual post as html
"""
original_post_json = post_json_object
@ -2132,6 +2150,8 @@ def html_individual_post(css_cache: {},
if by_str:
by_str_nickname = get_nickname_from_actor(by_str)
if not by_str_nickname:
return ''
by_str_domain, by_str_port = get_domain_from_actor(by_str)
by_str_domain = get_full_domain(by_str_domain, by_str_port)
by_str_handle = by_str_nickname + '@' + by_str_domain
@ -2180,7 +2200,8 @@ def html_individual_post(css_cache: {},
allow_local_network_access, theme_name,
system_language, max_like_count,
False, authorized, False, False, False, False,
cw_lists, lists_enabled, timezone, mitm)
cw_lists, lists_enabled, timezone, mitm,
bold_reading)
message_id = remove_id_ending(post_json_object['id'])
# show the previous posts
@ -2220,7 +2241,8 @@ def html_individual_post(css_cache: {},
False, authorized,
False, False, False, False,
cw_lists, lists_enabled,
timezone, mitm) + post_str
timezone, mitm,
bold_reading) + post_str
# show the following posts
post_filename = locate_post(base_dir, nickname, domain, message_id)
@ -2258,7 +2280,8 @@ def html_individual_post(css_cache: {},
False, authorized,
False, False, False, False,
cw_lists, lists_enabled,
timezone, False)
timezone, False,
bold_reading)
css_filename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
css_filename = base_dir + '/epicyon.css'
@ -2286,7 +2309,7 @@ def html_post_replies(css_cache: {},
max_like_count: int,
signing_priv_key_pem: str, cw_lists: {},
lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the replies to an individual post as html
"""
replies_str = ''
@ -2312,7 +2335,8 @@ def html_post_replies(css_cache: {},
False, False, False, False,
False, False,
cw_lists, lists_enabled,
timezone, False)
timezone, False,
bold_reading)
css_filename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
@ -2342,7 +2366,7 @@ def html_emoji_reaction_picker(css_cache: {},
max_like_count: int, signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
box_name: str, page_number: int,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Returns the emoji picker screen
"""
reacted_to_post_str = \
@ -2365,7 +2389,8 @@ def html_emoji_reaction_picker(css_cache: {},
theme_name, system_language,
max_like_count,
False, False, False, False, False, False,
cw_lists, lists_enabled, timezone, False)
cw_lists, lists_enabled, timezone, False,
bold_reading)
reactions_filename = base_dir + '/emoji/reactions.json'
if not os.path.isfile(reactions_filename):

View File

@ -75,6 +75,7 @@ from blog import get_blog_address
from webapp_post import individual_post_as_html
from webapp_timeline import html_individual_share
from blocking import get_cw_list_variable
from content import bold_reading_string
THEME_FORMATS = '.zip, .gz'
@ -146,7 +147,8 @@ def html_profile_after_search(css_cache: {},
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str,
onion_domain: str, i2p_domain: str) -> str:
onion_domain: str, i2p_domain: str,
bold_reading: bool) -> str:
"""Show a profile page after a search for a fediverse address
"""
http = False
@ -368,7 +370,8 @@ def html_profile_after_search(css_cache: {},
False, False, False,
False, False, False,
cw_lists, lists_enabled,
timezone, False)
timezone, False,
bold_reading)
i += 1
if i >= 8:
break
@ -591,7 +594,7 @@ def html_profile(signing_priv_key_pem: str,
max_items_per_page: int,
cw_lists: {}, lists_enabled: str,
content_license_url: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the profile page as html
"""
nickname = profile_json['preferredUsername']
@ -1002,7 +1005,7 @@ def html_profile(signing_priv_key_pem: str,
max_like_count,
signing_priv_key_pem,
cw_lists, lists_enabled,
timezone) + license_str
timezone, bold_reading) + license_str
if not is_group:
if selected == 'following':
profile_str += \
@ -1076,7 +1079,7 @@ def _html_profile_posts(recent_posts_cache: {}, max_recent_posts: int,
max_like_count: int,
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Shows posts on the profile screen
These should only be public posts
"""
@ -1125,7 +1128,8 @@ def _html_profile_posts(recent_posts_cache: {}, max_recent_posts: int,
False, False, False,
True, False, False,
cw_lists, lists_enabled,
timezone, False)
timezone, False,
bold_reading)
if post_str:
profile_str += post_str + separator_str
ctr += 1
@ -1993,7 +1997,7 @@ def _html_edit_profile_options(is_admin: bool,
notify_likes: str, notify_reactions: str,
hide_like_button: str,
hide_reaction_button: str,
translate: {}) -> str:
translate: {}, bold_reading: bool) -> str:
"""option checkboxes section of edit profile screen
"""
edit_profile_form = ' <div class="container">\n'
@ -2025,6 +2029,9 @@ def _html_edit_profile_options(is_admin: bool,
edit_profile_form += \
edit_check_box(translate["Don't show the Reaction button"],
'hideReactionButton', hide_reaction_button)
bold_str = bold_reading_string(translate['Bold reading'])
edit_profile_form += \
edit_check_box(bold_str, 'boldReading', bold_reading)
edit_profile_form += ' </div>\n'
return edit_profile_form
@ -2158,7 +2165,8 @@ def _html_edit_profile_top_banner(base_dir: str,
return edit_profile_form
def html_edit_profile(css_cache: {}, translate: {}, base_dir: str, path: str,
def html_edit_profile(server, css_cache: {}, translate: {},
base_dir: str, path: str,
domain: str, port: int, http_prefix: str,
default_timeline: str, theme: str,
peertube_instances: [],
@ -2177,6 +2185,10 @@ def html_edit_profile(css_cache: {}, translate: {}, base_dir: str, path: str,
return ''
domain_full = get_full_domain(domain, port)
bold_reading = False
if server.bold_reading.get(nickname):
bold_reading = True
actor_filename = acct_dir(base_dir, nickname, domain) + '.json'
if not os.path.isfile(actor_filename):
return ''
@ -2346,7 +2358,7 @@ def html_edit_profile(css_cache: {}, translate: {}, base_dir: str, path: str,
remove_twitter,
notify_likes, notify_reactions,
hide_like_button, hide_reaction_button,
translate)
translate, bold_reading)
# Contact information
edit_profile_form += \
@ -2429,6 +2441,8 @@ def _individual_follow_as_html(signing_priv_key_pem: str,
"""An individual follow entry on the profile screen
"""
follow_url_nickname = get_nickname_from_actor(followUrl)
if not follow_url_nickname:
return ''
follow_url_domain, follow_url_port = get_domain_from_actor(followUrl)
follow_url_domain_full = \
get_full_domain(follow_url_domain, follow_url_port)

View File

@ -380,6 +380,8 @@ def html_search(css_cache: {}, translate: {},
"""
actor = path.replace('/search', '')
search_nickname = get_nickname_from_actor(actor)
if not search_nickname:
return ''
set_custom_background(base_dir, 'search-background', 'follow-background')
@ -611,7 +613,7 @@ def html_history_search(css_cache: {}, translate: {}, base_dir: str,
signing_priv_key_pem: str,
cw_lists: {},
lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show a page containing search results for your post history
"""
if historysearch.startswith("'"):
@ -699,7 +701,7 @@ def html_history_search(css_cache: {}, translate: {}, base_dir: str,
show_individual_post_icons,
False, False, False, False,
cw_lists, lists_enabled,
timezone, False)
timezone, False, bold_reading)
if post_str:
history_search_form += separator_str + post_str
index += 1
@ -725,7 +727,7 @@ def html_hashtag_search(css_cache: {},
max_like_count: int,
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show a page containing search results for a hashtag
or after selecting a hashtag from the swarm
"""
@ -885,7 +887,8 @@ def html_hashtag_search(css_cache: {},
manually_approves_followers,
show_public_only,
store_to_sache, False, cw_lists,
lists_enabled, timezone, False)
lists_enabled, timezone, False,
bold_reading)
if post_str:
hashtag_search_form += separator_str + post_str
index += 1

View File

@ -455,7 +455,7 @@ def html_timeline(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the timeline as html
"""
enable_timing_log = False
@ -935,7 +935,8 @@ def html_timeline(css_cache: {}, default_timeline: str,
manually_approve_followers,
False, True, use_cache_only,
cw_lists, lists_enabled,
timezone, mitm)
timezone, mitm,
bold_reading)
_log_timeline_timing(enable_timing_log,
timeline_start_time, box_name, '12')
@ -1164,7 +1165,7 @@ def html_shares(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the shares timeline as html
"""
manually_approve_followers = \
@ -1194,7 +1195,8 @@ def html_shares(css_cache: {}, default_timeline: str,
access_keys, system_language, max_like_count,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone,
bold_reading)
def html_wanted(css_cache: {}, default_timeline: str,
@ -1223,7 +1225,7 @@ def html_wanted(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the wanted timeline as html
"""
manually_approve_followers = \
@ -1253,7 +1255,8 @@ def html_wanted(css_cache: {}, default_timeline: str,
access_keys, system_language, max_like_count,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone,
bold_reading)
def html_inbox(css_cache: {}, default_timeline: str,
@ -1283,7 +1286,7 @@ def html_inbox(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the inbox as html
"""
manually_approve_followers = \
@ -1313,7 +1316,8 @@ def html_inbox(css_cache: {}, default_timeline: str,
access_keys, system_language, max_like_count,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone,
bold_reading)
def html_bookmarks(css_cache: {}, default_timeline: str,
@ -1343,7 +1347,7 @@ def html_bookmarks(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the bookmarks as html
"""
manually_approve_followers = \
@ -1372,7 +1376,8 @@ def html_bookmarks(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone,
bold_reading)
def html_inbox_dms(css_cache: {}, default_timeline: str,
@ -1402,7 +1407,7 @@ def html_inbox_dms(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the DM timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1427,7 +1432,8 @@ def html_inbox_dms(css_cache: {}, default_timeline: str,
access_keys, system_language, max_like_count,
shared_items_federated_domains,
signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone,
bold_reading)
def html_inbox_replies(css_cache: {}, default_timeline: str,
@ -1457,7 +1463,7 @@ def html_inbox_replies(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the replies timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1481,7 +1487,7 @@ def html_inbox_replies(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone, bold_reading)
def html_inbox_media(css_cache: {}, default_timeline: str,
@ -1511,7 +1517,7 @@ def html_inbox_media(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the media timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1535,7 +1541,7 @@ def html_inbox_media(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone, bold_reading)
def html_inbox_blogs(css_cache: {}, default_timeline: str,
@ -1565,7 +1571,7 @@ def html_inbox_blogs(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the blogs timeline as html
"""
artist = is_artist(base_dir, nickname)
@ -1589,7 +1595,7 @@ def html_inbox_blogs(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone, bold_reading)
def html_inbox_features(css_cache: {}, default_timeline: str,
@ -1620,7 +1626,7 @@ def html_inbox_features(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the features timeline as html
"""
return html_timeline(css_cache, default_timeline,
@ -1643,7 +1649,7 @@ def html_inbox_features(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone, bold_reading)
def html_inbox_news(css_cache: {}, default_timeline: str,
@ -1673,7 +1679,7 @@ def html_inbox_news(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the news timeline as html
"""
return html_timeline(css_cache, default_timeline,
@ -1696,7 +1702,7 @@ def html_inbox_news(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone, bold_reading)
def html_outbox(css_cache: {}, default_timeline: str,
@ -1726,7 +1732,7 @@ def html_outbox(css_cache: {}, default_timeline: str,
shared_items_federated_domains: [],
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str,
timezone: str) -> str:
timezone: str, bold_reading: bool) -> str:
"""Show the Outbox as html
"""
manually_approve_followers = \
@ -1752,4 +1758,4 @@ def html_outbox(css_cache: {}, default_timeline: str,
allow_local_network_access, text_mode_banner,
access_keys, system_language, max_like_count,
shared_items_federated_domains, signing_priv_key_pem,
cw_lists, lists_enabled, timezone)
cw_lists, lists_enabled, timezone, bold_reading)

View File

@ -418,6 +418,8 @@ def shares_timeline_json(actor: str, pageNumber: int, items_per_page: int,
share_actor = share_actor.replace('___', '://')
share_actor = share_actor.replace('--', '/')
share_nickname = get_nickname_from_actor(share_actor)
if not share_nickname:
continue
if is_blocked(base_dir, nickname, domain,
share_nickname, federated_domain, None):
continue
@ -1538,6 +1540,8 @@ def html_search_result_share(base_dir: str, shared_item: {}, translate: {},
# should the remove button be shown?
show_remove_button = False
nickname = get_nickname_from_actor(actor)
if not nickname:
return ''
if actor.endswith('/users/' + contact_nickname):
show_remove_button = True
elif is_moderator(base_dir, nickname):