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:
"""Edit theme settings
"""
theme_filename = base_dir + '/theme/' + theme_name + '/theme.json'
theme_json = {}
theme_filename: str = base_dir + '/theme/' + theme_name + '/theme.json'
theme_json: dict = {}
if is_a_file(theme_filename):
theme_json = load_json(theme_filename)
# 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):
custom_theme_params = load_json(custom_variables_file)
custom_theme_params: str = load_json(custom_variables_file)
if custom_theme_params:
for variable_name, value in custom_theme_params.items():
theme_json[variable_name] = value
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'):
css_filename = base_dir + '/epicyon.css'
banner_file, _ = \
get_banner_file(base_dir, nickname, domain, theme_name)
banner_path = '/users/' + nickname + '/' + banner_file
banner_path: str = '/users/' + nickname + '/' + banner_file
instance_title = \
get_config_param(base_dir, 'instanceTitle')
preload_images = [banner_path]
theme_form = \
instance_title: str = get_config_param(base_dir, 'instanceTitle')
preload_images: list[str] = [banner_path]
theme_form: str = \
html_header_with_external_style(css_filename, instance_title, None,
preload_images)
theme_form += \
@ -216,8 +215,8 @@ def html_theme_designer(base_dir: str,
theme_form += ' <form method="POST" action="' + \
'/users/' + nickname + '/changeThemeSettings">\n'
reset_key = access_keys['menuLogout']
submit_key = access_keys['submitButton']
reset_key: str = access_keys['menuLogout']
submit_key: str = access_keys['submitButton']
theme_form += \
' <center>\n' + \
' <button type="submit" class="button" ' + \
@ -230,13 +229,13 @@ def html_theme_designer(base_dir: str,
contrast_warning: str = ''
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'):
foreground = theme_json['main-fg-color']
contrast = color_contrast(background, foreground)
foreground: str = theme_json['main-fg-color']
contrast: float = color_contrast(background, foreground)
if contrast:
if contrast < 4.5:
contrast_warning = '⚠️ '
contrast_warning: str = '⚠️ '
theme_form += \
' <center><label class="labels">' + \
contrast_warning + '<b>' + \
@ -250,15 +249,15 @@ def html_theme_designer(base_dir: str,
table_str += ' </colgroup>\n'
table_str += ' <tbody>\n'
font_str = ' <div class="container">\n' + table_str
color_str = ' <div class="container">\n' + table_str
dimension_str = ' <div class="container">\n' + table_str
switch_str = ' <div class="container">\n' + table_str
font_str: str = ' <div class="container">\n' + table_str
color_str: str = ' <div class="container">\n' + table_str
dimension_str: str = ' <div class="container">\n' + table_str
switch_str: str = ' <div class="container">\n' + table_str
for variable_name, value in theme_json.items():
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_label = variable_name_str
variable_name_label: str = variable_name_str
if contrast_warning:
if variable_name in ('main-bg-color', 'main-fg-color'):
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'
switch_str += ' </table>\n </div>\n'
theme_formats = '.zip, .gz'
export_import_str = ' <div class="container">\n'
theme_formats: str = '.zip, .gz'
export_import_str: str = ' <div class="container">\n'
export_import_str += \
' <label class="labels">' + \
translate['Import Theme'] + '</label>\n'
@ -362,7 +361,7 @@ def _relative_luminance(color: str) -> float:
"""
color = color.lstrip('#')
rgb = list(int(color[i:i+2], 16) for i in (0, 2, 4))
srgb = (
srgb: list[float] = (
rgb[0] / 255.0,
rgb[1] / 255.0,
rgb[2] / 255.0
@ -397,8 +396,8 @@ def color_contrast(background: str, foreground: str) -> float:
foreground = color_to_hex[foreground]
else:
return None
background_luminance = _relative_luminance(background)
foreground_luminance = _relative_luminance(foreground)
background_luminance: float = _relative_luminance(background)
foreground_luminance: float = _relative_luminance(foreground)
if background_luminance > foreground_luminance:
return (0.05 + background_luminance) / (0.05 + foreground_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:
return
time_diff = int((time.time() - timeline_start_time) * 1000)
time_diff: int = int((time.time() - timeline_start_time) * 1000)
if time_diff > 100:
print('TIMELINE TIMING ' +
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
"""
# 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):
language = \
get_config_param(base_dir, 'language')
language: str = get_config_param(base_dir, 'language')
if not language:
language = 'en'
theme_name = \
get_config_param(base_dir, 'theme')
default_filename = None
theme_name: str = get_config_param(base_dir, 'theme')
default_filename: str = None
if theme_name:
default_filename = \
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
if is_a_file(help_filename):
instance_title = \
get_config_param(base_dir, 'instanceTitle')
instance_title: str = get_config_param(base_dir, 'instanceTitle')
if not instance_title:
instance_title = 'Epicyon'
help_text = \
help_text: str = \
load_string(help_filename,
'EX: _get_help_for_timeline unable to read ' +
help_filename)
if help_text:
if dangerous_markup(help_text, False, []):
return ''
help_text = help_text.replace('INSTANCE', instance_title)
help_text: str = help_text.replace('INSTANCE', instance_title)
return '<div class="container">\n' + \
markdown_to_html(help_text) + '\n' + \
'</div>\n'
@ -285,22 +282,22 @@ def _html_timeline_keyboard(moderator: bool, text_mode_banner: str,
access_keys: {}, translate: {}) -> str:
"""Returns html for timeline keyboard navigation
"""
calendar_str = translate['Calendar']
calendar_str: str = translate['Calendar']
if new_calendar_event:
calendar_str = '<strong>' + calendar_str + '</strong>'
dm_str = translate['DM']
dm_str: str = translate['DM']
if new_dm:
dm_str = '<strong>' + dm_str + '</strong>'
replies_str = translate['Replies']
replies_str: str = translate['Replies']
if new_reply:
replies_str = '<strong>' + replies_str + '</strong>'
shares_str = translate['Shares']
shares_str: str = translate['Shares']
if new_share:
shares_str = '<strong>' + shares_str + '</strong>'
wanted_str = translate['Wanted']
wanted_str: str = translate['Wanted']
if new_wanted:
wanted_str = '<strong>' + wanted_str + '</strong>'
menu_profile = \
menu_profile: str = \
html_hide_from_screen_reader('👤') + ' ' + \
translate['Switch to profile view']
menu_inbox = \
@ -333,7 +330,7 @@ def _html_timeline_keyboard(moderator: bool, text_mode_banner: str,
translate['Create a new post']
menu_moderation = \
html_hide_from_screen_reader('⚡️') + ' ' + translate['Mod']
nav_links = {
nav_links: dict = {
menu_profile: '/users/' + nickname,
menu_inbox: users_path + '/inbox#timelineposts',
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_links: users_path + '/linksmobile'
}
nav_access_keys = {}
nav_access_keys: dict = {}
for variable_name, key in access_keys.items():
if not locals().get(variable_name):
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
"""
# end of timeline-posts
tl_str = ' </div>\n'
tl_str: str = ' </div>\n'
# end of column-center
tl_str += ' </td>\n'
@ -421,11 +418,11 @@ def page_number_buttons(users_path: str, box_name: str,
num_str: str = ''
for page in range(min_page_number, max_page_number):
if num_str:
separator_str = \
separator_str: str = \
'<label class="pageslistDash">────</label>'
num_str += html_hide_from_screen_reader(separator_str)
aria_page_str: str = ''
page_str = ' ' + str(page) + ' '
page_str: str = ' ' + str(page) + ' '
curr_page_str: str = ''
if page == page_number:
page_str = '[<mark>' + str(page) + '</mark>]'
@ -495,17 +492,17 @@ def html_timeline(default_timeline: str,
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?
new_calendar_event: bool = False
calendar_image = 'calendar.png'
calendar_path = '/calendar'
calendar_file = account_dir + '/.newCalendar'
calendar_image: str = 'calendar.png'
calendar_path: str = '/calendar'
calendar_file: str = account_dir + '/.newCalendar'
if is_a_file(calendar_file):
new_calendar_event = True
calendar_image = 'calendar_notify.png'
calendar_path_str = \
calendar_path_str: str = \
load_string(calendar_file,
'EX: html_timeline unable to read ' +
calendar_file)
@ -517,7 +514,7 @@ def html_timeline(default_timeline: str,
# should the DM button be highlighted?
new_dm: bool = False
dm_file = account_dir + '/.newDM'
dm_file: str = account_dir + '/.newDM'
if is_a_file(dm_file):
new_dm = True
if box_name == 'dm':
@ -526,7 +523,7 @@ def html_timeline(default_timeline: str,
# should the Replies button be highlighted?
new_reply: bool = False
reply_file = account_dir + '/.newReply'
reply_file: str = account_dir + '/.newReply'
if is_a_file(reply_file):
new_reply = True
if box_name == 'tlreplies':
@ -535,7 +532,7 @@ def html_timeline(default_timeline: str,
# should the Shares button be highlighted?
new_share: bool = False
new_share_file = account_dir + '/.newShare'
new_share_file: str = account_dir + '/.newShare'
if is_a_file(new_share_file):
new_share = True
if box_name == 'tlshares':
@ -545,7 +542,7 @@ def html_timeline(default_timeline: str,
# should the Wanted button be highlighted?
new_wanted: bool = False
new_wanted_file = account_dir + '/.newWanted'
new_wanted_file: str = account_dir + '/.newWanted'
if is_a_file(new_wanted_file):
new_wanted = True
if box_name == 'tlwanted':
@ -555,7 +552,7 @@ def html_timeline(default_timeline: str,
# should the Moderation/reports button be highlighted?
new_report: bool = False
new_report_file = account_dir + '/.newReport'
new_report_file: str = account_dir + '/.newReport'
if is_a_file(new_report_file):
new_report = True
if box_name == 'moderation':
@ -565,7 +562,7 @@ def html_timeline(default_timeline: str,
# show polls/votes?
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):
show_vote_posts = False
@ -574,7 +571,7 @@ def html_timeline(default_timeline: str,
separator_str = html_post_separator(base_dir, None)
# 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'):
css_filename = base_dir + '/epicyon.css'
@ -647,16 +644,16 @@ def html_timeline(default_timeline: str,
bookmarks_button = 'buttonselected'
# 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
actor = http_prefix + '://' + full_domain + users_path
users_path: str = '/users/' + nickname
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
follow_approvals: str = ''
follow_requests_filename = \
follow_requests_filename: str = \
acct_dir(base_dir, nickname, domain) + '/followrequests.txt'
if is_a_file(follow_requests_filename):
follow_requests_list: list[str] = \
@ -731,13 +728,12 @@ def html_timeline(default_timeline: str,
'<span>' + translate['Bookmarks'] + '</span></button></a>'
# filename of the banner shown at the top
banner_file, _ = \
get_banner_file(base_dir, nickname, domain, theme)
banner_path = users_path + '/' + banner_file
banner_file, _ = get_banner_file(base_dir, nickname, domain, theme)
banner_path: str = users_path + '/' + banner_file
# these images are pre-loaded to prevent the web page from
# jumping around when rendering
preload_images = [
preload_images: list[str] = [
banner_path,
'/icons/showhide.png',
'/icons/repeat_hide.png',
@ -762,9 +758,8 @@ def html_timeline(default_timeline: str,
'/icons/categoriesrss.png'
]
instance_title = \
get_config_param(base_dir, 'instanceTitle')
tl_str = \
instance_title: str = get_config_param(base_dir, 'instanceTitle')
tl_str: str = \
html_header_with_external_style(css_filename, instance_title, None,
preload_images)
@ -781,7 +776,7 @@ def html_timeline(default_timeline: str,
header_icons_str = '<div class="headericons">'
# 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,
users_path, translate,
access_keys)
@ -1018,7 +1013,7 @@ def html_timeline(default_timeline: str,
if nickname in min_images_for_accounts:
minimize_all_images = True
no_seen_posts_filename = account_dir + '/.noSeenPosts'
no_seen_posts_filename: str = account_dir + '/.noSeenPosts'
no_seen_posts: bool = False
if is_a_file(no_seen_posts_filename):
no_seen_posts = True
@ -1213,7 +1208,7 @@ def html_individual_share(domain: str, share_id: str,
shares_file_type: str) -> str:
"""Returns an individual shared item as html
"""
profile_str = '<div class="container">\n'
profile_str: str = '<div class="container">\n'
profile_str += \
'<p class="share-title">' + shared_item['displayName'] + '</p>\n'
if shared_item.get('imageUrl'):
@ -1237,7 +1232,7 @@ def html_individual_share(domain: str, share_id: str,
profile_str += \
'<b>' + translate['Location'] + ':</b> ' + \
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 is_float(shared_item['itemPrice']):
if float(shared_item['itemPrice']) > 0:
@ -1247,15 +1242,15 @@ def html_individual_share(domain: str, share_id: str,
shared_item['itemCurrency']
contact_title_str = translate['Buy']
profile_str += '</p>\n'
sharedesc = shared_item['displayName']
sharedesc: str = shared_item['displayName']
if '<' not in sharedesc and ';' not in sharedesc:
if show_contact:
button_style_str = 'button'
button_style_str: str = 'button'
if shared_item['category'] == 'accommodation':
contact_title_str = translate['Request to stay']
button_style_str = 'contactbutton'
contact_actor = get_actor_from_post(shared_item)
contact_actor: str = get_actor_from_post(shared_item)
profile_str += \
'<p>' + \
'<a href="' + actor + \
@ -1294,9 +1289,9 @@ def _html_shares_timeline(translate: {}, page_number: int, items_per_page: int,
base_dir, domain, nickname,
max_shares_per_account,
shared_items_federated_domains, shares_file_type)
domain_full = get_full_domain(domain, port)
actor = local_actor_url(http_prefix, nickname, domain_full)
admin_nickname = get_config_param(base_dir, 'admin')
domain_full: str = get_full_domain(domain, port)
actor: str = local_actor_url(http_prefix, nickname, domain_full)
admin_nickname: str = get_config_param(base_dir, 'admin')
admin_actor: str = ''
if admin_nickname:
admin_actor = \
@ -1318,7 +1313,7 @@ def _html_shares_timeline(translate: {}, page_number: int, items_per_page: int,
'" alt="' + translate['Page up'] + '"></a>\n' + \
' </center>\n'
separator_str = html_post_separator(base_dir, None)
separator_str: str = html_post_separator(base_dir, None)
ctr: int = 0
is_admin_account: bool = False
@ -1409,10 +1404,10 @@ def html_shares(default_timeline: str,
block_nostr: {}) -> str:
"""Show the shares timeline as html
"""
manually_approve_followers = \
manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname)
show_announces = True
artist: bool = is_artist(base_dir, nickname)
show_announces: bool = True
return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts,
@ -1490,10 +1485,10 @@ def html_wanted(default_timeline: str,
block_nostr: {}) -> str:
"""Show the wanted timeline as html
"""
manually_approve_followers = \
manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname)
show_announces = True
artist: bool = is_artist(base_dir, nickname)
show_announces: bool = True
return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts,
@ -1574,9 +1569,9 @@ def html_inbox(default_timeline: str,
block_nostr: {}) -> str:
"""Show the inbox as html
"""
manually_approve_followers = \
manually_approve_followers: bool = \
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,
recent_posts_cache, max_recent_posts,
@ -1655,10 +1650,10 @@ def html_bookmarks(default_timeline: str,
block_nostr: {}) -> str:
"""Show the bookmarks as html
"""
manually_approve_followers = \
manually_approve_followers: bool = \
follower_approval_active(base_dir, nickname, domain)
artist = is_artist(base_dir, nickname)
show_announces = True
artist: bool = is_artist(base_dir, nickname)
show_announces: bool = True
return html_timeline(default_timeline,
recent_posts_cache, max_recent_posts,
@ -2192,9 +2187,9 @@ def html_outbox(default_timeline: str,
block_nostr: {}) -> str:
"""Show the Outbox as html
"""
manually_approve_followers = \
manually_approve_followers: bool = \
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,
recent_posts_cache, max_recent_posts,
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:
# reminder post
return False
minimize_filename = \
minimize_filename: str = \
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):
following_filename = \
following_filename: str = \
acct_dir(base_dir, nickname, domain) + '/following.txt'
if not is_a_file(following_filename):
return False
@ -103,21 +103,22 @@ def get_broken_link_substitute() -> str:
def html_following_list(base_dir: str, following_filename: str) -> str:
"""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 ' +
following_filename)
if msg:
following_list = msg.split('\n')
following_list: list[str] = msg.split('\n')
following_list.sort()
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'):
css_filename = base_dir + '/epicyon.css'
instance_title = \
instance_title: str = \
get_config_param(base_dir, 'instanceTitle')
preload_images: list[str] = []
following_list_html = \
following_list_html: str = \
html_header_with_external_style(css_filename,
instance_title, None,
preload_images)
@ -135,11 +136,12 @@ def csv_following_list(following_filename: str,
base_dir: str, nickname: str, domain: str) -> str:
"""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 ' +
following_filename)
if msg:
following_list = msg.split('\n')
following_list: list[str] = msg.split('\n')
following_list.sort()
if following_list:
following_list_csv: str = ''
@ -147,23 +149,23 @@ def csv_following_list(following_filename: str,
if not following_address:
continue
following_nickname = \
following_nickname: str = \
get_nickname_from_actor(following_address)
following_domain, _ = \
get_domain_from_actor(following_address)
announce_is_allowed = \
announce_is_allowed: bool = \
allowed_announce(base_dir, nickname, domain,
following_nickname,
following_domain)
notify_on_new = 'false'
notify_on_new: str = 'false'
languages: str = ''
person_notes = \
person_notes: str = \
get_person_notes(base_dir, nickname, domain,
following_address)
if person_notes:
# make notes suitable for csv file
replacements = {
replacements: dict = {
',': ' ',
'"': "'",
'\n': '<br>',
@ -189,14 +191,14 @@ def html_hashtag_blocked(base_dir: str, translate: {}) -> str:
"""Show the screen for a blocked hashtag
"""
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'):
css_filename = base_dir + '/suspended.css'
instance_title = \
instance_title: str = \
get_config_param(base_dir, 'instanceTitle')
preload_images: list[str] = []
blocked_hashtag_form = \
blocked_hashtag_form: str = \
html_header_with_external_style(css_filename, instance_title, None,
preload_images)
blocked_hashtag_form += '<div><center>\n'
@ -275,7 +277,7 @@ def get_show_map_button(post_id: str, translate: {},
map_content: str) -> str:
"""Returns the markup for a "show map" button
"""
show_map_str = 'Show Map'
show_map_str: str = 'Show Map'
if translate.get('Show Map'):
show_map_str = translate['Show Map']
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
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(translate['Show Map'], '', 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'):
actor_json['attachment']: list[dict] = []
property_name_lower = property_name.lower()
property_name_lower: str = property_name.lower()
# remove any existing value
property_found = None
@ -397,17 +399,17 @@ def update_avatar_image_cache(signing_priv_key_pem: str,
"""
if not avatar_url:
return None
actor_str = actor.replace('/', '-')
avatar_image_path = base_dir + '/cache/avatars/' + actor_str
actor_str: str = actor.replace('/', '-')
avatar_image_path: str = base_dir + '/cache/avatars/' + actor_str
# try different image types
image_formats = image_mime_types_dict()
avatar_image_filename = None
avatar_image_filename: str = None
session_headers = None
for im_format, mime_type in image_formats.items():
if avatar_url.endswith('.' + im_format) or \
'.' + im_format + '?' in avatar_url:
session_headers = {
session_headers: dict = {
'Accept': 'image/' + mime_type
}
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:
print('EX: Failed to download avatar image: ' +
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:
session_headers = {
'Accept': 'application/activity+json; profile="' + prof + '"'
@ -463,13 +465,15 @@ def update_avatar_image_cache(signing_priv_key_pem: str,
session_headers = {
'Accept': 'application/ld+json; profile="' + prof + '"'
}
person_json = \
person_json: dict = \
get_json(signing_priv_key_pem, session, actor,
session_headers, None,
debug, mitm_servers, __version__, http_prefix, None)
if get_json_valid(person_json):
if not person_json.get('id'):
return None
if not isinstance(person_json['id'], str):
return None
pub_key, _ = get_actor_public_key_from_id(person_json, None)
if not pub_key:
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:
"""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'
if not is_a_file(schedule_index_filename):
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
by sharing a large number of things
"""
all_shares_json = {}
dir_str = data_dir(base_dir)
all_shares_json: dict = {}
dir_str: str = data_dir(base_dir)
for _, dirs, files in os.walk(dir_str):
for handle in dirs:
if not is_account_dir(handle):
continue
account_dir = acct_handle_dir(base_dir, handle)
shares_filename = account_dir + '/' + shares_file_type + '.json'
account_dir: str = acct_handle_dir(base_dir, handle)
shares_filename: str = \
account_dir + '/' + shares_file_type + '.json'
if not is_a_file(shares_filename):
continue
shares_json = load_json(shares_filename)
if not shares_json:
continue
account_nickname = handle.split('@')[0]
account_nickname: str = handle.split('@')[0]
# Don't include shared items from blocked accounts
if account_nickname != nickname:
if is_blocked(base_dir, nickname, domain,
account_nickname, domain, None, None):
continue
# 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
for item_id, item in shares_json.items():
# assign owner to the item
@ -546,9 +552,9 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int,
break
if shared_items_federated_domains:
if shares_file_type == 'shares':
catalogs_dir = base_dir + '/cache/catalogs'
catalogs_dir: str = base_dir + '/cache/catalogs'
else:
catalogs_dir = base_dir + '/cache/wantedItems'
catalogs_dir: str = base_dir + '/cache/wantedItems'
if is_a_dir(catalogs_dir):
for _, dirs, files in os.walk(catalogs_dir):
for fname in files:
@ -556,11 +562,11 @@ def shares_timeline_json(actor: str, page_number: int, items_per_page: int,
continue
if not fname.endswith('.' + shares_file_type + '.json'):
continue
federated_domain = fname.split('.')[0]
federated_domain: str = fname.split('.')[0]
if federated_domain not in shared_items_federated_domains:
continue
shares_filename = catalogs_dir + '/' + fname
shares_json = load_json(shares_filename)
shares_filename: str = catalogs_dir + '/' + fname
shares_json: dict = load_json(shares_filename)
if not shares_json:
continue
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
if '--shareditems--' not in item_id:
continue
share_actor = item_id.split('--shareditems--')[0]
replacements = {
share_actor: str = item_id.split('--shareditems--')[0]
replacements: dict = {
'___': '://',
'--': '/'
}
share_actor = \
share_actor: str = \
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:
continue
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
# 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
start_index = items_per_page * page_number
start_index: int = items_per_page * page_number
max_index = len(shares_json.items())
if max_index < items_per_page:
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)
if shares_file_type == 'shares':
share_type = 'offer'
collection_name = nickname + "'s Shared Items"
share_type: str = 'offer'
collection_name: str = nickname + "'s Shared Items"
else:
share_type = 'request'
collection_name = nickname + "'s Wanted Items"
share_type: str = 'request'
collection_name: str = nickname + "'s Wanted Items"
for share_id, shared_item in shares_json.items():
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:
shares_collection.append(offer_item)
result_json = {
result_json: dict = {
"@context": [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
@ -665,6 +673,8 @@ def post_contains_public(post_json_object: {}) -> bool:
return contains_public
for to_address in post_json_object['object']['to']:
if not isinstance(to_address, str):
continue
if to_address.endswith('#Public') or \
to_address == 'as:Public' or \
to_address == 'Public':
@ -673,6 +683,8 @@ def post_contains_public(post_json_object: {}) -> bool:
if not contains_public:
if post_json_object['object'].get('cc'):
for to_address2 in post_json_object['object']['cc']:
if not isinstance(to_address2, str):
continue
if to_address2.endswith('#Public') or \
to_address2 == 'as:Public' or \
to_address2 == 'Public':
@ -685,7 +697,7 @@ def get_banner_file(base_dir: str,
nickname: str, domain: str, theme: str) -> (str, str):
"""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 = \
get_image_file(base_dir, 'banner', account_dir, theme)
return banner_file, banner_filename
@ -696,7 +708,7 @@ def get_profile_background_file(base_dir: str,
theme: str) -> (str, str):
"""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 = \
get_image_file(base_dir, 'image', account_dir, theme)
return banner_file, banner_filename
@ -707,7 +719,7 @@ def get_search_banner_file(base_dir: str,
theme: str) -> (str, str):
"""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 = \
get_image_file(base_dir, 'search_banner', account_dir, theme)
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):
"""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 = \
get_image_file(base_dir, 'left_col_image', account_dir, theme)
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):
"""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 = \
get_image_file(base_dir, 'right_col_image', account_dir, theme)
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: [],
lang: str = 'en') -> str:
if metadata is None:
metadata: str = ''
css_file = '/' + css_filename.split('/')[-1]
metadata = ''
css_file: str = '/' + css_filename.split('/')[-1]
pwa_theme_color, pwa_theme_background_color = \
get_pwa_theme_colors(css_filename)
preload_images_str: str = ''
@ -747,7 +759,7 @@ def html_header_with_external_style(css_filename: str, instance_title: str,
preload_images_str += \
' <link rel="preload" as="image" href="' + \
image_path + '">\n'
html_str = \
html_str: str = \
'<!DOCTYPE html>\n' + \
'<!--\n' + \
'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:
preload_images: list[str] = []
html_str = \
html_str: str = \
html_header_with_external_style(css_filename,
instance_title, None,
preload_images, lang)
@ -798,7 +810,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
add_comma: str = ''
country_markup: str = ''
if ',' in city:
country = city.split(',', 1)[1].strip().title()
country: str = city.split(',', 1)[1].strip().title()
city = city.split(',', 1)[0]
country_markup = \
' "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 isinstance(actor_json['hasOccupation'], list):
skills_markup = ' "hasOccupation": [\n'
first_entry = True
first_entry: bool = True
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 not first_entry:
skills_markup += ',\n'
skl = skill_dict['hasOccupation']
role_name = skl['name']
skl: str = skill_dict['hasOccupation']
role_name: str = skl['name']
if not role_name:
role_name = 'member'
category = \
category: str = \
skl['occupationalCategory']['codeValue']
category_url = \
category_url: str = \
'https://www.onetonline.org/link/summary/' + category
skills_markup += \
' {\n' + \
@ -855,11 +871,11 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
elif skill_dict['@type'] == 'Occupation':
if not first_entry:
skills_markup += ',\n'
oc_name = skill_dict['name']
oc_name: str = skill_dict['name']
if not oc_name:
oc_name = 'member'
skills_list = skill_dict['skills']
skills_list_str = '['
skills_list: list[str] = skill_dict['skills']
skills_list_str: str = '['
for skill_str in skills_list:
if skills_list_str != '[':
skills_list_str += ', '
@ -883,13 +899,13 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
description: str = ''
if actor_json.get('summary'):
description = remove_html(actor_json['summary'])
name_str = remove_html(actor_json['name'])
domain_full = actor_json['id'].split('://')[1].split('/')[0]
handle = actor_json['preferredUsername'] + '@' + domain_full
name_str: str = remove_html(actor_json['name'])
domain_full: str = actor_json['id'].split('://')[1].split('/')[0]
handle: str = actor_json['preferredUsername'] + '@' + domain_full
url_str = get_url_from_post(actor_json['icon']['url'])
icon_url = remove_html(url_str)
person_markup = \
url_str: str = get_url_from_post(actor_json['icon']['url'])
icon_url: str = remove_html(url_str)
person_markup: str = \
' "about": {\n' + \
' "@type" : "Person",\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' + \
' },\n'
profile_markup = \
profile_markup: str = \
' <script id="initial-state" type="application/ld+json">\n' + \
' {\n' + \
' "@context":"https://schema.org",\n' + \
@ -923,10 +939,10 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
' }\n' + \
' </script>\n'
description = remove_html(description)
url_str = get_url_from_post(actor_json['url'])
actor2_url = remove_html(url_str)
og_metadata = \
description: str = remove_html(description)
url_str: str = get_url_from_post(actor_json['url'])
actor2_url: str = remove_html(url_str)
og_metadata: str = \
" <meta content=\"profile\" property=\"og:type\" />\n" + \
" <meta content=\"" + description + \
"\" name='description'>\n" + \
@ -946,7 +962,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
" <meta content=\"" + handle + \
"\" property=\"profile:username\" />\n"
if actor_json.get('attachment'):
og_tags = (
og_tags: list[str] = (
'email', 'openpgp', 'blog', 'xmpp', 'matrix', 'briar',
'cwtch', 'languages'
)
@ -958,10 +974,10 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
if not prop_value_name:
continue
if attach_json.get('name'):
name = attach_json['name'].lower()
name: str = attach_json['name'].lower()
else:
name = attach_json['schema:name'].lower()
value = attach_json[prop_value_name]
name: str = attach_json['schema:name'].lower()
value: str = attach_json[prop_value_name]
for og_tag in og_tags:
if name != og_tag:
continue
@ -970,7 +986,7 @@ def html_header_with_person_markup(css_filename: str, instance_title: str,
"\" property=\"og:" + og_tag + "\" />\n"
preload_images: list[str] = []
html_str = \
html_str: str = \
html_header_with_external_style(css_filename, instance_title,
og_metadata + profile_markup,
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
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
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' + \
' {\n' + \
' "@context" : "http://schema.org",\n' + \
@ -1014,7 +1030,7 @@ def html_header_with_website_markup(css_filename: str, instance_title: str,
' }\n' + \
' </script>\n'
og_metadata = \
og_metadata: str = \
' <meta content="Epicyon hosted on ' + domain + \
'" property="og:site_name" />\n' + \
' <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'
preload_images: list[str] = []
html_str = \
html_str: str = \
html_header_with_external_style(css_filename, instance_title,
og_metadata + website_markup,
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
https://schema.org/BlogPosting
"""
author_url = local_actor_url(http_prefix, nickname, domain)
about_url = http_prefix + '://' + domain + '/about.html'
author_url: str = local_actor_url(http_prefix, nickname, domain)
about_url: str = http_prefix + '://' + domain + '/about.html'
# license for content on the site may be different from
# the software license
blog_markup = \
blog_markup: str = \
' <script id="initial-state" type="application/ld+json">\n' + \
' {\n' + \
' "@context" : "http://schema.org",\n' + \
@ -1074,7 +1090,7 @@ def html_header_with_blog_markup(css_filename: str, instance_title: str,
' }\n' + \
' </script>\n'
og_metadata = \
og_metadata: str = \
' <meta property="og:locale" content="' + \
system_language + '" />\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'
preload_images: list[str] = []
html_str = \
html_str: str = \
html_header_with_external_style(css_filename, instance_title,
og_metadata + blog_markup,
preload_images, system_language)
@ -1096,7 +1112,7 @@ def html_header_with_blog_markup(css_filename: str, instance_title: str,
def html_footer() -> str:
html_str = ' </body>\n'
html_str: str = ' </body>\n'
html_str += '</html>\n'
return html_str
@ -1108,7 +1124,7 @@ def load_individual_post_as_html_from_cache(base_dir: str,
return the html text
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)
post_html: str = ''
@ -1146,7 +1162,7 @@ def add_emoji_to_display_name(session, base_dir: str, http_prefix: str,
'</p>': ''
}
display_name = replace_strings(display_name, replacements)
emoji_tags = {}
emoji_tags: dict = {}
# print('TAG: display_name before tags: ' + display_name)
display_name = \
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:
break
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()
if prev_display_name == display_name:
break
@ -1193,8 +1209,8 @@ def _is_image_mime_type(mime_type: str) -> bool:
return True
if not mime_type.startswith('image/'):
return False
extensions = get_image_extensions()
ext = mime_type.split('/')[1]
extensions: list[str] = get_image_extensions()
ext: str = mime_type.split('/')[1]
if ext in extensions:
return True
return False
@ -1205,8 +1221,8 @@ def _is_video_mime_type(mime_type: str) -> bool:
"""
if not mime_type.startswith('video/'):
return False
extensions = get_video_extensions()
ext = mime_type.split('/')[1]
extensions: list[str] = get_video_extensions()
ext: str = mime_type.split('/')[1]
if ext in extensions:
return True
return False
@ -1219,8 +1235,8 @@ def _is_audio_mime_type(mime_type: str) -> bool:
return True
if not mime_type.startswith('audio/'):
return False
extensions = get_audio_extensions()
ext = mime_type.split('/')[1]
extensions: list[str] = get_audio_extensions()
ext: str = mime_type.split('/')[1]
if ext in extensions:
return True
return False
@ -1231,13 +1247,13 @@ def _is_attached_image(attachment_url: str) -> bool:
"""
if '.' not in attachment_url:
return False
image_ext = get_image_extensions()
ext = attachment_url.split('.')[-1]
image_ext: list[str] = get_image_extensions()
ext: str = attachment_url.split('.')[-1]
if ext in image_ext:
return True
if '/' in attachment_url:
# 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:
return True
return False
@ -1248,10 +1264,10 @@ def _is_attached_video(attachment_filename: str) -> bool:
"""
if '.' not in attachment_filename:
return False
video_ext = (
video_ext: list[str] = (
'mp4', 'webm', 'ogv'
)
ext = attachment_filename.split('.')[-1]
ext: str = attachment_filename.split('.')[-1]
if ext in video_ext:
return True
return False
@ -1260,8 +1276,8 @@ def _is_attached_video(attachment_filename: str) -> bool:
def _is_nsfw(content: str) -> bool:
"""Does the given content indicate nsfw?
"""
content_lower = content.lower()
nsfw_tags = (
content_lower: str = content.lower()
nsfw_tags: list[str] = (
'nsfw', 'porn', 'pr0n', 'explicit', 'lewd',
'nude', 'boob', 'erotic', 'sex'
)