Variable types

main
bashrc 2026-05-06 11:15:06 +01:00
parent c12c28dd76
commit 3a7a2ee0f5
3 changed files with 222 additions and 212 deletions

View File

@ -173,32 +173,31 @@ def html_theme_designer(base_dir: str,
theme_name: str, access_keys: {}) -> str: theme_name: str, access_keys: {}) -> str:
"""Edit theme settings """Edit theme settings
""" """
theme_filename = base_dir + '/theme/' + theme_name + '/theme.json' theme_filename: str = base_dir + '/theme/' + theme_name + '/theme.json'
theme_json = {} theme_json: dict = {}
if is_a_file(theme_filename): if is_a_file(theme_filename):
theme_json = load_json(theme_filename) theme_json = load_json(theme_filename)
# set custom theme parameters # set custom theme parameters
custom_variables_file = data_dir(base_dir) + '/theme.json' custom_variables_file: str = data_dir(base_dir) + '/theme.json'
if is_a_file(custom_variables_file): if is_a_file(custom_variables_file):
custom_theme_params = load_json(custom_variables_file) custom_theme_params: str = load_json(custom_variables_file)
if custom_theme_params: if custom_theme_params:
for variable_name, value in custom_theme_params.items(): for variable_name, value in custom_theme_params.items():
theme_json[variable_name] = value theme_json[variable_name] = value
theme_form: str = '' theme_form: str = ''
css_filename = base_dir + '/epicyon-profile.css' css_filename: str = base_dir + '/epicyon-profile.css'
if is_a_file(base_dir + '/epicyon.css'): if is_a_file(base_dir + '/epicyon.css'):
css_filename = base_dir + '/epicyon.css' css_filename = base_dir + '/epicyon.css'
banner_file, _ = \ banner_file, _ = \
get_banner_file(base_dir, nickname, domain, theme_name) get_banner_file(base_dir, nickname, domain, theme_name)
banner_path = '/users/' + nickname + '/' + banner_file banner_path: str = '/users/' + nickname + '/' + banner_file
instance_title = \ instance_title: str = get_config_param(base_dir, 'instanceTitle')
get_config_param(base_dir, 'instanceTitle') preload_images: list[str] = [banner_path]
preload_images = [banner_path] theme_form: str = \
theme_form = \
html_header_with_external_style(css_filename, instance_title, None, html_header_with_external_style(css_filename, instance_title, None,
preload_images) preload_images)
theme_form += \ theme_form += \
@ -216,8 +215,8 @@ def html_theme_designer(base_dir: str,
theme_form += ' <form method="POST" action="' + \ theme_form += ' <form method="POST" action="' + \
'/users/' + nickname + '/changeThemeSettings">\n' '/users/' + nickname + '/changeThemeSettings">\n'
reset_key = access_keys['menuLogout'] reset_key: str = access_keys['menuLogout']
submit_key = access_keys['submitButton'] submit_key: str = access_keys['submitButton']
theme_form += \ theme_form += \
' <center>\n' + \ ' <center>\n' + \
' <button type="submit" class="button" ' + \ ' <button type="submit" class="button" ' + \
@ -230,13 +229,13 @@ def html_theme_designer(base_dir: str,
contrast_warning: str = '' contrast_warning: str = ''
if theme_json.get('main-bg-color'): if theme_json.get('main-bg-color'):
background = theme_json['main-bg-color'] background: str = theme_json['main-bg-color']
if theme_json.get('main-fg-color'): if theme_json.get('main-fg-color'):
foreground = theme_json['main-fg-color'] foreground: str = theme_json['main-fg-color']
contrast = color_contrast(background, foreground) contrast: float = color_contrast(background, foreground)
if contrast: if contrast:
if contrast < 4.5: if contrast < 4.5:
contrast_warning = '⚠️ ' contrast_warning: str = '⚠️ '
theme_form += \ theme_form += \
' <center><label class="labels">' + \ ' <center><label class="labels">' + \
contrast_warning + '<b>' + \ contrast_warning + '<b>' + \
@ -250,15 +249,15 @@ def html_theme_designer(base_dir: str,
table_str += ' </colgroup>\n' table_str += ' </colgroup>\n'
table_str += ' <tbody>\n' table_str += ' <tbody>\n'
font_str = ' <div class="container">\n' + table_str font_str: str = ' <div class="container">\n' + table_str
color_str = ' <div class="container">\n' + table_str color_str: str = ' <div class="container">\n' + table_str
dimension_str = ' <div class="container">\n' + table_str dimension_str: str = ' <div class="container">\n' + table_str
switch_str = ' <div class="container">\n' + table_str switch_str: str = ' <div class="container">\n' + table_str
for variable_name, value in theme_json.items(): for variable_name, value in theme_json.items():
if 'font-size' in variable_name: if 'font-size' in variable_name:
variable_name_str = variable_name.replace('-', ' ') variable_name_str: str = variable_name.replace('-', ' ')
variable_name_str = variable_name_str.title() variable_name_str = variable_name_str.title()
variable_name_label = variable_name_str variable_name_label: str = variable_name_str
if contrast_warning: if contrast_warning:
if variable_name in ('main-bg-color', 'main-fg-color'): if variable_name in ('main-bg-color', 'main-fg-color'):
variable_name_label = contrast_warning + variable_name_str variable_name_label = contrast_warning + variable_name_str
@ -333,8 +332,8 @@ def html_theme_designer(base_dir: str,
dimension_str += ' </table>\n </div>\n' dimension_str += ' </table>\n </div>\n'
switch_str += ' </table>\n </div>\n' switch_str += ' </table>\n </div>\n'
theme_formats = '.zip, .gz' theme_formats: str = '.zip, .gz'
export_import_str = ' <div class="container">\n' export_import_str: str = ' <div class="container">\n'
export_import_str += \ export_import_str += \
' <label class="labels">' + \ ' <label class="labels">' + \
translate['Import Theme'] + '</label>\n' translate['Import Theme'] + '</label>\n'
@ -362,7 +361,7 @@ def _relative_luminance(color: str) -> float:
""" """
color = color.lstrip('#') color = color.lstrip('#')
rgb = list(int(color[i:i+2], 16) for i in (0, 2, 4)) rgb = list(int(color[i:i+2], 16) for i in (0, 2, 4))
srgb = ( srgb: list[float] = (
rgb[0] / 255.0, rgb[0] / 255.0,
rgb[1] / 255.0, rgb[1] / 255.0,
rgb[2] / 255.0 rgb[2] / 255.0
@ -397,8 +396,8 @@ def color_contrast(background: str, foreground: str) -> float:
foreground = color_to_hex[foreground] foreground = color_to_hex[foreground]
else: else:
return None return None
background_luminance = _relative_luminance(background) background_luminance: float = _relative_luminance(background)
foreground_luminance = _relative_luminance(foreground) foreground_luminance: float = _relative_luminance(foreground)
if background_luminance > foreground_luminance: if background_luminance > foreground_luminance:
return (0.05 + background_luminance) / (0.05 + foreground_luminance) return (0.05 + background_luminance) / (0.05 + foreground_luminance)
return (0.05 + foreground_luminance) / (0.05 + background_luminance) return (0.05 + foreground_luminance) / (0.05 + background_luminance)

View File

@ -62,7 +62,7 @@ def _log_timeline_timing(enable_timing_log: bool, timeline_start_time,
""" """
if not enable_timing_log: if not enable_timing_log:
return return
time_diff = int((time.time() - timeline_start_time) * 1000) time_diff: int = int((time.time() - timeline_start_time) * 1000)
if time_diff > 100: if time_diff > 100:
print('TIMELINE TIMING ' + print('TIMELINE TIMING ' +
box_name + ' ' + debug_id + ' = ' + str(time_diff)) box_name + ' ' + debug_id + ' = ' + str(time_diff))
@ -72,15 +72,13 @@ def _get_help_for_timeline(base_dir: str, box_name: str) -> str:
"""Shows help text for the given timeline """Shows help text for the given timeline
""" """
# get the filename for help for this timeline # get the filename for help for this timeline
help_filename = data_dir(base_dir) + '/help_' + box_name + '.md' help_filename: str = data_dir(base_dir) + '/help_' + box_name + '.md'
if not is_a_file(help_filename): if not is_a_file(help_filename):
language = \ language: str = get_config_param(base_dir, 'language')
get_config_param(base_dir, 'language')
if not language: if not language:
language = 'en' language = 'en'
theme_name = \ theme_name: str = get_config_param(base_dir, 'theme')
get_config_param(base_dir, 'theme') default_filename: str = None
default_filename = None
if theme_name: if theme_name:
default_filename = \ default_filename = \
base_dir + '/theme/' + theme_name + '/welcome/' + \ base_dir + '/theme/' + theme_name + '/welcome/' + \
@ -99,18 +97,17 @@ def _get_help_for_timeline(base_dir: str, box_name: str) -> str:
# show help text # show help text
if is_a_file(help_filename): if is_a_file(help_filename):
instance_title = \ instance_title: str = get_config_param(base_dir, 'instanceTitle')
get_config_param(base_dir, 'instanceTitle')
if not instance_title: if not instance_title:
instance_title = 'Epicyon' instance_title = 'Epicyon'
help_text = \ help_text: str = \
load_string(help_filename, load_string(help_filename,
'EX: _get_help_for_timeline unable to read ' + 'EX: _get_help_for_timeline unable to read ' +
help_filename) help_filename)
if help_text: if help_text:
if dangerous_markup(help_text, False, []): if dangerous_markup(help_text, False, []):
return '' return ''
help_text = help_text.replace('INSTANCE', instance_title) help_text: str = help_text.replace('INSTANCE', instance_title)
return '<div class="container">\n' + \ return '<div class="container">\n' + \
markdown_to_html(help_text) + '\n' + \ markdown_to_html(help_text) + '\n' + \
'</div>\n' '</div>\n'
@ -285,22 +282,22 @@ def _html_timeline_keyboard(moderator: bool, text_mode_banner: str,
access_keys: {}, translate: {}) -> str: access_keys: {}, translate: {}) -> str:
"""Returns html for timeline keyboard navigation """Returns html for timeline keyboard navigation
""" """
calendar_str = translate['Calendar'] calendar_str: str = translate['Calendar']
if new_calendar_event: if new_calendar_event:
calendar_str = '<strong>' + calendar_str + '</strong>' calendar_str = '<strong>' + calendar_str + '</strong>'
dm_str = translate['DM'] dm_str: str = translate['DM']
if new_dm: if new_dm:
dm_str = '<strong>' + dm_str + '</strong>' dm_str = '<strong>' + dm_str + '</strong>'
replies_str = translate['Replies'] replies_str: str = translate['Replies']
if new_reply: if new_reply:
replies_str = '<strong>' + replies_str + '</strong>' replies_str = '<strong>' + replies_str + '</strong>'
shares_str = translate['Shares'] shares_str: str = translate['Shares']
if new_share: if new_share:
shares_str = '<strong>' + shares_str + '</strong>' shares_str = '<strong>' + shares_str + '</strong>'
wanted_str = translate['Wanted'] wanted_str: str = translate['Wanted']
if new_wanted: if new_wanted:
wanted_str = '<strong>' + wanted_str + '</strong>' wanted_str = '<strong>' + wanted_str + '</strong>'
menu_profile = \ menu_profile: str = \
html_hide_from_screen_reader('👤') + ' ' + \ html_hide_from_screen_reader('👤') + ' ' + \
translate['Switch to profile view'] translate['Switch to profile view']
menu_inbox = \ menu_inbox = \
@ -333,7 +330,7 @@ def _html_timeline_keyboard(moderator: bool, text_mode_banner: str,
translate['Create a new post'] translate['Create a new post']
menu_moderation = \ menu_moderation = \
html_hide_from_screen_reader('⚡️') + ' ' + translate['Mod'] html_hide_from_screen_reader('⚡️') + ' ' + translate['Mod']
nav_links = { nav_links: dict = {
menu_profile: '/users/' + nickname, menu_profile: '/users/' + nickname,
menu_inbox: users_path + '/inbox#timelineposts', menu_inbox: users_path + '/inbox#timelineposts',
menu_search: users_path + '/search', menu_search: users_path + '/search',
@ -349,7 +346,7 @@ def _html_timeline_keyboard(moderator: bool, text_mode_banner: str,
menu_newswire: users_path + '/newswiremobile', menu_newswire: users_path + '/newswiremobile',
menu_links: users_path + '/linksmobile' menu_links: users_path + '/linksmobile'
} }
nav_access_keys = {} nav_access_keys: dict = {}
for variable_name, key in access_keys.items(): for variable_name, key in access_keys.items():
if not locals().get(variable_name): if not locals().get(variable_name):
continue continue
@ -375,7 +372,7 @@ def _html_timeline_end(base_dir: str, nickname: str, domain_full: str,
"""Ending of the timeline, containing the right column """Ending of the timeline, containing the right column
""" """
# end of timeline-posts # end of timeline-posts
tl_str = ' </div>\n' tl_str: str = ' </div>\n'
# end of column-center # end of column-center
tl_str += ' </td>\n' tl_str += ' </td>\n'
@ -421,11 +418,11 @@ def page_number_buttons(users_path: str, box_name: str,
num_str: str = '' num_str: str = ''
for page in range(min_page_number, max_page_number): for page in range(min_page_number, max_page_number):
if num_str: if num_str:
separator_str = \ separator_str: str = \
'<label class="pageslistDash">────</label>' '<label class="pageslistDash">────</label>'
num_str += html_hide_from_screen_reader(separator_str) num_str += html_hide_from_screen_reader(separator_str)
aria_page_str: str = '' aria_page_str: str = ''
page_str = ' ' + str(page) + ' ' page_str: str = ' ' + str(page) + ' '
curr_page_str: str = '' curr_page_str: str = ''
if page == page_number: if page == page_number:
page_str = '[<mark>' + str(page) + '</mark>]' page_str = '[<mark>' + str(page) + '</mark>]'
@ -495,17 +492,17 @@ def html_timeline(default_timeline: str,
timeline_start_time = time.time() timeline_start_time = time.time()
account_dir = acct_dir(base_dir, nickname, domain) account_dir: str = acct_dir(base_dir, nickname, domain)
# should the calendar icon be highlighted? # should the calendar icon be highlighted?
new_calendar_event: bool = False new_calendar_event: bool = False
calendar_image = 'calendar.png' calendar_image: str = 'calendar.png'
calendar_path = '/calendar' calendar_path: str = '/calendar'
calendar_file = account_dir + '/.newCalendar' calendar_file: str = account_dir + '/.newCalendar'
if is_a_file(calendar_file): if is_a_file(calendar_file):
new_calendar_event = True new_calendar_event = True
calendar_image = 'calendar_notify.png' calendar_image = 'calendar_notify.png'
calendar_path_str = \ calendar_path_str: str = \
load_string(calendar_file, load_string(calendar_file,
'EX: html_timeline unable to read ' + 'EX: html_timeline unable to read ' +
calendar_file) calendar_file)
@ -517,7 +514,7 @@ def html_timeline(default_timeline: str,
# should the DM button be highlighted? # should the DM button be highlighted?
new_dm: bool = False new_dm: bool = False
dm_file = account_dir + '/.newDM' dm_file: str = account_dir + '/.newDM'
if is_a_file(dm_file): if is_a_file(dm_file):
new_dm = True new_dm = True
if box_name == 'dm': if box_name == 'dm':
@ -526,7 +523,7 @@ def html_timeline(default_timeline: str,
# should the Replies button be highlighted? # should the Replies button be highlighted?
new_reply: bool = False new_reply: bool = False
reply_file = account_dir + '/.newReply' reply_file: str = account_dir + '/.newReply'
if is_a_file(reply_file): if is_a_file(reply_file):
new_reply = True new_reply = True
if box_name == 'tlreplies': if box_name == 'tlreplies':
@ -535,7 +532,7 @@ def html_timeline(default_timeline: str,
# should the Shares button be highlighted? # should the Shares button be highlighted?
new_share: bool = False new_share: bool = False
new_share_file = account_dir + '/.newShare' new_share_file: str = account_dir + '/.newShare'
if is_a_file(new_share_file): if is_a_file(new_share_file):
new_share = True new_share = True
if box_name == 'tlshares': if box_name == 'tlshares':
@ -545,7 +542,7 @@ def html_timeline(default_timeline: str,
# should the Wanted button be highlighted? # should the Wanted button be highlighted?
new_wanted: bool = False new_wanted: bool = False
new_wanted_file = account_dir + '/.newWanted' new_wanted_file: str = account_dir + '/.newWanted'
if is_a_file(new_wanted_file): if is_a_file(new_wanted_file):
new_wanted = True new_wanted = True
if box_name == 'tlwanted': if box_name == 'tlwanted':
@ -555,7 +552,7 @@ def html_timeline(default_timeline: str,
# should the Moderation/reports button be highlighted? # should the Moderation/reports button be highlighted?
new_report: bool = False new_report: bool = False
new_report_file = account_dir + '/.newReport' new_report_file: str = account_dir + '/.newReport'
if is_a_file(new_report_file): if is_a_file(new_report_file):
new_report = True new_report = True
if box_name == 'moderation': if box_name == 'moderation':
@ -565,7 +562,7 @@ def html_timeline(default_timeline: str,
# show polls/votes? # show polls/votes?
show_vote_posts: bool = True show_vote_posts: bool = True
show_vote_file = account_dir + '/.noVotes' show_vote_file: str = account_dir + '/.noVotes'
if is_a_file(show_vote_file): if is_a_file(show_vote_file):
show_vote_posts = False show_vote_posts = False
@ -574,7 +571,7 @@ def html_timeline(default_timeline: str,
separator_str = html_post_separator(base_dir, None) separator_str = html_post_separator(base_dir, None)
# the css filename # the css filename
css_filename = base_dir + '/epicyon-profile.css' css_filename: str = base_dir + '/epicyon-profile.css'
if is_a_file(base_dir + '/epicyon.css'): if is_a_file(base_dir + '/epicyon.css'):
css_filename = base_dir + '/epicyon.css' css_filename = base_dir + '/epicyon.css'
@ -647,16 +644,16 @@ def html_timeline(default_timeline: str,
bookmarks_button = 'buttonselected' bookmarks_button = 'buttonselected'
# get the full domain, including any port number # get the full domain, including any port number
full_domain = get_full_domain(domain, port) full_domain: str = get_full_domain(domain, port)
users_path = '/users/' + nickname users_path: str = '/users/' + nickname
actor = http_prefix + '://' + full_domain + users_path actor: str = http_prefix + '://' + full_domain + users_path
show_individual_post_icons = True show_individual_post_icons: bool = True
# show an icon for new follow approvals # show an icon for new follow approvals
follow_approvals: str = '' follow_approvals: str = ''
follow_requests_filename = \ follow_requests_filename: str = \
acct_dir(base_dir, nickname, domain) + '/followrequests.txt' acct_dir(base_dir, nickname, domain) + '/followrequests.txt'
if is_a_file(follow_requests_filename): if is_a_file(follow_requests_filename):
follow_requests_list: list[str] = \ follow_requests_list: list[str] = \
@ -731,13 +728,12 @@ def html_timeline(default_timeline: str,
'<span>' + translate['Bookmarks'] + '</span></button></a>' '<span>' + translate['Bookmarks'] + '</span></button></a>'
# filename of the banner shown at the top # filename of the banner shown at the top
banner_file, _ = \ banner_file, _ = get_banner_file(base_dir, nickname, domain, theme)
get_banner_file(base_dir, nickname, domain, theme) banner_path: str = users_path + '/' + banner_file
banner_path = users_path + '/' + banner_file
# these images are pre-loaded to prevent the web page from # these images are pre-loaded to prevent the web page from
# jumping around when rendering # jumping around when rendering
preload_images = [ preload_images: list[str] = [
banner_path, banner_path,
'/icons/showhide.png', '/icons/showhide.png',
'/icons/repeat_hide.png', '/icons/repeat_hide.png',
@ -762,9 +758,8 @@ def html_timeline(default_timeline: str,
'/icons/categoriesrss.png' '/icons/categoriesrss.png'
] ]
instance_title = \ instance_title: str = get_config_param(base_dir, 'instanceTitle')
get_config_param(base_dir, 'instanceTitle') tl_str: str = \
tl_str = \
html_header_with_external_style(css_filename, instance_title, None, html_header_with_external_style(css_filename, instance_title, None,
preload_images) preload_images)
@ -781,7 +776,7 @@ def html_timeline(default_timeline: str,
header_icons_str = '<div class="headericons">' header_icons_str = '<div class="headericons">'
# what screen to go to when a new post is created # what screen to go to when a new post is created
new_post_button_str = \ new_post_button_str: str = \
_html_timeline_new_post(manually_approve_followers, box_name, _html_timeline_new_post(manually_approve_followers, box_name,
users_path, translate, users_path, translate,
access_keys) access_keys)
@ -1018,7 +1013,7 @@ def html_timeline(default_timeline: str,
if nickname in min_images_for_accounts: if nickname in min_images_for_accounts:
minimize_all_images = True minimize_all_images = True
no_seen_posts_filename = account_dir + '/.noSeenPosts' no_seen_posts_filename: str = account_dir + '/.noSeenPosts'
no_seen_posts: bool = False no_seen_posts: bool = False
if is_a_file(no_seen_posts_filename): if is_a_file(no_seen_posts_filename):
no_seen_posts = True no_seen_posts = True
@ -1213,7 +1208,7 @@ def html_individual_share(domain: str, share_id: str,
shares_file_type: str) -> str: shares_file_type: str) -> str:
"""Returns an individual shared item as html """Returns an individual shared item as html
""" """
profile_str = '<div class="container">\n' profile_str: str = '<div class="container">\n'
profile_str += \ profile_str += \
'<p class="share-title">' + shared_item['displayName'] + '</p>\n' '<p class="share-title">' + shared_item['displayName'] + '</p>\n'
if shared_item.get('imageUrl'): if shared_item.get('imageUrl'):
@ -1237,7 +1232,7 @@ def html_individual_share(domain: str, share_id: str,
profile_str += \ profile_str += \
'<b>' + translate['Location'] + ':</b> ' + \ '<b>' + translate['Location'] + ':</b> ' + \
shared_item['location'] + '<br>' shared_item['location'] + '<br>'
contact_title_str = translate['Contact'] contact_title_str: str = translate['Contact']
if shared_item.get('itemPrice') and shared_item.get('itemCurrency'): if shared_item.get('itemPrice') and shared_item.get('itemCurrency'):
if is_float(shared_item['itemPrice']): if is_float(shared_item['itemPrice']):
if float(shared_item['itemPrice']) > 0: if float(shared_item['itemPrice']) > 0:
@ -1247,15 +1242,15 @@ def html_individual_share(domain: str, share_id: str,
shared_item['itemCurrency'] shared_item['itemCurrency']
contact_title_str = translate['Buy'] contact_title_str = translate['Buy']
profile_str += '</p>\n' profile_str += '</p>\n'
sharedesc = shared_item['displayName'] sharedesc: str = shared_item['displayName']
if '<' not in sharedesc and ';' not in sharedesc: if '<' not in sharedesc and ';' not in sharedesc:
if show_contact: if show_contact:
button_style_str = 'button' button_style_str: str = 'button'
if shared_item['category'] == 'accommodation': if shared_item['category'] == 'accommodation':
contact_title_str = translate['Request to stay'] contact_title_str = translate['Request to stay']
button_style_str = 'contactbutton' button_style_str = 'contactbutton'
contact_actor = get_actor_from_post(shared_item) contact_actor: str = get_actor_from_post(shared_item)
profile_str += \ profile_str += \
'<p>' + \ '<p>' + \
'<a href="' + actor + \ '<a href="' + actor + \
@ -1294,9 +1289,9 @@ def _html_shares_timeline(translate: {}, page_number: int, items_per_page: int,
base_dir, domain, nickname, base_dir, domain, nickname,
max_shares_per_account, max_shares_per_account,
shared_items_federated_domains, shares_file_type) shared_items_federated_domains, shares_file_type)
domain_full = get_full_domain(domain, port) domain_full: str = get_full_domain(domain, port)
actor = local_actor_url(http_prefix, nickname, domain_full) actor: str = local_actor_url(http_prefix, nickname, domain_full)
admin_nickname = get_config_param(base_dir, 'admin') admin_nickname: str = get_config_param(base_dir, 'admin')
admin_actor: str = '' admin_actor: str = ''
if admin_nickname: if admin_nickname:
admin_actor = \ admin_actor = \
@ -1318,7 +1313,7 @@ def _html_shares_timeline(translate: {}, page_number: int, items_per_page: int,
'" alt="' + translate['Page up'] + '"></a>\n' + \ '" alt="' + translate['Page up'] + '"></a>\n' + \
' </center>\n' ' </center>\n'
separator_str = html_post_separator(base_dir, None) separator_str: str = html_post_separator(base_dir, None)
ctr: int = 0 ctr: int = 0
is_admin_account: bool = False is_admin_account: bool = False
@ -1409,10 +1404,10 @@ def html_shares(default_timeline: str,
block_nostr: {}) -> str: block_nostr: {}) -> str:
"""Show the shares timeline as html """Show the shares timeline as html
""" """
manually_approve_followers = \ manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain) follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname) artist: bool = is_artist(base_dir, nickname)
show_announces = True show_announces: bool = True
return html_timeline(default_timeline, return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts, recent_posts_cache, max_recent_posts,
@ -1490,10 +1485,10 @@ def html_wanted(default_timeline: str,
block_nostr: {}) -> str: block_nostr: {}) -> str:
"""Show the wanted timeline as html """Show the wanted timeline as html
""" """
manually_approve_followers = \ manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain) follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname) artist: bool = is_artist(base_dir, nickname)
show_announces = True show_announces: bool = True
return html_timeline(default_timeline, return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts, recent_posts_cache, max_recent_posts,
@ -1574,9 +1569,9 @@ def html_inbox(default_timeline: str,
block_nostr: {}) -> str: block_nostr: {}) -> str:
"""Show the inbox as html """Show the inbox as html
""" """
manually_approve_followers = \ manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain) follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname) artist: bool = is_artist(base_dir, nickname)
return html_timeline(default_timeline, return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts, recent_posts_cache, max_recent_posts,
@ -1655,10 +1650,10 @@ def html_bookmarks(default_timeline: str,
block_nostr: {}) -> str: block_nostr: {}) -> str:
"""Show the bookmarks as html """Show the bookmarks as html
""" """
manually_approve_followers = \ manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain) follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname) artist: bool = is_artist(base_dir, nickname)
show_announces = True show_announces: bool = True
return html_timeline(default_timeline, return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts, recent_posts_cache, max_recent_posts,
@ -2192,9 +2187,9 @@ def html_outbox(default_timeline: str,
block_nostr: {}) -> str: block_nostr: {}) -> str:
"""Show the Outbox as html """Show the Outbox as html
""" """
manually_approve_followers = \ manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain) follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname) artist: bool = is_artist(base_dir, nickname)
return html_timeline(default_timeline, return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts, recent_posts_cache, max_recent_posts,
translate, page_number, translate, page_number,

View File

@ -77,11 +77,11 @@ def minimizing_attached_images(base_dir: str, nickname: str, domain: str,
if following_nickname == nickname and following_domain == domain: if following_nickname == nickname and following_domain == domain:
# reminder post # reminder post
return False return False
minimize_filename = \ minimize_filename: str = \
acct_dir(base_dir, nickname, domain) + '/followingMinimizeImages.txt' acct_dir(base_dir, nickname, domain) + '/followingMinimizeImages.txt'
handle = following_nickname + '@' + following_domain handle: str = following_nickname + '@' + following_domain
if not is_a_file(minimize_filename): if not is_a_file(minimize_filename):
following_filename = \ following_filename: str = \
acct_dir(base_dir, nickname, domain) + '/following.txt' acct_dir(base_dir, nickname, domain) + '/following.txt'
if not is_a_file(following_filename): if not is_a_file(following_filename):
return False return False
@ -103,21 +103,22 @@ def get_broken_link_substitute() -> str:
def html_following_list(base_dir: str, following_filename: str) -> str: def html_following_list(base_dir: str, following_filename: str) -> str:
"""Returns a list of handles being followed """Returns a list of handles being followed
""" """
msg = load_string(following_filename, msg: str = \
load_string(following_filename,
'EX: html_following_list unable to read ' + 'EX: html_following_list unable to read ' +
following_filename) following_filename)
if msg: if msg:
following_list = msg.split('\n') following_list: list[str] = msg.split('\n')
following_list.sort() following_list.sort()
if following_list: if following_list:
css_filename = base_dir + '/epicyon-profile.css' css_filename: str = base_dir + '/epicyon-profile.css'
if is_a_file(base_dir + '/epicyon.css'): if is_a_file(base_dir + '/epicyon.css'):
css_filename = base_dir + '/epicyon.css' css_filename = base_dir + '/epicyon.css'
instance_title = \ instance_title: str = \
get_config_param(base_dir, 'instanceTitle') get_config_param(base_dir, 'instanceTitle')
preload_images: list[str] = [] preload_images: list[str] = []
following_list_html = \ following_list_html: str = \
html_header_with_external_style(css_filename, html_header_with_external_style(css_filename,
instance_title, None, instance_title, None,
preload_images) preload_images)
@ -135,11 +136,12 @@ def csv_following_list(following_filename: str,
base_dir: str, nickname: str, domain: str) -> str: base_dir: str, nickname: str, domain: str) -> str:
"""Returns a csv of handles being followed """Returns a csv of handles being followed
""" """
msg = load_string(following_filename, msg: str = \
load_string(following_filename,
'EX: csv_following_list unable to read ' + 'EX: csv_following_list unable to read ' +
following_filename) following_filename)
if msg: if msg:
following_list = msg.split('\n') following_list: list[str] = msg.split('\n')
following_list.sort() following_list.sort()
if following_list: if following_list:
following_list_csv: str = '' following_list_csv: str = ''
@ -147,23 +149,23 @@ def csv_following_list(following_filename: str,
if not following_address: if not following_address:
continue continue
following_nickname = \ following_nickname: str = \
get_nickname_from_actor(following_address) get_nickname_from_actor(following_address)
following_domain, _ = \ following_domain, _ = \
get_domain_from_actor(following_address) get_domain_from_actor(following_address)
announce_is_allowed = \ announce_is_allowed: bool = \
allowed_announce(base_dir, nickname, domain, allowed_announce(base_dir, nickname, domain,
following_nickname, following_nickname,
following_domain) following_domain)
notify_on_new = 'false' notify_on_new: str = 'false'
languages: str = '' languages: str = ''
person_notes = \ person_notes: str = \
get_person_notes(base_dir, nickname, domain, get_person_notes(base_dir, nickname, domain,
following_address) following_address)
if person_notes: if person_notes:
# make notes suitable for csv file # make notes suitable for csv file
replacements = { replacements: dict = {
',': ' ', ',': ' ',
'"': "'", '"': "'",
'\n': '<br>', '\n': '<br>',
@ -189,14 +191,14 @@ def html_hashtag_blocked(base_dir: str, translate: {}) -> str:
"""Show the screen for a blocked hashtag """Show the screen for a blocked hashtag
""" """
blocked_hashtag_form: str = '' blocked_hashtag_form: str = ''
css_filename = base_dir + '/epicyon-suspended.css' css_filename: str = base_dir + '/epicyon-suspended.css'
if is_a_file(base_dir + '/suspended.css'): if is_a_file(base_dir + '/suspended.css'):
css_filename = base_dir + '/suspended.css' css_filename = base_dir + '/suspended.css'
instance_title = \ instance_title: str = \
get_config_param(base_dir, 'instanceTitle') get_config_param(base_dir, 'instanceTitle')
preload_images: list[str] = [] preload_images: list[str] = []
blocked_hashtag_form = \ blocked_hashtag_form: str = \
html_header_with_external_style(css_filename, instance_title, None, html_header_with_external_style(css_filename, instance_title, None,
preload_images) preload_images)
blocked_hashtag_form += '<div><center>\n' blocked_hashtag_form += '<div><center>\n'
@ -275,7 +277,7 @@ def get_show_map_button(post_id: str, translate: {},
map_content: str) -> str: map_content: str) -> str:
"""Returns the markup for a "show map" button """Returns the markup for a "show map" button
""" """
show_map_str = 'Show Map' show_map_str: str = 'Show Map'
if translate.get('Show Map'): if translate.get('Show Map'):
show_map_str = translate['Show Map'] show_map_str = translate['Show Map']
html_str = ' <details><summary class="cw" tabindex="10">' + \ html_str = ' <details><summary class="cw" tabindex="10">' + \
@ -289,7 +291,7 @@ def open_content_warning(text: str, translate: {}) -> str:
"""Opens content warning when replying to a post with a cw """Opens content warning when replying to a post with a cw
so that you can see what you are replying to so that you can see what you are replying to
""" """
text = replace_embedded_map_with_link(text, translate) text: str = replace_embedded_map_with_link(text, translate)
text = text.replace('<details>', '').replace('</details>', '') text = text.replace('<details>', '').replace('</details>', '')
text = text.replace(translate['Show Map'], '', 1) text = text.replace(translate['Show Map'], '', 1)
text = text.replace(translate['SHOW MORE'], '', 1) text = text.replace(translate['SHOW MORE'], '', 1)
@ -303,7 +305,7 @@ def _set_actor_property_url(actor_json: {},
if not actor_json.get('attachment'): if not actor_json.get('attachment'):
actor_json['attachment']: list[dict] = [] actor_json['attachment']: list[dict] = []
property_name_lower = property_name.lower() property_name_lower: str = property_name.lower()
# remove any existing value # remove any existing value
property_found = None property_found = None
@ -397,17 +399,17 @@ def update_avatar_image_cache(signing_priv_key_pem: str,
""" """
if not avatar_url: if not avatar_url:
return None return None
actor_str = actor.replace('/', '-') actor_str: str = actor.replace('/', '-')
avatar_image_path = base_dir + '/cache/avatars/' + actor_str avatar_image_path: str = base_dir + '/cache/avatars/' + actor_str
# try different image types # try different image types
image_formats = image_mime_types_dict() image_formats = image_mime_types_dict()
avatar_image_filename = None avatar_image_filename: str = None
session_headers = None session_headers = None
for im_format, mime_type in image_formats.items(): for im_format, mime_type in image_formats.items():
if avatar_url.endswith('.' + im_format) or \ if avatar_url.endswith('.' + im_format) or \
'.' + im_format + '?' in avatar_url: '.' + im_format + '?' in avatar_url:
session_headers = { session_headers: dict = {
'Accept': 'image/' + mime_type 'Accept': 'image/' + mime_type
} }
avatar_image_filename = avatar_image_path + '.' + im_format avatar_image_filename = avatar_image_path + '.' + im_format
@ -454,7 +456,7 @@ def update_avatar_image_cache(signing_priv_key_pem: str,
except BaseException as ex: except BaseException as ex:
print('EX: Failed to download avatar image: ' + print('EX: Failed to download avatar image: ' +
str(avatar_url) + ' ' + str(ex)) str(avatar_url) + ' ' + str(ex))
prof = 'https://www.w3.org/ns/activitystreams' prof: str = 'https://www.w3.org/ns/activitystreams'
if '/channel/' not in actor or '/accounts/' not in actor: if '/channel/' not in actor or '/accounts/' not in actor:
session_headers = { session_headers = {
'Accept': 'application/activity+json; profile="' + prof + '"' 'Accept': 'application/activity+json; profile="' + prof + '"'
@ -463,13 +465,15 @@ def update_avatar_image_cache(signing_priv_key_pem: str,
session_headers = { session_headers = {
'Accept': 'application/ld+json; profile="' + prof + '"' 'Accept': 'application/ld+json; profile="' + prof + '"'
} }
person_json = \ person_json: dict = \
get_json(signing_priv_key_pem, session, actor, get_json(signing_priv_key_pem, session, actor,
session_headers, None, session_headers, None,
debug, mitm_servers, __version__, http_prefix, None) debug, mitm_servers, __version__, http_prefix, None)
if get_json_valid(person_json): if get_json_valid(person_json):
if not person_json.get('id'): if not person_json.get('id'):
return None return None
if not isinstance(person_json['id'], str):
return None
pub_key, _ = get_actor_public_key_from_id(person_json, None) pub_key, _ = get_actor_public_key_from_id(person_json, None)
if not pub_key: if not pub_key:
return None return None
@ -495,7 +499,7 @@ def update_avatar_image_cache(signing_priv_key_pem: str,
def scheduled_posts_exist(base_dir: str, nickname: str, domain: str) -> bool: def scheduled_posts_exist(base_dir: str, nickname: str, domain: str) -> bool:
"""Returns true if there are posts scheduled to be delivered """Returns true if there are posts scheduled to be delivered
""" """
schedule_index_filename = \ schedule_index_filename: str = \
acct_dir(base_dir, nickname, domain) + '/schedule.index' acct_dir(base_dir, nickname, domain) + '/schedule.index'
if not is_a_file(schedule_index_filename): if not is_a_file(schedule_index_filename):
return False return False
@ -513,27 +517,29 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int,
max_shares_per_account helps to avoid one person dominating the timeline max_shares_per_account helps to avoid one person dominating the timeline
by sharing a large number of things by sharing a large number of things
""" """
all_shares_json = {} all_shares_json: dict = {}
dir_str = data_dir(base_dir) dir_str: str = data_dir(base_dir)
for _, dirs, files in os.walk(dir_str): for _, dirs, files in os.walk(dir_str):
for handle in dirs: for handle in dirs:
if not is_account_dir(handle): if not is_account_dir(handle):
continue continue
account_dir = acct_handle_dir(base_dir, handle) account_dir: str = acct_handle_dir(base_dir, handle)
shares_filename = account_dir + '/' + shares_file_type + '.json' shares_filename: str = \
account_dir + '/' + shares_file_type + '.json'
if not is_a_file(shares_filename): if not is_a_file(shares_filename):
continue continue
shares_json = load_json(shares_filename) shares_json = load_json(shares_filename)
if not shares_json: if not shares_json:
continue continue
account_nickname = handle.split('@')[0] account_nickname: str = handle.split('@')[0]
# Don't include shared items from blocked accounts # Don't include shared items from blocked accounts
if account_nickname != nickname: if account_nickname != nickname:
if is_blocked(base_dir, nickname, domain, if is_blocked(base_dir, nickname, domain,
account_nickname, domain, None, None): account_nickname, domain, None, None):
continue continue
# actor who owns this share # actor who owns this share
owner = actor.split('/users/')[0] + '/users/' + account_nickname owner: str = \
actor.split('/users/')[0] + '/users/' + account_nickname
ctr: int = 0 ctr: int = 0
for item_id, item in shares_json.items(): for item_id, item in shares_json.items():
# assign owner to the item # assign owner to the item
@ -546,9 +552,9 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int,
break break
if shared_items_federated_domains: if shared_items_federated_domains:
if shares_file_type == 'shares': if shares_file_type == 'shares':
catalogs_dir = base_dir + '/cache/catalogs' catalogs_dir: str = base_dir + '/cache/catalogs'
else: else:
catalogs_dir = base_dir + '/cache/wantedItems' catalogs_dir: str = base_dir + '/cache/wantedItems'
if is_a_dir(catalogs_dir): if is_a_dir(catalogs_dir):
for _, dirs, files in os.walk(catalogs_dir): for _, dirs, files in os.walk(catalogs_dir):
for fname in files: for fname in files:
@ -556,11 +562,11 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int,
continue continue
if not fname.endswith('.' + shares_file_type + '.json'): if not fname.endswith('.' + shares_file_type + '.json'):
continue continue
federated_domain = fname.split('.')[0] federated_domain: str = fname.split('.')[0]
if federated_domain not in shared_items_federated_domains: if federated_domain not in shared_items_federated_domains:
continue continue
shares_filename = catalogs_dir + '/' + fname shares_filename: str = catalogs_dir + '/' + fname
shares_json = load_json(shares_filename) shares_json: dict = load_json(shares_filename)
if not shares_json: if not shares_json:
continue continue
ctr: int = 0 ctr: int = 0
@ -568,14 +574,15 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int,
# assign owner to the item # assign owner to the item
if '--shareditems--' not in item_id: if '--shareditems--' not in item_id:
continue continue
share_actor = item_id.split('--shareditems--')[0] share_actor: str = item_id.split('--shareditems--')[0]
replacements = { replacements: dict = {
'___': '://', '___': '://',
'--': '/' '--': '/'
} }
share_actor = \ share_actor: str = \
replace_strings(share_actor, replacements) replace_strings(share_actor, replacements)
share_nickname = get_nickname_from_actor(share_actor) share_nickname: str = \
get_nickname_from_actor(share_actor)
if not share_nickname: if not share_nickname:
continue continue
if is_blocked(base_dir, nickname, domain, if is_blocked(base_dir, nickname, domain,
@ -590,9 +597,10 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int,
break break
break break
# sort the shared items in descending order of publication date # sort the shared items in descending order of publication date
shares_json = OrderedDict(sorted(all_shares_json.items(), reverse=True)) shares_json: dict = \
OrderedDict(sorted(all_shares_json.items(), reverse=True))
last_page: bool = False last_page: bool = False
start_index = items_per_page * page_number start_index: int = items_per_page * page_number
max_index = len(shares_json.items()) max_index = len(shares_json.items())
if max_index < items_per_page: if max_index < items_per_page:
last_page = True last_page = True
@ -629,11 +637,11 @@ def get_shares_collection(actor: str, page_number: int, items_per_page: int,
shared_items_federated_domains, shares_file_type) shared_items_federated_domains, shares_file_type)
if shares_file_type == 'shares': if shares_file_type == 'shares':
share_type = 'offer' share_type: str = 'offer'
collection_name = nickname + "'s Shared Items" collection_name: str = nickname + "'s Shared Items"
else: else:
share_type = 'request' share_type: str = 'request'
collection_name = nickname + "'s Wanted Items" collection_name: str = nickname + "'s Wanted Items"
for share_id, shared_item in shares_json.items(): for share_id, shared_item in shares_json.items():
shared_item['shareId'] = share_id shared_item['shareId'] = share_id
@ -642,7 +650,7 @@ def get_shares_collection(actor: str, page_number: int, items_per_page: int,
if offer_item: if offer_item:
shares_collection.append(offer_item) shares_collection.append(offer_item)
result_json = { result_json: dict = {
"@context": [ "@context": [
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1' 'https://w3id.org/security/v1'
@ -665,6 +673,8 @@ def post_contains_public(post_json_object: {}) -> bool:
return contains_public return contains_public
for to_address in post_json_object['object']['to']: for to_address in post_json_object['object']['to']:
if not isinstance(to_address, str):
continue
if to_address.endswith('#Public') or \ if to_address.endswith('#Public') or \
to_address == 'as:Public' or \ to_address == 'as:Public' or \
to_address == 'Public': to_address == 'Public':
@ -673,6 +683,8 @@ def post_contains_public(post_json_object: {}) -> bool:
if not contains_public: if not contains_public:
if post_json_object['object'].get('cc'): if post_json_object['object'].get('cc'):
for to_address2 in post_json_object['object']['cc']: for to_address2 in post_json_object['object']['cc']:
if not isinstance(to_address2, str):
continue
if to_address2.endswith('#Public') or \ if to_address2.endswith('#Public') or \
to_address2 == 'as:Public' or \ to_address2 == 'as:Public' or \
to_address2 == 'Public': to_address2 == 'Public':
@ -685,7 +697,7 @@ def get_banner_file(base_dir: str,
nickname: str, domain: str, theme: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
"""Gets the image for the timeline banner """Gets the image for the timeline banner
""" """
account_dir = acct_dir(base_dir, nickname, domain) account_dir: str = acct_dir(base_dir, nickname, domain)
banner_file, banner_filename = \ banner_file, banner_filename = \
get_image_file(base_dir, 'banner', account_dir, theme) get_image_file(base_dir, 'banner', account_dir, theme)
return banner_file, banner_filename return banner_file, banner_filename
@ -696,7 +708,7 @@ def get_profile_background_file(base_dir: str,
theme: str) -> (str, str): theme: str) -> (str, str):
"""Gets the image for the profile background """Gets the image for the profile background
""" """
account_dir = acct_dir(base_dir, nickname, domain) account_dir: str = acct_dir(base_dir, nickname, domain)
banner_file, banner_filename = \ banner_file, banner_filename = \
get_image_file(base_dir, 'image', account_dir, theme) get_image_file(base_dir, 'image', account_dir, theme)
return banner_file, banner_filename return banner_file, banner_filename
@ -707,7 +719,7 @@ def get_search_banner_file(base_dir: str,
theme: str) -> (str, str): theme: str) -> (str, str):
"""Gets the image for the search banner """Gets the image for the search banner
""" """
account_dir = acct_dir(base_dir, nickname, domain) account_dir: str = acct_dir(base_dir, nickname, domain)
banner_file, banner_filename = \ banner_file, banner_filename = \
get_image_file(base_dir, 'search_banner', account_dir, theme) get_image_file(base_dir, 'search_banner', account_dir, theme)
return banner_file, banner_filename return banner_file, banner_filename
@ -717,7 +729,7 @@ def get_left_image_file(base_dir: str,
nickname: str, domain: str, theme: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
"""Gets the image for the left column """Gets the image for the left column
""" """
account_dir = acct_dir(base_dir, nickname, domain) account_dir: str = acct_dir(base_dir, nickname, domain)
banner_file, banner_filename = \ banner_file, banner_filename = \
get_image_file(base_dir, 'left_col_image', account_dir, theme) get_image_file(base_dir, 'left_col_image', account_dir, theme)
return banner_file, banner_filename return banner_file, banner_filename
@ -727,7 +739,7 @@ def get_right_image_file(base_dir: str,
nickname: str, domain: str, theme: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
"""Gets the image for the right column """Gets the image for the right column
""" """
account_dir = acct_dir(base_dir, nickname, domain) account_dir: str = acct_dir(base_dir, nickname, domain)
banner_file, banner_filename = \ banner_file, banner_filename = \
get_image_file(base_dir, 'right_col_image', account_dir, theme) get_image_file(base_dir, 'right_col_image', account_dir, theme)
return banner_file, banner_filename return banner_file, banner_filename
@ -737,8 +749,8 @@ def html_header_with_external_style(css_filename: str, instance_title: str,
metadata: str, preload_images: [], metadata: str, preload_images: [],
lang: str = 'en') -> str: lang: str = 'en') -> str:
if metadata is None: if metadata is None:
metadata: str = '' metadata = ''
css_file = '/' + css_filename.split('/')[-1] css_file: str = '/' + css_filename.split('/')[-1]
pwa_theme_color, pwa_theme_background_color = \ pwa_theme_color, pwa_theme_background_color = \
get_pwa_theme_colors(css_filename) get_pwa_theme_colors(css_filename)
preload_images_str: str = '' preload_images_str: str = ''
@ -747,7 +759,7 @@ def html_header_with_external_style(css_filename: str, instance_title: str,
preload_images_str += \ preload_images_str += \
' <link rel="preload" as="image" href="' + \ ' <link rel="preload" as="image" href="' + \
image_path + '">\n' image_path + '">\n'
html_str = \ html_str: str = \
'<!DOCTYPE html>\n' + \ '<!DOCTYPE html>\n' + \
'<!--\n' + \ '<!--\n' + \
'Thankyou for using Epicyon. If you are reading this message then ' + \ 'Thankyou for using Epicyon. If you are reading this message then ' + \
@ -786,7 +798,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
""" """
if not actor_json: if not actor_json:
preload_images: list[str] = [] preload_images: list[str] = []
html_str = \ html_str: str = \
html_header_with_external_style(css_filename, html_header_with_external_style(css_filename,
instance_title, None, instance_title, None,
preload_images, lang) preload_images, lang)
@ -798,7 +810,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
add_comma: str = '' add_comma: str = ''
country_markup: str = '' country_markup: str = ''
if ',' in city: if ',' in city:
country = city.split(',', 1)[1].strip().title() country: str = city.split(',', 1)[1].strip().title()
city = city.split(',', 1)[0] city = city.split(',', 1)[0]
country_markup = \ country_markup = \
' "addressCountry": "' + country + '"\n' ' "addressCountry": "' + country + '"\n'
@ -813,18 +825,22 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
if actor_json.get('hasOccupation'): if actor_json.get('hasOccupation'):
if isinstance(actor_json['hasOccupation'], list): if isinstance(actor_json['hasOccupation'], list):
skills_markup = ' "hasOccupation": [\n' skills_markup = ' "hasOccupation": [\n'
first_entry = True first_entry: bool = True
for skill_dict in actor_json['hasOccupation']: for skill_dict in actor_json['hasOccupation']:
if not skill_dict.get('@type'):
continue
if not isinstance(skill_dict['@type'], str):
continue
if skill_dict['@type'] == 'Role': if skill_dict['@type'] == 'Role':
if not first_entry: if not first_entry:
skills_markup += ',\n' skills_markup += ',\n'
skl = skill_dict['hasOccupation'] skl: str = skill_dict['hasOccupation']
role_name = skl['name'] role_name: str = skl['name']
if not role_name: if not role_name:
role_name = 'member' role_name = 'member'
category = \ category: str = \
skl['occupationalCategory']['codeValue'] skl['occupationalCategory']['codeValue']
category_url = \ category_url: str = \
'https://www.onetonline.org/link/summary/' + category 'https://www.onetonline.org/link/summary/' + category
skills_markup += \ skills_markup += \
' {\n' + \ ' {\n' + \
@ -855,11 +871,11 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
elif skill_dict['@type'] == 'Occupation': elif skill_dict['@type'] == 'Occupation':
if not first_entry: if not first_entry:
skills_markup += ',\n' skills_markup += ',\n'
oc_name = skill_dict['name'] oc_name: str = skill_dict['name']
if not oc_name: if not oc_name:
oc_name = 'member' oc_name = 'member'
skills_list = skill_dict['skills'] skills_list: list[str] = skill_dict['skills']
skills_list_str = '[' skills_list_str: str = '['
for skill_str in skills_list: for skill_str in skills_list:
if skills_list_str != '[': if skills_list_str != '[':
skills_list_str += ', ' skills_list_str += ', '
@ -883,13 +899,13 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
description: str = '' description: str = ''
if actor_json.get('summary'): if actor_json.get('summary'):
description = remove_html(actor_json['summary']) description = remove_html(actor_json['summary'])
name_str = remove_html(actor_json['name']) name_str: str = remove_html(actor_json['name'])
domain_full = actor_json['id'].split('://')[1].split('/')[0] domain_full: str = actor_json['id'].split('://')[1].split('/')[0]
handle = actor_json['preferredUsername'] + '@' + domain_full handle: str = actor_json['preferredUsername'] + '@' + domain_full
url_str = get_url_from_post(actor_json['icon']['url']) url_str: str = get_url_from_post(actor_json['icon']['url'])
icon_url = remove_html(url_str) icon_url: str = remove_html(url_str)
person_markup = \ person_markup: str = \
' "about": {\n' + \ ' "about": {\n' + \
' "@type" : "Person",\n' + \ ' "@type" : "Person",\n' + \
' "name": "' + name_str + '",\n' + \ ' "name": "' + name_str + '",\n' + \
@ -899,7 +915,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
' "url": "' + actor_json['id'] + '"\n' + \ ' "url": "' + actor_json['id'] + '"\n' + \
' },\n' ' },\n'
profile_markup = \ profile_markup: str = \
' <script id="initial-state" type="application/ld+json">\n' + \ ' <script id="initial-state" type="application/ld+json">\n' + \
' {\n' + \ ' {\n' + \
' "@context":"https://schema.org",\n' + \ ' "@context":"https://schema.org",\n' + \
@ -923,10 +939,10 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
' }\n' + \ ' }\n' + \
' </script>\n' ' </script>\n'
description = remove_html(description) description: str = remove_html(description)
url_str = get_url_from_post(actor_json['url']) url_str: str = get_url_from_post(actor_json['url'])
actor2_url = remove_html(url_str) actor2_url: str = remove_html(url_str)
og_metadata = \ og_metadata: str = \
" <meta content=\"profile\" property=\"og:type\" />\n" + \ " <meta content=\"profile\" property=\"og:type\" />\n" + \
" <meta content=\"" + description + \ " <meta content=\"" + description + \
"\" name='description'>\n" + \ "\" name='description'>\n" + \
@ -946,7 +962,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
" <meta content=\"" + handle + \ " <meta content=\"" + handle + \
"\" property=\"profile:username\" />\n" "\" property=\"profile:username\" />\n"
if actor_json.get('attachment'): if actor_json.get('attachment'):
og_tags = ( og_tags: list[str] = (
'email', 'openpgp', 'blog', 'xmpp', 'matrix', 'briar', 'email', 'openpgp', 'blog', 'xmpp', 'matrix', 'briar',
'cwtch', 'languages' 'cwtch', 'languages'
) )
@ -958,10 +974,10 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
if not prop_value_name: if not prop_value_name:
continue continue
if attach_json.get('name'): if attach_json.get('name'):
name = attach_json['name'].lower() name: str = attach_json['name'].lower()
else: else:
name = attach_json['schema:name'].lower() name: str = attach_json['schema:name'].lower()
value = attach_json[prop_value_name] value: str = attach_json[prop_value_name]
for og_tag in og_tags: for og_tag in og_tags:
if name != og_tag: if name != og_tag:
continue continue
@ -970,7 +986,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
"\" property=\"og:" + og_tag + "\" />\n" "\" property=\"og:" + og_tag + "\" />\n"
preload_images: list[str] = [] preload_images: list[str] = []
html_str = \ html_str: str = \
html_header_with_external_style(css_filename, instance_title, html_header_with_external_style(css_filename, instance_title,
og_metadata + profile_markup, og_metadata + profile_markup,
preload_images, lang) preload_images, lang)
@ -983,12 +999,12 @@ def html_header_with_website_markup(css_filename: str, instance_title: str,
"""html header which includes website markup """html header which includes website markup
https://schema.org/WebSite https://schema.org/WebSite
""" """
license_url = 'https://www.gnu.org/licenses/agpl-3.0.rdf' license_url: str = 'https://www.gnu.org/licenses/agpl-3.0.rdf'
# social networking category # social networking category
genre_url = 'http://vocab.getty.edu/aat/300312270' genre_url: str = 'http://vocab.getty.edu/aat/300312270'
website_markup = \ website_markup: str = \
' <script id="initial-state" type="application/ld+json">\n' + \ ' <script id="initial-state" type="application/ld+json">\n' + \
' {\n' + \ ' {\n' + \
' "@context" : "http://schema.org",\n' + \ ' "@context" : "http://schema.org",\n' + \
@ -1014,7 +1030,7 @@ def html_header_with_website_markup(css_filename: str, instance_title: str,
' }\n' + \ ' }\n' + \
' </script>\n' ' </script>\n'
og_metadata = \ og_metadata: str = \
' <meta content="Epicyon hosted on ' + domain + \ ' <meta content="Epicyon hosted on ' + domain + \
'" property="og:site_name" />\n' + \ '" property="og:site_name" />\n' + \
' <meta content="' + http_prefix + '://' + domain + \ ' <meta content="' + http_prefix + '://' + domain + \
@ -1029,7 +1045,7 @@ def html_header_with_website_markup(css_filename: str, instance_title: str,
' <meta content="summary_large_image" property="twitter:card" />\n' ' <meta content="summary_large_image" property="twitter:card" />\n'
preload_images: list[str] = [] preload_images: list[str] = []
html_str = \ html_str: str = \
html_header_with_external_style(css_filename, instance_title, html_header_with_external_style(css_filename, instance_title,
og_metadata + website_markup, og_metadata + website_markup,
preload_images, system_language) preload_images, system_language)
@ -1045,13 +1061,13 @@ def html_header_with_blog_markup(css_filename: str, instance_title: str,
"""html header which includes blog post markup """html header which includes blog post markup
https://schema.org/BlogPosting https://schema.org/BlogPosting
""" """
author_url = local_actor_url(http_prefix, nickname, domain) author_url: str = local_actor_url(http_prefix, nickname, domain)
about_url = http_prefix + '://' + domain + '/about.html' about_url: str = http_prefix + '://' + domain + '/about.html'
# license for content on the site may be different from # license for content on the site may be different from
# the software license # the software license
blog_markup = \ blog_markup: str = \
' <script id="initial-state" type="application/ld+json">\n' + \ ' <script id="initial-state" type="application/ld+json">\n' + \
' {\n' + \ ' {\n' + \
' "@context" : "http://schema.org",\n' + \ ' "@context" : "http://schema.org",\n' + \
@ -1074,7 +1090,7 @@ def html_header_with_blog_markup(css_filename: str, instance_title: str,
' }\n' + \ ' }\n' + \
' </script>\n' ' </script>\n'
og_metadata = \ og_metadata: str = \
' <meta property="og:locale" content="' + \ ' <meta property="og:locale" content="' + \
system_language + '" />\n' + \ system_language + '" />\n' + \
' <meta property="og:type" content="article" />\n' + \ ' <meta property="og:type" content="article" />\n' + \
@ -1088,7 +1104,7 @@ def html_header_with_blog_markup(css_filename: str, instance_title: str,
modified + '" />\n' modified + '" />\n'
preload_images: list[str] = [] preload_images: list[str] = []
html_str = \ html_str: str = \
html_header_with_external_style(css_filename, instance_title, html_header_with_external_style(css_filename, instance_title,
og_metadata + blog_markup, og_metadata + blog_markup,
preload_images, system_language) preload_images, system_language)
@ -1096,7 +1112,7 @@ def html_header_with_blog_markup(css_filename: str, instance_title: str,
def html_footer() -> str: def html_footer() -> str:
html_str = ' </body>\n' html_str: str = ' </body>\n'
html_str += '</html>\n' html_str += '</html>\n'
return html_str return html_str
@ -1108,7 +1124,7 @@ def load_individual_post_as_html_from_cache(base_dir: str,
return the html text return the html text
This is much quicker than generating the html from the json object This is much quicker than generating the html from the json object
""" """
cached_post_filename = \ cached_post_filename: str = \
get_cached_post_filename(base_dir, nickname, domain, post_json_object) get_cached_post_filename(base_dir, nickname, domain, post_json_object)
post_html: str = '' post_html: str = ''
@ -1146,7 +1162,7 @@ def add_emoji_to_display_name(session, base_dir: str, http_prefix: str,
'</p>': '' '</p>': ''
} }
display_name = replace_strings(display_name, replacements) display_name = replace_strings(display_name, replacements)
emoji_tags = {} emoji_tags: dict = {}
# print('TAG: display_name before tags: ' + display_name) # print('TAG: display_name before tags: ' + display_name)
display_name = \ display_name = \
add_html_tags(base_dir, http_prefix, add_html_tags(base_dir, http_prefix,
@ -1176,7 +1192,7 @@ def add_emoji_to_display_name(session, base_dir: str, http_prefix: str,
if '://' in display_name: if '://' in display_name:
break break
emoji_str = display_name.split(':')[1] emoji_str = display_name.split(':')[1]
prev_display_name = display_name prev_display_name: str = display_name
display_name = display_name.replace(':' + emoji_str + ':', '').strip() display_name = display_name.replace(':' + emoji_str + ':', '').strip()
if prev_display_name == display_name: if prev_display_name == display_name:
break break
@ -1193,8 +1209,8 @@ def _is_image_mime_type(mime_type: str) -> bool:
return True return True
if not mime_type.startswith('image/'): if not mime_type.startswith('image/'):
return False return False
extensions = get_image_extensions() extensions: list[str] = get_image_extensions()
ext = mime_type.split('/')[1] ext: str = mime_type.split('/')[1]
if ext in extensions: if ext in extensions:
return True return True
return False return False
@ -1205,8 +1221,8 @@ def _is_video_mime_type(mime_type: str) -> bool:
""" """
if not mime_type.startswith('video/'): if not mime_type.startswith('video/'):
return False return False
extensions = get_video_extensions() extensions: list[str] = get_video_extensions()
ext = mime_type.split('/')[1] ext: str = mime_type.split('/')[1]
if ext in extensions: if ext in extensions:
return True return True
return False return False
@ -1219,8 +1235,8 @@ def _is_audio_mime_type(mime_type: str) -> bool:
return True return True
if not mime_type.startswith('audio/'): if not mime_type.startswith('audio/'):
return False return False
extensions = get_audio_extensions() extensions: list[str] = get_audio_extensions()
ext = mime_type.split('/')[1] ext: str = mime_type.split('/')[1]
if ext in extensions: if ext in extensions:
return True return True
return False return False
@ -1231,13 +1247,13 @@ def _is_attached_image(attachment_url: str) -> bool:
""" """
if '.' not in attachment_url: if '.' not in attachment_url:
return False return False
image_ext = get_image_extensions() image_ext: list[str] = get_image_extensions()
ext = attachment_url.split('.')[-1] ext: str = attachment_url.split('.')[-1]
if ext in image_ext: if ext in image_ext:
return True return True
if '/' in attachment_url: if '/' in attachment_url:
# this might still be an image, but without a file extension # this might still be an image, but without a file extension
last_part = attachment_url.split('/')[-1] last_part: str = attachment_url.split('/')[-1]
if '.' not in last_part: if '.' not in last_part:
return True return True
return False return False
@ -1248,10 +1264,10 @@ def _is_attached_video(attachment_filename: str) -> bool:
""" """
if '.' not in attachment_filename: if '.' not in attachment_filename:
return False return False
video_ext = ( video_ext: list[str] = (
'mp4', 'webm', 'ogv' 'mp4', 'webm', 'ogv'
) )
ext = attachment_filename.split('.')[-1] ext: str = attachment_filename.split('.')[-1]
if ext in video_ext: if ext in video_ext:
return True return True
return False return False
@ -1260,8 +1276,8 @@ def _is_attached_video(attachment_filename: str) -> bool:
def _is_nsfw(content: str) -> bool: def _is_nsfw(content: str) -> bool:
"""Does the given content indicate nsfw? """Does the given content indicate nsfw?
""" """
content_lower = content.lower() content_lower: str = content.lower()
nsfw_tags = ( nsfw_tags: list[str] = (
'nsfw', 'porn', 'pr0n', 'explicit', 'lewd', 'nsfw', 'porn', 'pr0n', 'explicit', 'lewd',
'nude', 'boob', 'erotic', 'sex' 'nude', 'boob', 'erotic', 'sex'
) )