\n'
return (title_str, reply_avatar_image_in_post,
container_class_icons, container_class)
def _get_post_title_html(base_dir: str,
http_prefix: str,
nickname: str, domain: str,
show_repeat_icon: bool,
is_announced: bool,
post_json_object: {},
post_actor: str,
translate: {},
enable_timing_log: bool,
post_start_time,
box_name: str,
person_cache: {},
allow_downloads: bool,
avatar_position: str,
page_number: int,
message_id_str: str,
container_class_icons: str,
container_class: str,
mitm: bool) -> (str, str, str, str):
"""Returns the title of a post containing names of participants
x replies to y, x announces y, etc
"""
if not is_announced and box_name == 'search' and \
post_json_object.get('object'):
if post_json_object['object'].get('attributedTo'):
if post_json_object['object']['attributedTo'] != post_actor:
is_announced = True
if is_announced:
return _get_post_title_announce_html(base_dir,
http_prefix,
nickname, domain,
show_repeat_icon,
is_announced,
post_json_object,
post_actor,
translate,
enable_timing_log,
post_start_time,
box_name,
person_cache,
allow_downloads,
avatar_position,
page_number,
message_id_str,
container_class_icons,
container_class, mitm)
return _get_post_title_reply_html(base_dir,
http_prefix,
nickname, domain,
show_repeat_icon,
is_announced,
post_json_object,
post_actor,
translate,
enable_timing_log,
post_start_time,
box_name,
person_cache,
allow_downloads,
avatar_position,
page_number,
message_id_str,
container_class_icons,
container_class, mitm)
def _get_footer_with_icons(show_icons: bool,
container_class_icons: str,
reply_str: str, announce_str: str,
like_str: str, reaction_str: str,
bookmark_str: str,
delete_str: str, mute_str: str, edit_str: str,
buy_str: str,
post_json_object: {}, published_link: str,
time_class: str, published_str: str,
nickname: str, content_license_url: str,
translate: {}) -> str:
"""Returns the html for a post footer containing icons
"""
if not show_icons:
return None
footer_str = '\n \n'
return footer_str
def _substitute_onion_domains(base_dir: str, content: str) -> str:
"""Replace clearnet domains with onion domains
"""
# any common sites which have onion equivalents
bbc_onion = \
'bbcweb3hytmzhn5d532owbu6oqadra5z3ar726vq5kgwwn6aucdccrad.onion'
ddg_onion = \
'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion'
guardian_onion = \
'guardian2zotagl6tmjucg3lrhxdk4dw3lhbqnkvvkywawy3oqfoprid.onion'
propublica_onion = \
'p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd.onion'
# woe betide anyone following a facebook link, but if you must
# then do it safely
facebook_onion = \
'facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion'
protonmail_onion = \
'protonmailrmez3lotccipshtkleegetolb73fuirgj7r4o4vfu7ozyd.onion'
riseup_onion = \
'vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion'
keybase_onion = \
'keybase5wmilwokqirssclfnsqrjdsi7jdir5wy7y7iu3tanwmtp6oid.onion'
zerobin_onion = \
'zerobinftagjpeeebbvyzjcqyjpmjvynj5qlexwyxe7l3vqejxnqv5qd.onion'
securedrop_onion = \
'sdolvtfhatvsysc6l34d65ymdwxcujausv7k5jk4cy5ttzhjoi6fzvyd.onion'
# the hell site 🔥
twitter_onion = \
'twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion'
onion_domains = {
"bbc.com": bbc_onion,
"bbc.co.uk": bbc_onion,
"theguardian.com": guardian_onion,
"theguardian.co.uk": guardian_onion,
"duckduckgo.com": ddg_onion,
"propublica.org": propublica_onion,
"facebook.com": facebook_onion,
"protonmail.ch": protonmail_onion,
"proton.me": protonmail_onion,
"riseup.net": riseup_onion,
"keybase.io": keybase_onion,
"zerobin.net": zerobin_onion,
"securedrop.org": securedrop_onion,
"twitter.com": twitter_onion
}
onion_domains_filename = base_dir + '/accounts/onion_domains.txt'
if os.path.isfile(onion_domains_filename):
onion_domains_list = []
try:
with open(onion_domains_filename, 'r',
encoding='utf-8') as fp_onions:
onion_domains_list = fp_onions.readlines()
except OSError:
print('EX: unable to load onion domains file ' +
onion_domains_filename)
if onion_domains_list:
onion_domains = {}
separators = (' ', ',', '->')
for line in onion_domains_list:
line = line.strip()
if line.startswith('#'):
continue
for sep in separators:
if sep not in line:
continue
clearnet = line.split(sep, 1)[0].strip()
onion1 = line.split(sep, 1)[1].strip()
onion = remove_eol(onion1)
if clearnet and onion:
onion_domains[clearnet] = onion
break
for clearnet, onion in onion_domains.items():
if clearnet in content:
content = content.replace(clearnet, onion)
return content
def _add_dogwhistle_warnings(summary: str, content: str,
dogwhistles: {}, translate: {}) -> {}:
"""Adds dogwhistle warnings for the given content
"""
if not dogwhistles:
return summary
content_str = str(summary) + ' ' + content
detected = detect_dogwhistles(content_str, dogwhistles)
if not detected:
return summary
for _, item in detected.items():
if not item.get('category'):
continue
whistle_str = item['category']
if translate.get(whistle_str):
whistle_str = translate[whistle_str]
if summary:
if whistle_str not in summary:
summary += ', ' + whistle_str
else:
summary = whistle_str
return summary
def _get_content_license(post_json_object: {}) -> str:
"""Returns the content license for the given post
"""
if not post_json_object['object'].get('attachment'):
if not post_json_object['object'].get('schema:license'):
return None
if post_json_object['object'].get('schema:license'):
value = post_json_object['object']['schema:license']
if '://' not in value:
value = license_link_from_name(value)
return value
for item in post_json_object['object']['attachment']:
if not item.get('name'):
continue
name_lower = item['name'].lower()
if 'license' not in name_lower and \
'copyright' not in name_lower and \
'licence' not in name_lower:
continue
if item.get('value'):
value = item['value']
elif item.get('href'):
value = item['href']
else:
continue
if '://' not in value:
value = license_link_from_name(value)
return value
return None
def _get_copyright_footer(content_license_url: str,
translate: {}) -> str:
"""Returns the footer copyright link
"""
# show the CC symbol
icon_filename = 'license_cc.png'
if '/zero/' in content_license_url:
icon_filename = 'license_cc0.png'
elif 'unlicense' in content_license_url:
icon_filename = 'license_un.png'
elif 'wtfpl' in content_license_url:
icon_filename = 'license_wtf.png'
elif '/fdl' in content_license_url:
icon_filename = 'license_fdl.png'
description = 'Content License'
if translate.get('Content License'):
description = translate['Content License']
copyright_str = \
' ' + \
'' + \
'\n'
return copyright_str
def _get_buy_footer(buy_links: {}, translate: {}) -> str:
"""Returns the footer buy link
"""
if not buy_links:
return ''
icon_filename = 'buy.png'
description = translate['Buy']
buy_str = ''
for buy_title, buy_url in buy_links.items():
buy_str = \
' ' + \
'' + \
'\n'
break
return buy_str
def individual_post_as_html(signing_priv_key_pem: str,
allow_downloads: bool,
recent_posts_cache: {}, max_recent_posts: int,
translate: {},
page_number: int, base_dir: str,
session, cached_webfingers: {}, person_cache: {},
nickname: str, domain: str, port: int,
post_json_object: {},
avatar_url: str, show_avatar_options: bool,
allow_deletion: bool,
http_prefix: str, project_version: str,
box_name: str,
yt_replace_domain: str,
twitter_replacement_domain: str,
show_published_date_only: bool,
peertube_instances: [],
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int,
show_repeats: bool,
show_icons: bool,
manually_approves_followers: bool,
show_public_only: bool,
store_to_cache: bool,
use_cache_only: bool,
cw_lists: {},
lists_enabled: str,
timezone: str,
mitm: bool, bold_reading: bool,
dogwhistles: {},
minimize_all_images: bool,
first_post_id: str,
buy_sites: {}) -> str:
""" Shows a single post as html
"""
if not post_json_object:
return ''
# maximum number of different emoji reactions which can be added to a post
max_reaction_types = 5
# benchmark
post_start_time = time.time()
post_actor = post_json_object['actor']
# ZZZzzz
if is_person_snoozed(base_dir, nickname, domain, post_actor):
return ''
# if downloads of avatar images aren't enabled then we can do more
# accurate timing of different parts of the code
enable_timing_log = not allow_downloads
_log_post_timing(enable_timing_log, post_start_time, '1')
avatar_position = ''
message_id = ''
if post_json_object.get('id'):
message_id = remove_hash_from_post_id(post_json_object['id'])
message_id = remove_id_ending(message_id)
_log_post_timing(enable_timing_log, post_start_time, '2')
# does this post have edits?
edits_post_url = \
remove_id_ending(message_id.strip()).replace('/', '#') + '.edits'
account_dir = acct_dir(base_dir, nickname, domain) + '/'
edits_filename = account_dir + box_name + '/' + edits_post_url
edits_str = ''
if os.path.isfile(edits_filename):
edits_json = load_json(edits_filename, 0, 1)
if edits_json:
edits_str = create_edits_html(edits_json, post_json_object,
translate, timezone, system_language)
message_id_str = ''
if message_id:
message_id_str = ';' + message_id
domain_full = get_full_domain(domain, port)
page_number_param = ''
if page_number:
page_number_param = '?page=' + str(page_number)
# get the html post from the recent posts cache if it exists there
post_html = \
_get_post_from_recent_cache(session, base_dir,
http_prefix, nickname, domain,
post_json_object,
post_actor,
person_cache,
allow_downloads,
show_public_only,
store_to_cache,
box_name,
avatar_url,
enable_timing_log,
post_start_time,
page_number,
recent_posts_cache,
max_recent_posts,
signing_priv_key_pem,
first_post_id)
if post_html:
return post_html
if use_cache_only and post_json_object['type'] != 'Announce':
return ''
_log_post_timing(enable_timing_log, post_start_time, '4')
avatar_url = \
get_avatar_image_url(session,
base_dir, http_prefix,
post_actor, person_cache,
avatar_url, allow_downloads,
signing_priv_key_pem)
_log_post_timing(enable_timing_log, post_start_time, '5')
# get the display name
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)
if not post_actor_domain:
return ''
post_actor_domain_full = \
get_full_domain(post_actor_domain, post_actor_port)
post_actor_handle = post_actor_nickname + '@' + post_actor_domain_full
post_actor_wf = \
webfinger_handle(session, post_actor_handle, http_prefix,
cached_webfingers,
domain, __version__, False, False,
signing_priv_key_pem)
avatar_url2 = None
display_name = None
if post_actor_wf:
origin_domain = domain
(_, _, _, _, _, avatar_url2,
display_name, _) = get_person_box(signing_priv_key_pem,
origin_domain,
base_dir, session,
post_actor_wf,
person_cache,
project_version,
http_prefix,
nickname, domain,
'outbox', 72367)
_log_post_timing(enable_timing_log, post_start_time, '6')
if avatar_url2:
avatar_url = avatar_url2
if display_name:
# add any emoji to the display name
if ':' in display_name:
display_name = \
add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain,
display_name, False, translate)
_log_post_timing(enable_timing_log, post_start_time, '7')
avatar_link = \
_get_avatar_image_html(show_avatar_options,
nickname, domain_full,
avatar_url, post_actor,
translate, avatar_position,
page_number, message_id_str)
avatar_image_in_post = \
'
' + avatar_link + '
\n'
timeline_post_bookmark = remove_id_ending(post_json_object['id'])
timeline_post_bookmark = timeline_post_bookmark.replace('://', '-')
timeline_post_bookmark = timeline_post_bookmark.replace('/', '-')
# If this is the inbox timeline then don't show the repeat icon on any DMs
show_repeat_icon = show_repeats
is_public_repeat = False
post_is_dm = is_dm(post_json_object)
if show_repeats:
if post_is_dm:
show_repeat_icon = False
else:
if not is_public_post(post_json_object):
is_public_repeat = True
title_str = ''
gallery_str = ''
is_announced = False
announce_json_object = None
if post_json_object['type'] == 'Announce':
announce_json_object = post_json_object.copy()
blocked_cache = {}
post_json_announce = \
download_announce(session, base_dir, http_prefix,
nickname, domain, post_json_object,
project_version,
yt_replace_domain,
twitter_replacement_domain,
allow_local_network_access,
recent_posts_cache, False,
system_language,
domain_full, person_cache,
signing_priv_key_pem,
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'])
reject_post_id(base_dir, nickname, domain, announced_post_id,
recent_posts_cache)
return ''
post_json_object = post_json_announce
# is the announced post in the html cache?
post_html = \
_get_post_from_recent_cache(session, base_dir,
http_prefix, nickname, domain,
post_json_object,
post_actor,
person_cache,
allow_downloads,
show_public_only,
store_to_cache,
box_name,
avatar_url,
enable_timing_log,
post_start_time,
page_number,
recent_posts_cache,
max_recent_posts,
signing_priv_key_pem,
first_post_id)
if post_html:
return post_html
announce_filename = \
locate_post(base_dir, nickname, domain, post_json_object['id'])
if announce_filename:
update_announce_collection(recent_posts_cache,
base_dir, announce_filename,
post_actor, nickname,
domain_full, False)
# create a file for use by text-to-speech
if is_recent_post(post_json_object, 3):
if post_json_object.get('actor'):
if not os.path.isfile(announce_filename + '.tts'):
update_speaker(base_dir, http_prefix,
nickname, domain, domain_full,
post_json_object, person_cache,
translate, post_json_object['actor'],
theme_name, system_language,
box_name)
try:
with open(announce_filename + '.tts', 'w+',
encoding='utf-8') as ttsfile:
ttsfile.write('\n')
except OSError:
print('EX: unable to write tts ' +
announce_filename + '.tts')
is_announced = True
_log_post_timing(enable_timing_log, post_start_time, '8')
if not has_object_dict(post_json_object):
return ''
# if this post should be public then check its recipients
if show_public_only:
if not post_contains_public(post_json_object):
return ''
is_moderation_post = False
if post_json_object['object'].get('moderationStatus'):
is_moderation_post = True
container_class = 'container'
container_class_icons = 'containericons'
time_class = 'time-right'
actor_nickname = get_nickname_from_actor(post_actor)
if not actor_nickname:
# single user instance
actor_nickname = 'dev'
actor_domain, _ = get_domain_from_actor(post_actor)
# scope icon before display name
if is_followers_post(post_json_object):
title_str += \
' \n'
elif is_unlisted_post(post_json_object):
title_str += \
' \n'
elif is_reminder(post_json_object):
title_str += \
' \n'
display_name = get_display_name(base_dir, post_actor, person_cache)
if display_name:
if len(display_name) < 2 or \
display_name_is_emoji(display_name):
display_name = None
if display_name:
if ':' in display_name:
display_name = \
add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain,
display_name, False, translate)
title_str += \
' ' + \
'' + display_name + '' + \
'\n'
else:
if not message_id:
# pprint(post_json_object)
print('ERROR: no message_id')
if not actor_nickname:
# pprint(post_json_object)
print('ERROR: no actor_nickname')
if not actor_domain:
# pprint(post_json_object)
print('ERROR: no actor_domain')
actor_handle = actor_nickname + '@' + actor_domain
title_str += \
' ' + \
'@' + actor_handle + '\n'
# benchmark 9
_log_post_timing(enable_timing_log, post_start_time, '9')
# Show a DM icon for DMs in the inbox timeline
if post_is_dm:
title_str = \
title_str + ' \n'
# check if replying is permitted
comments_enabled = True
if isinstance(post_json_object['object'], dict) and \
'commentsEnabled' in post_json_object['object']:
if post_json_object['object']['commentsEnabled'] is False:
comments_enabled = False
elif 'rejectReplies' in post_json_object['object']:
if post_json_object['object']['rejectReplies']:
comments_enabled = False
conversation_id = None
if isinstance(post_json_object['object'], dict):
if 'conversation' in post_json_object['object']:
if post_json_object['object']['conversation']:
conversation_id = post_json_object['object']['conversation']
elif 'context' in post_json_object['object']:
if post_json_object['object']['context']:
conversation_id = post_json_object['object']['context']
public_reply = False
unlisted_reply = False
if is_public_post(post_json_object):
public_reply = True
if is_unlisted_post(post_json_object):
public_reply = False
unlisted_reply = True
reply_str = _get_reply_icon_html(base_dir, nickname, domain,
public_reply, unlisted_reply,
show_icons, comments_enabled,
post_json_object, page_number_param,
translate, system_language,
conversation_id)
_log_post_timing(enable_timing_log, post_start_time, '10')
edit_str = _get_edit_icon_html(base_dir, nickname, domain_full,
post_json_object, actor_nickname,
translate, False, first_post_id)
_log_post_timing(enable_timing_log, post_start_time, '11')
announce_str = \
_get_announce_icon_html(is_announced,
post_actor,
nickname, domain_full,
announce_json_object,
post_json_object,
is_public_repeat,
is_moderation_post,
show_repeat_icon,
translate,
page_number_param,
timeline_post_bookmark,
box_name, max_like_count,
first_post_id)
_log_post_timing(enable_timing_log, post_start_time, '12')
# whether to show a like button
hide_like_button_file = \
acct_dir(base_dir, nickname, domain) + '/.hideLikeButton'
show_like_button = True
if os.path.isfile(hide_like_button_file):
show_like_button = False
# whether to show a reaction button
hide_reaction_button_file = \
acct_dir(base_dir, nickname, domain) + '/.hideReactionButton'
show_reaction_button = True
if os.path.isfile(hide_reaction_button_file):
show_reaction_button = False
like_json_object = post_json_object
if announce_json_object:
like_json_object = announce_json_object
like_str = _get_like_icon_html(nickname, domain_full,
is_moderation_post,
show_like_button,
like_json_object,
enable_timing_log,
post_start_time,
translate, page_number_param,
timeline_post_bookmark,
box_name, max_like_count,
first_post_id)
_log_post_timing(enable_timing_log, post_start_time, '12.5')
bookmark_str = \
_get_bookmark_icon_html(base_dir, nickname, domain,
domain_full, post_json_object,
is_moderation_post, translate,
enable_timing_log,
post_start_time, box_name,
page_number_param,
timeline_post_bookmark,
first_post_id, message_id)
_log_post_timing(enable_timing_log, post_start_time, '12.9')
reaction_str = \
_get_reaction_icon_html(nickname, post_json_object,
is_moderation_post,
show_reaction_button,
translate,
enable_timing_log,
post_start_time, box_name,
page_number_param,
timeline_post_bookmark,
first_post_id)
_log_post_timing(enable_timing_log, post_start_time, '12.10')
is_muted = post_is_muted(base_dir, nickname, domain,
post_json_object, message_id)
_log_post_timing(enable_timing_log, post_start_time, '13')
mute_str = \
_get_mute_icon_html(is_muted,
post_actor,
message_id,
nickname, domain_full,
allow_deletion,
page_number_param,
box_name,
timeline_post_bookmark,
translate, first_post_id)
delete_str = \
_get_delete_icon_html(nickname, domain_full,
allow_deletion,
post_actor,
message_id,
post_json_object,
page_number_param,
translate, first_post_id)
_log_post_timing(enable_timing_log, post_start_time, '13.1')
# get the title: x replies to y, x announces y, etc
(title_str2,
reply_avatar_image_in_post,
container_class_icons,
container_class) = _get_post_title_html(base_dir,
http_prefix,
nickname, domain,
show_repeat_icon,
is_announced,
post_json_object,
post_actor,
translate,
enable_timing_log,
post_start_time,
box_name,
person_cache,
allow_downloads,
avatar_position,
page_number,
message_id_str,
container_class_icons,
container_class, mitm)
title_str += title_str2
_log_post_timing(enable_timing_log, post_start_time, '14')
person_url = local_actor_url(http_prefix, nickname, domain_full)
actor_json = \
get_person_from_cache(base_dir, person_url, person_cache)
languages_understood = []
if actor_json:
languages_understood = get_actor_languages_list(actor_json)
content_str = get_content_from_post(post_json_object, system_language,
languages_understood)
# remove any css styling within the post itself
content_str = remove_style_within_html(content_str)
content_language = \
get_language_from_post(post_json_object, system_language,
languages_understood)
content_str = dont_speak_hashtags(content_str)
attachment_str, gallery_str = \
get_post_attachments_as_html(base_dir, nickname, domain,
domain_full,
post_json_object,
box_name, translate,
is_muted, avatar_link,
reply_str, announce_str, like_str,
bookmark_str, delete_str, mute_str,
content_str,
minimize_all_images,
system_language)
published_str = \
_get_published_date_str(post_json_object, show_published_date_only,
timezone)
_log_post_timing(enable_timing_log, post_start_time, '15')
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/', '/')
# if this is a local link then make it relative so that it works
# on clearnet or onion address
if domain + '/users/' in published_link or \
domain + ':' + str(port) + '/users/' in published_link:
published_link = '/users/' + published_link.split('/users/')[1]
content_license_url = _get_content_license(post_json_object)
if not is_news_post(post_json_object):
if show_icons:
footer_str = ''
else:
footer_str = '
\n'
if content_license_url and not is_reminder(post_json_object):
footer_str += _get_copyright_footer(content_license_url,
translate)
conv_link = '/users/' + nickname + '?convthread=' + \
published_link.replace('/', '--')
footer_str += '' + \
published_str + '\n'
if not show_icons:
footer_str += '
\n'
else:
footer_str = '' + \
published_str + '\n'
# change the background color for DMs in inbox timeline
if post_is_dm:
container_class_icons = 'containericons dm'
container_class = 'container dm'
# add any content warning from the cwlists directory
add_cw_from_lists(post_json_object, cw_lists, translate, lists_enabled,
system_language)
post_is_sensitive = False
if post_json_object['object'].get('sensitive'):
# sensitive posts should have a summary
if post_json_object['object'].get('summary'):
post_is_sensitive = post_json_object['object']['sensitive']
else:
# add a generic summary if none is provided
sensitive_str = 'Sensitive'
if translate.get(sensitive_str):
sensitive_str = translate[sensitive_str]
post_json_object['object']['summary'] = sensitive_str
post_json_object['object']['summaryMap'] = {
system_language: sensitive_str
}
if not post_json_object['object'].get('summary'):
post_json_object['object']['summary'] = ''
post_json_object['object']['summaryMap'] = {
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] = \
post_json_object['object']['content']
domain_full = get_full_domain(domain, port)
if not content_str:
content_str = get_content_from_post(post_json_object, system_language,
languages_understood)
# remove any css styling within the post itself
content_str = remove_style_within_html(content_str)
content_language = \
get_language_from_post(post_json_object, system_language,
languages_understood)
content_str = dont_speak_hashtags(content_str)
if not content_str:
content_str = \
auto_translate_post(base_dir, post_json_object,
system_language, translate)
if not content_str:
return ''
content_str = \
replace_remote_hashtags(content_str, nickname, domain)
summary_str = ''
if content_str:
summary_str = get_summary_from_post(post_json_object, system_language,
languages_understood)
# add dogwhistle warnings to summary
summary_str = _add_dogwhistle_warnings(summary_str, content_str,
dogwhistles, translate)
content_all_str = str(summary_str) + ' ' + content_str
# does an emoji or lack of alt text on an image indicate a
# no boost preference? if so then don't show the repeat/announce icon
attachment = []
if post_json_object['object'].get('attachment'):
attachment = post_json_object['object']['attachment']
if disallow_announce(content_all_str, attachment):
announce_str = ''
# does an emoji indicate a no replies preference?
# if so then don't show the reply icon
if disallow_reply(content_all_str):
reply_str = ''
is_patch = is_git_patch(base_dir, nickname, domain,
post_json_object['object']['type'],
summary_str, content_str)
# html for the buy icon
buy_str = ''
if 'attachment' not in post_json_object['object']:
post_json_object['object']['attachment'] = []
if not is_patch:
buy_links = get_buy_links(post_json_object, translate, buy_sites)
buy_str = _get_buy_footer(buy_links, translate)
new_footer_str = \
_get_footer_with_icons(show_icons,
container_class_icons,
reply_str, announce_str,
like_str, reaction_str, bookmark_str,
delete_str, mute_str, edit_str, buy_str,
post_json_object, published_link,
time_class, published_str, nickname,
content_license_url, translate)
if new_footer_str:
footer_str = new_footer_str
# add an extra line if there is a content warning,
# for better vertical spacing on mobile
if post_is_sensitive:
footer_str = ' ' + footer_str
if not summary_str:
summary_str = get_summary_from_post(post_json_object, system_language,
languages_understood)
_log_post_timing(enable_timing_log, post_start_time, '16')
if not is_pgp_encrypted(content_str):
# if we are on an onion instance then substitute any common clearnet
# domains with their onion version
if '.onion' in domain and '://' in content_str:
content_str = \
_substitute_onion_domains(base_dir, content_str)
if not is_patch:
# remove any tabs
content_str = \
content_str.replace('\t', '').replace('\r', '')
# 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, bold_reading)
object_content = limit_repeated_words(object_content, 6)
object_content = \
switch_words(base_dir, nickname, domain, object_content)
object_content = html_replace_email_quote(object_content)
object_content = html_replace_quote_marks(object_content)
# append any edits
object_content += edits_str
else:
object_content = content_str
else:
encrypted_str = 'Encrypted'
if translate.get(encrypted_str):
encrypted_str = translate[encrypted_str]
object_content = '🔒 ' + encrypted_str
object_content = \
'' + \
object_content + ''
if not post_is_sensitive:
content_str = object_content + attachment_str
content_str = add_embedded_elements(translate, content_str,
peertube_instances, domain)
content_str = insert_question(base_dir, translate,
nickname, domain,
content_str, post_json_object,
page_number)
else:
post_id = 'post' + str(create_password(8))
content_str = ''
if summary_str:
cw_str = \
add_emoji_to_display_name(session, base_dir, http_prefix,
nickname, domain,
summary_str, False, translate)
content_str += \
'\n'
if is_moderation_post:
container_class = 'container report'
# get the content warning text
cw_content_str = object_content + attachment_str
if not is_patch:
cw_content_str = add_embedded_elements(translate, cw_content_str,
peertube_instances,
domain_full)
cw_content_str = \
insert_question(base_dir, translate, nickname, domain,
cw_content_str, post_json_object, page_number)
cw_content_str = \
switch_words(base_dir, nickname, domain, cw_content_str)
if not is_blog_post(post_json_object):
# get the content warning button
content_str += \
get_content_warning_button(post_id, translate, cw_content_str)
else:
content_str += cw_content_str
_log_post_timing(enable_timing_log, post_start_time, '17')
map_str = ''
buy_links = {}
if post_json_object['object'].get('tag'):
if not is_patch:
content_str = \
replace_emoji_from_tags(session, base_dir, content_str,
post_json_object['object']['tag'],
'content', False, True)
buy_links = get_buy_links(post_json_object, translate, buy_sites)
# show embedded map if the location contains a map url
location_str = \
get_location_from_tags(post_json_object['object']['tag'])
if location_str:
if '://' in location_str and '.' in location_str:
bounding_box_degrees = 0.001
map_str = \
html_open_street_map(location_str,
bounding_box_degrees,
translate)
if map_str:
map_str = '
\n' + map_str + '
\n'
attrib = None
if post_json_object['object'].get('attributedTo'):
attrib = post_json_object['object']['attributedTo']
if map_str and attrib:
# is this being sent by the author?
if '://' + domain_full + '/users/' + nickname in attrib:
location_domain = location_str
if '://' in location_str:
location_domain = location_str.split('://')[1]
if '/' in location_domain:
location_domain = location_domain.split('/')[0]
location_domain = \
location_str.split('://')[0] + '://' + location_domain
else:
if '/' in location_domain:
location_domain = location_domain.split('/')[0]
location_domain = 'https://' + location_domain
# remember the map site used
set_map_preferences_url(base_dir, nickname, domain,
location_domain)
# remember the coordinates
map_zoom, map_latitude, map_longitude = \
geocoords_from_map_link(location_str)
if map_zoom and map_latitude and map_longitude:
set_map_preferences_coords(base_dir, nickname, domain,
map_latitude, map_longitude,
map_zoom)
if is_muted:
content_str = ''
else:
if not is_patch:
message_class = 'message'
if language_right_to_left(content_language):
message_class = 'message_rtl'
content_str = '
' + \
content_str + \
'
\n'
else:
content_str = \
'
' + content_str + \
'
\n'
# show blog citations
citations_str = \
_get_blog_citations_html(box_name, post_json_object, translate)
post_html = ''
if box_name != 'tlmedia':
reaction_str = ''
if show_icons:
reaction_str = \
html_emoji_reactions(post_json_object, True, person_url,
max_reaction_types,
box_name, page_number)
if post_is_sensitive and reaction_str:
reaction_str = ' ' + reaction_str
post_html = '