__filename__ = "webapp_profile.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
import os
from pprint import pprint
from webfinger import webfingerHandle
from utils import get_display_name
from utils import is_group_account
from utils import has_object_dict
from utils import get_occupation_name
from utils import get_locked_account
from utils import get_full_domain
from utils import is_artist
from utils import is_dormant
from utils import get_nickname_from_actor
from utils import get_domain_from_actor
from utils import is_system_account
from utils import remove_html
from utils import load_json
from utils import get_config_param
from utils import get_image_formats
from utils import acct_dir
from utils import get_supported_languages
from utils import local_actor_url
from utils import get_reply_interval_hours
from languages import getActorLanguages
from skills import getSkills
from theme import getThemesList
from person import person_box_json
from person import getActorJson
from person import getPersonAvatarUrl
from posts import getPersonBox
from posts import is_moderator
from posts import parseUserFeed
from posts import isCreateInsideAnnounce
from donate import get_donation_url
from donate import get_website
from xmpp import get_xmpp_address
from matrix import get_matrix_address
from ssb import get_ssb_address
from pgp import get_email_address
from pgp import get_pgp_fingerprint
from pgp import get_pgp_pub_key
from enigma import get_enigma_pub_key
from tox import get_tox_address
from briar import get_briar_address
from jami import get_jami_address
from cwtch import get_cwtch_address
from filters import isFiltered
from follow import isFollowerOfPerson
from follow import getFollowerDomains
from webapp_frontscreen import htmlFrontScreen
from webapp_utils import htmlKeyboardNavigation
from webapp_utils import htmlHideFromScreenReader
from webapp_utils import scheduledPostsExist
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlHeaderWithPersonMarkup
from webapp_utils import htmlFooter
from webapp_utils import addEmojiToDisplayName
from webapp_utils import getBannerFile
from webapp_utils import htmlPostSeparator
from webapp_utils import editCheckBox
from webapp_utils import editTextField
from webapp_utils import editTextArea
from webapp_utils import beginEditSection
from webapp_utils import endEditSection
from blog import get_blog_address
from webapp_post import individualPostAsHtml
from webapp_timeline import htmlIndividualShare
from blocking import get_cw_list_variable
def _validProfilePreviewPost(post_json_object: {},
personUrl: str) -> (bool, {}):
"""Returns true if the given post should appear on a person/group profile
after searching for a handle
"""
isAnnouncedFeedItem = False
if isCreateInsideAnnounce(post_json_object):
isAnnouncedFeedItem = True
post_json_object = post_json_object['object']
if not post_json_object.get('type'):
return False, None
if post_json_object['type'] == 'Create':
if not has_object_dict(post_json_object):
return False, None
if post_json_object['type'] != 'Create' and \
post_json_object['type'] != 'Announce':
if post_json_object['type'] != 'Note' and \
post_json_object['type'] != 'Page':
return False, None
if not post_json_object.get('to'):
return False, None
if not post_json_object.get('id'):
return False, None
# wrap in create
cc = []
if post_json_object.get('cc'):
cc = post_json_object['cc']
newPostJsonObject = {
'object': post_json_object,
'to': post_json_object['to'],
'cc': cc,
'id': post_json_object['id'],
'actor': personUrl,
'type': 'Create'
}
post_json_object = newPostJsonObject
if not post_json_object.get('actor'):
return False, None
if not isAnnouncedFeedItem:
if post_json_object['actor'] != personUrl and \
post_json_object['object']['type'] != 'Page':
return False, None
return True, post_json_object
def htmlProfileAfterSearch(css_cache: {},
recent_posts_cache: {}, max_recent_posts: int,
translate: {},
base_dir: str, path: str, http_prefix: str,
nickname: str, domain: str, port: int,
profileHandle: str,
session, cached_webfingers: {}, person_cache: {},
debug: bool, project_version: str,
yt_replace_domain: str,
twitter_replacement_domain: str,
show_published_date_only: bool,
defaultTimeline: str,
peertube_instances: [],
allow_local_network_access: bool,
theme_name: str,
accessKeys: {},
system_language: str,
max_like_count: int,
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str) -> str:
"""Show a profile page after a search for a fediverse address
"""
http = False
gnunet = False
if http_prefix == 'http':
http = True
elif http_prefix == 'gnunet':
gnunet = True
profile_json, asHeader = \
getActorJson(domain, profileHandle, http, gnunet, debug, False,
signing_priv_key_pem, session)
if not profile_json:
return None
personUrl = profile_json['id']
searchDomain, searchPort = get_domain_from_actor(personUrl)
if not searchDomain:
return None
searchNickname = get_nickname_from_actor(personUrl)
if not searchNickname:
return None
searchDomainFull = get_full_domain(searchDomain, searchPort)
profileStr = ''
cssFilename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
cssFilename = base_dir + '/epicyon.css'
isGroup = False
if profile_json.get('type'):
if profile_json['type'] == 'Group':
isGroup = True
avatarUrl = ''
if profile_json.get('icon'):
if profile_json['icon'].get('url'):
avatarUrl = profile_json['icon']['url']
if not avatarUrl:
avatarUrl = getPersonAvatarUrl(base_dir, personUrl,
person_cache, True)
displayName = searchNickname
if profile_json.get('name'):
displayName = profile_json['name']
lockedAccount = get_locked_account(profile_json)
if lockedAccount:
displayName += '🔒'
movedTo = ''
if profile_json.get('movedTo'):
movedTo = profile_json['movedTo']
if '"' in movedTo:
movedTo = movedTo.split('"')[1]
displayName += ' ⌂'
followsYou = \
isFollowerOfPerson(base_dir,
nickname, domain,
searchNickname,
searchDomainFull)
profileDescription = ''
if profile_json.get('summary'):
profileDescription = profile_json['summary']
outboxUrl = None
if not profile_json.get('outbox'):
if debug:
pprint(profile_json)
print('DEBUG: No outbox found')
return None
outboxUrl = profile_json['outbox']
# profileBackgroundImage = ''
# if profile_json.get('image'):
# if profile_json['image'].get('url'):
# profileBackgroundImage = profile_json['image']['url']
# url to return to
backUrl = path
if not backUrl.endswith('/inbox'):
backUrl += '/inbox'
profileDescriptionShort = profileDescription
if '\n' in profileDescription:
if len(profileDescription.split('\n')) > 2:
profileDescriptionShort = ''
else:
if ' ' in profileDescription:
if len(profileDescription.split(' ')) > 2:
profileDescriptionShort = ''
# keep the profile description short
if len(profileDescriptionShort) > 256:
profileDescriptionShort = ''
# remove formatting from profile description used on title
avatarDescription = ''
if profile_json.get('summary'):
if isinstance(profile_json['summary'], str):
avatarDescription = \
profile_json['summary'].replace(' ', '\n')
avatarDescription = avatarDescription.replace('
', '')
avatarDescription = avatarDescription.replace('
', '')
if '<' in avatarDescription:
avatarDescription = remove_html(avatarDescription)
imageUrl = ''
if profile_json.get('image'):
if profile_json['image'].get('url'):
imageUrl = profile_json['image']['url']
alsoKnownAs = None
if profile_json.get('alsoKnownAs'):
alsoKnownAs = profile_json['alsoKnownAs']
joinedDate = None
if profile_json.get('published'):
if 'T' in profile_json['published']:
joinedDate = profile_json['published']
profileStr = \
_getProfileHeaderAfterSearch(base_dir,
nickname, defaultTimeline,
searchNickname,
searchDomainFull,
translate,
displayName, followsYou,
profileDescriptionShort,
avatarUrl, imageUrl,
movedTo, profile_json['id'],
alsoKnownAs, accessKeys,
joinedDate)
domain_full = get_full_domain(domain, port)
followIsPermitted = True
if not profile_json.get('followers'):
# no followers collection specified within actor
followIsPermitted = False
elif searchNickname == 'news' and searchDomainFull == domain_full:
# currently the news actor is not something you can follow
followIsPermitted = False
elif searchNickname == nickname and searchDomainFull == domain_full:
# don't follow yourself!
followIsPermitted = False
if followIsPermitted:
followStr = 'Follow'
if isGroup:
followStr = 'Join'
profileStr += \
'\n' + \
' \n' + \
'
\n'
else:
profileStr += \
'\n' + \
' \n' + \
'
\n'
userFeed = \
parseUserFeed(signing_priv_key_pem,
session, outboxUrl, asHeader, project_version,
http_prefix, domain, debug)
if userFeed:
i = 0
for item in userFeed:
showItem, post_json_object = \
_validProfilePreviewPost(item, personUrl)
if not showItem:
continue
profileStr += \
individualPostAsHtml(signing_priv_key_pem,
True, recent_posts_cache,
max_recent_posts,
translate, None, base_dir,
session, cached_webfingers, person_cache,
nickname, domain, port,
post_json_object, avatarUrl,
False, False,
http_prefix, project_version, 'inbox',
yt_replace_domain,
twitter_replacement_domain,
show_published_date_only,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count,
False, False, False, False, False, False,
cw_lists, lists_enabled)
i += 1
if i >= 8:
break
instanceTitle = \
get_config_param(base_dir, 'instanceTitle')
return htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None) + \
profileStr + htmlFooter()
def _getProfileHeader(base_dir: str, http_prefix: str,
nickname: str, domain: str,
domain_full: str, translate: {},
defaultTimeline: str,
displayName: str,
avatarDescription: str,
profileDescriptionShort: str,
loginButton: str, avatarUrl: str,
theme: str, movedTo: str,
alsoKnownAs: [],
pinnedContent: str,
accessKeys: {},
joinedDate: str,
occupationName: str) -> str:
"""The header of the profile screen, containing background
image and avatar
"""
htmlStr = \
'\n\n \n\n'
return htmlStr
def _getProfileHeaderAfterSearch(base_dir: str,
nickname: str, defaultTimeline: str,
searchNickname: str,
searchDomainFull: str,
translate: {},
displayName: str,
followsYou: bool,
profileDescriptionShort: str,
avatarUrl: str, imageUrl: str,
movedTo: str, actor: str,
alsoKnownAs: [],
accessKeys: {},
joinedDate: str) -> str:
"""The header of a searched for handle, containing background
image and avatar
"""
if not imageUrl:
imageUrl = '/defaultprofilebackground'
htmlStr = \
'\n\n \n\n'
return htmlStr
def htmlProfile(signing_priv_key_pem: str,
rss_icon_at_top: bool,
css_cache: {}, icons_as_buttons: bool,
defaultTimeline: str,
recent_posts_cache: {}, max_recent_posts: int,
translate: {}, project_version: str,
base_dir: str, http_prefix: str, authorized: bool,
profile_json: {}, selected: str,
session, cached_webfingers: {}, person_cache: {},
yt_replace_domain: str,
twitter_replacement_domain: str,
show_published_date_only: bool,
newswire: {}, theme: str, dormant_months: int,
peertube_instances: [],
allow_local_network_access: bool,
text_mode_banner: str,
debug: bool, accessKeys: {}, city: str,
system_language: str, max_like_count: int,
shared_items_federated_domains: [],
extraJson: {}, pageNumber: int,
maxItemsPerPage: int,
cw_lists: {}, lists_enabled: str,
content_license_url: str) -> str:
"""Show the profile page as html
"""
nickname = profile_json['preferredUsername']
if not nickname:
return ""
if is_system_account(nickname):
return htmlFrontScreen(signing_priv_key_pem,
rss_icon_at_top,
css_cache, icons_as_buttons,
defaultTimeline,
recent_posts_cache, max_recent_posts,
translate, project_version,
base_dir, http_prefix, authorized,
profile_json, selected,
session, cached_webfingers, person_cache,
yt_replace_domain,
twitter_replacement_domain,
show_published_date_only,
newswire, theme, extraJson,
allow_local_network_access, accessKeys,
system_language, max_like_count,
shared_items_federated_domains, None,
pageNumber, maxItemsPerPage, cw_lists,
lists_enabled)
domain, port = get_domain_from_actor(profile_json['id'])
if not domain:
return ""
displayName = \
addEmojiToDisplayName(session, base_dir, http_prefix,
nickname, domain,
profile_json['name'], True)
domain_full = get_full_domain(domain, port)
profileDescription = \
addEmojiToDisplayName(session, base_dir, http_prefix,
nickname, domain,
profile_json['summary'], False)
postsButton = 'button'
followingButton = 'button'
followersButton = 'button'
rolesButton = 'button'
skillsButton = 'button'
sharesButton = 'button'
wantedButton = 'button'
if selected == 'posts':
postsButton = 'buttonselected'
elif selected == 'following':
followingButton = 'buttonselected'
elif selected == 'followers':
followersButton = 'buttonselected'
elif selected == 'roles':
rolesButton = 'buttonselected'
elif selected == 'skills':
skillsButton = 'buttonselected'
elif selected == 'shares':
sharesButton = 'buttonselected'
elif selected == 'wanted':
wantedButton = 'buttonselected'
loginButton = ''
followApprovalsSection = ''
followApprovals = False
editProfileStr = ''
logoutStr = ''
actor = profile_json['id']
usersPath = '/users/' + actor.split('/users/')[1]
donateSection = ''
donateUrl = get_donation_url(profile_json)
websiteUrl = get_website(profile_json, translate)
blogAddress = get_blog_address(profile_json)
EnigmaPubKey = get_enigma_pub_key(profile_json)
PGPpubKey = get_pgp_pub_key(profile_json)
PGPfingerprint = get_pgp_fingerprint(profile_json)
emailAddress = get_email_address(profile_json)
xmppAddress = get_xmpp_address(profile_json)
matrixAddress = get_matrix_address(profile_json)
ssbAddress = get_ssb_address(profile_json)
toxAddress = get_tox_address(profile_json)
briarAddress = get_briar_address(profile_json)
jamiAddress = get_jami_address(profile_json)
cwtchAddress = get_cwtch_address(profile_json)
if donateUrl or websiteUrl or xmppAddress or matrixAddress or \
ssbAddress or toxAddress or briarAddress or \
jamiAddress or cwtchAddress or PGPpubKey or EnigmaPubKey or \
PGPfingerprint or emailAddress:
donateSection = '\n'
donateSection += '
\n'
if donateUrl and not is_system_account(nickname):
donateSection += \
' ' + translate['Donate'] + \
'
\n'
if websiteUrl:
donateSection += \
'' + translate['Website'] + ': ' + websiteUrl + '
\n'
if emailAddress:
donateSection += \
'' + translate['Email'] + ': ' + emailAddress + '
\n'
if blogAddress:
donateSection += \
'Blog: ' + blogAddress + '
\n'
if xmppAddress:
donateSection += \
'' + translate['XMPP'] + ': ' + xmppAddress + '
\n'
if matrixAddress:
donateSection += \
'' + translate['Matrix'] + ': ' + matrixAddress + '
\n'
if ssbAddress:
donateSection += \
'SSB: ' + \
ssbAddress + '
\n'
if toxAddress:
donateSection += \
'Tox: ' + \
toxAddress + '
\n'
if briarAddress:
if briarAddress.startswith('briar://'):
donateSection += \
'' + \
briarAddress + '
\n'
else:
donateSection += \
'briar://' + \
briarAddress + '
\n'
if jamiAddress:
donateSection += \
'Jami: ' + \
jamiAddress + '
\n'
if cwtchAddress:
donateSection += \
'Cwtch: ' + \
cwtchAddress + '
\n'
if EnigmaPubKey:
donateSection += \
'Enigma: ' + \
EnigmaPubKey + '
\n'
if PGPfingerprint:
donateSection += \
'PGP: ' + \
PGPfingerprint.replace('\n', ' ') + '
\n'
if PGPpubKey:
donateSection += \
'' + PGPpubKey.replace('\n', ' ') + '
\n'
donateSection += ' \n'
donateSection += '
\n'
if authorized:
editProfileStr = \
'' + \
' \n'
logoutStr = \
'' + \
' \n'
# are there any follow requests?
followRequestsFilename = \
acct_dir(base_dir, nickname, domain) + '/followrequests.txt'
if os.path.isfile(followRequestsFilename):
with open(followRequestsFilename, 'r') as f:
for line in f:
if len(line) > 0:
followApprovals = True
followersButton = 'buttonhighlighted'
if selected == 'followers':
followersButton = 'buttonselectedhighlighted'
break
if selected == 'followers':
if followApprovals:
currFollowerDomains = \
getFollowerDomains(base_dir, nickname, domain)
with open(followRequestsFilename, 'r') as f:
for followerHandle in f:
if len(line) > 0:
followerHandle = followerHandle.replace('\n', '')
if '://' in followerHandle:
followerActor = followerHandle
else:
nick = followerHandle.split('@')[0]
dom = followerHandle.split('@')[1]
followerActor = \
local_actor_url(http_prefix, nick, dom)
# is this a new domain?
# if so then append a new instance indicator
followerDomain, _ = \
get_domain_from_actor(followerActor)
newFollowerDomain = ''
if followerDomain not in currFollowerDomains:
newFollowerDomain = ' ✨'
basePath = '/users/' + nickname
followApprovalsSection += ''
profileDescriptionShort = profileDescription
if '\n' in profileDescription:
if len(profileDescription.split('\n')) > 2:
profileDescriptionShort = ''
else:
if ' ' in profileDescription:
if len(profileDescription.split(' ')) > 2:
profileDescriptionShort = ''
profileDescription = profileDescription.replace(' ', '\n')
# keep the profile description short
if len(profileDescriptionShort) > 256:
profileDescriptionShort = ''
# remove formatting from profile description used on title
avatarDescription = ''
if profile_json.get('summary'):
avatarDescription = profile_json['summary'].replace(' ', '\n')
avatarDescription = avatarDescription.replace('', '')
avatarDescription = avatarDescription.replace('
', '')
movedTo = ''
if profile_json.get('movedTo'):
movedTo = profile_json['movedTo']
if '"' in movedTo:
movedTo = movedTo.split('"')[1]
alsoKnownAs = None
if profile_json.get('alsoKnownAs'):
alsoKnownAs = profile_json['alsoKnownAs']
joinedDate = None
if profile_json.get('published'):
if 'T' in profile_json['published']:
joinedDate = profile_json['published']
occupationName = None
if profile_json.get('hasOccupation'):
occupationName = get_occupation_name(profile_json)
avatarUrl = profile_json['icon']['url']
# use alternate path for local avatars to avoid any caching issues
if '://' + domain_full + '/system/accounts/avatars/' in avatarUrl:
avatarUrl = \
avatarUrl.replace('://' + domain_full +
'/system/accounts/avatars/',
'://' + domain_full + '/users/')
# get pinned post content
accountDir = acct_dir(base_dir, nickname, domain)
pinnedFilename = accountDir + '/pinToProfile.txt'
pinnedContent = None
if os.path.isfile(pinnedFilename):
with open(pinnedFilename, 'r') as pinFile:
pinnedContent = pinFile.read()
profileHeaderStr = \
_getProfileHeader(base_dir, http_prefix,
nickname, domain,
domain_full, translate,
defaultTimeline, displayName,
avatarDescription,
profileDescriptionShort,
loginButton, avatarUrl, theme,
movedTo, alsoKnownAs,
pinnedContent, accessKeys,
joinedDate, occupationName)
# keyboard navigation
userPathStr = '/users/' + nickname
deft = defaultTimeline
isGroup = False
followersStr = translate['Followers']
if is_group_account(base_dir, nickname, domain):
isGroup = True
followersStr = translate['Members']
menuTimeline = \
htmlHideFromScreenReader('🏠') + ' ' + \
translate['Switch to timeline view']
menuEdit = \
htmlHideFromScreenReader('✍') + ' ' + translate['Edit']
if not isGroup:
menuFollowing = \
htmlHideFromScreenReader('👥') + ' ' + translate['Following']
menuFollowers = \
htmlHideFromScreenReader('👪') + ' ' + followersStr
if not isGroup:
menuRoles = \
htmlHideFromScreenReader('🤚') + ' ' + translate['Roles']
menuSkills = \
htmlHideFromScreenReader('🛠') + ' ' + translate['Skills']
menuLogout = \
htmlHideFromScreenReader('❎') + ' ' + translate['Logout']
navLinks = {
menuTimeline: userPathStr + '/' + deft,
menuEdit: userPathStr + '/editprofile',
menuFollowing: userPathStr + '/following#timeline',
menuFollowers: userPathStr + '/followers#timeline',
menuRoles: userPathStr + '/roles#timeline',
menuSkills: userPathStr + '/skills#timeline',
menuLogout: '/logout'
}
if is_artist(base_dir, nickname):
menuThemeDesigner = \
htmlHideFromScreenReader('🎨') + ' ' + translate['Theme Designer']
navLinks[menuThemeDesigner] = userPathStr + '/themedesigner'
navAccessKeys = {}
for variableName, key in accessKeys.items():
if not locals().get(variableName):
continue
navAccessKeys[locals()[variableName]] = key
profileStr = htmlKeyboardNavigation(text_mode_banner,
navLinks, navAccessKeys)
profileStr += profileHeaderStr + donateSection
profileStr += ''
# start of #timeline
profileStr += '\n'
profileStr += followApprovalsSection
cssFilename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
cssFilename = base_dir + '/epicyon.css'
licenseStr = \
'
' + \
' '
if selected == 'posts':
profileStr += \
_htmlProfilePosts(recent_posts_cache, max_recent_posts,
translate,
base_dir, http_prefix, authorized,
nickname, domain, port,
session, cached_webfingers, person_cache,
project_version,
yt_replace_domain,
twitter_replacement_domain,
show_published_date_only,
peertube_instances,
allow_local_network_access,
theme, system_language,
max_like_count,
signing_priv_key_pem,
cw_lists, lists_enabled) + licenseStr
if not isGroup:
if selected == 'following':
profileStr += \
_htmlProfileFollowing(translate, base_dir, http_prefix,
authorized, nickname,
domain, port, session,
cached_webfingers,
person_cache, extraJson,
project_version, ["unfollow"], selected,
usersPath, pageNumber, maxItemsPerPage,
dormant_months, debug,
signing_priv_key_pem)
if selected == 'followers':
profileStr += \
_htmlProfileFollowing(translate, base_dir, http_prefix,
authorized, nickname,
domain, port, session,
cached_webfingers,
person_cache, extraJson,
project_version, ["block"],
selected, usersPath, pageNumber,
maxItemsPerPage, dormant_months, debug,
signing_priv_key_pem)
if not isGroup:
if selected == 'roles':
profileStr += \
_htmlProfileRoles(translate, nickname, domain_full,
extraJson)
elif selected == 'skills':
profileStr += \
_htmlProfileSkills(translate, nickname, domain_full, extraJson)
# elif selected == 'shares':
# profileStr += \
# _htmlProfileShares(actor, translate,
# nickname, domain_full,
# extraJson, 'shares') + licenseStr
# elif selected == 'wanted':
# profileStr += \
# _htmlProfileShares(actor, translate,
# nickname, domain_full,
# extraJson, 'wanted') + licenseStr
# end of #timeline
profileStr += '
'
instanceTitle = \
get_config_param(base_dir, 'instanceTitle')
profileStr = \
htmlHeaderWithPersonMarkup(cssFilename, instanceTitle,
profile_json, city,
content_license_url) + \
profileStr + htmlFooter()
return profileStr
def _htmlProfilePosts(recent_posts_cache: {}, max_recent_posts: int,
translate: {},
base_dir: str, http_prefix: str,
authorized: bool,
nickname: str, domain: str, port: int,
session, cached_webfingers: {}, person_cache: {},
project_version: str,
yt_replace_domain: str,
twitter_replacement_domain: str,
show_published_date_only: bool,
peertube_instances: [],
allow_local_network_access: bool,
theme_name: str, system_language: str,
max_like_count: int,
signing_priv_key_pem: str,
cw_lists: {}, lists_enabled: str) -> str:
"""Shows posts on the profile screen
These should only be public posts
"""
separatorStr = htmlPostSeparator(base_dir, None)
profileStr = ''
maxItems = 4
ctr = 0
currPage = 1
boxName = 'outbox'
while ctr < maxItems and currPage < 4:
outboxFeedPathStr = \
'/users/' + nickname + '/' + boxName + '?page=' + \
str(currPage)
outboxFeed = \
person_box_json({}, session, base_dir, domain,
port,
outboxFeedPathStr,
http_prefix,
10, boxName,
authorized, 0, False, 0)
if not outboxFeed:
break
if len(outboxFeed['orderedItems']) == 0:
break
for item in outboxFeed['orderedItems']:
if item['type'] == 'Create':
postStr = \
individualPostAsHtml(signing_priv_key_pem,
True, recent_posts_cache,
max_recent_posts,
translate, None,
base_dir, session, cached_webfingers,
person_cache,
nickname, domain, port, item,
None, True, False,
http_prefix, project_version, 'inbox',
yt_replace_domain,
twitter_replacement_domain,
show_published_date_only,
peertube_instances,
allow_local_network_access,
theme_name, system_language,
max_like_count,
False, False, False,
True, False, False,
cw_lists, lists_enabled)
if postStr:
profileStr += postStr + separatorStr
ctr += 1
if ctr >= maxItems:
break
currPage += 1
return profileStr
def _htmlProfileFollowing(translate: {}, base_dir: str, http_prefix: str,
authorized: bool,
nickname: str, domain: str, port: int,
session, cached_webfingers: {}, person_cache: {},
followingJson: {}, project_version: str,
buttons: [],
feedName: str, actor: str,
pageNumber: int,
maxItemsPerPage: int,
dormant_months: int, debug: bool,
signing_priv_key_pem: str) -> str:
"""Shows following on the profile screen
"""
profileStr = ''
if authorized and pageNumber:
if authorized and pageNumber > 1:
# page up arrow
profileStr += \
' \n' + \
' \n' + \
' \n'
for followingActor in followingJson['orderedItems']:
# is this a dormant followed account?
dormant = False
if authorized and feedName == 'following':
dormant = \
is_dormant(base_dir, nickname, domain, followingActor,
dormant_months)
profileStr += \
_individualFollowAsHtml(signing_priv_key_pem,
translate, base_dir, session,
cached_webfingers, person_cache,
domain, followingActor,
authorized, nickname,
http_prefix, project_version, dormant,
debug, buttons)
if authorized and maxItemsPerPage and pageNumber:
if len(followingJson['orderedItems']) >= maxItemsPerPage:
# page down arrow
profileStr += \
' \n' + \
' \n' + \
' \n'
return profileStr
def _htmlProfileRoles(translate: {}, nickname: str, domain: str,
rolesList: []) -> str:
"""Shows roles on the profile screen
"""
profileStr = ''
profileStr += \
'\n
\n'
for role in rolesList:
if translate.get(role):
profileStr += '
' + translate[role] + ' \n'
else:
profileStr += '' + role + ' \n'
profileStr += ' \n'
if len(profileStr) == 0:
profileStr += \
'@' + nickname + '@' + domain + ' has no roles assigned
\n'
else:
profileStr = '' + profileStr + '
\n'
return profileStr
def _htmlProfileSkills(translate: {}, nickname: str, domain: str,
skillsJson: {}) -> str:
"""Shows skills on the profile screen
"""
profileStr = ''
for skill, level in skillsJson.items():
profileStr += \
'\n \n'
if len(profileStr) > 0:
profileStr = '' + \
profileStr + '
\n'
return profileStr
def _htmlProfileShares(actor: str, translate: {},
nickname: str, domain: str, sharesJson: {},
sharesFileType: str) -> str:
"""Shows shares on the profile screen
"""
profileStr = ''
for item in sharesJson['orderedItems']:
profileStr += htmlIndividualShare(domain, item['shareId'],
actor, item, translate, False, False,
sharesFileType)
if len(profileStr) > 0:
profileStr = '' + profileStr + '
\n'
return profileStr
def _grayscaleEnabled(base_dir: str) -> bool:
"""Is grayscale UI enabled?
"""
return os.path.isfile(base_dir + '/accounts/.grayscale')
def _htmlThemesDropdown(base_dir: str, translate: {}) -> str:
"""Returns the html for theme selection dropdown
"""
# Themes section
themes = getThemesList(base_dir)
themesDropdown = ' ' + \
translate['Theme'] + ' \n'
grayscale = _grayscaleEnabled(base_dir)
themesDropdown += \
editCheckBox(translate['Grayscale'], 'grayscale', grayscale)
themesDropdown += ' '
for theme_name in themes:
translatedThemeName = theme_name
if translate.get(theme_name):
translatedThemeName = translate[theme_name]
themesDropdown += ' ' + \
translatedThemeName + ' '
themesDropdown += ' '
if os.path.isfile(base_dir + '/fonts/custom.woff') or \
os.path.isfile(base_dir + '/fonts/custom.woff2') or \
os.path.isfile(base_dir + '/fonts/custom.otf') or \
os.path.isfile(base_dir + '/fonts/custom.ttf'):
themesDropdown += \
editCheckBox(translate['Remove the custom font'],
'removeCustomFont', False)
theme_name = get_config_param(base_dir, 'theme')
themesDropdown = \
themesDropdown.replace('',
' ')
return themesDropdown
def _htmlEditProfileGraphicDesign(base_dir: str, translate: {}) -> str:
"""Graphic design section on Edit Profile screen
"""
themeFormats = '.zip, .gz'
graphicsStr = beginEditSection(translate['Graphic Design'])
low_bandwidth = get_config_param(base_dir, 'low_bandwidth')
if not low_bandwidth:
low_bandwidth = False
graphicsStr += _htmlThemesDropdown(base_dir, translate)
graphicsStr += \
' ' + \
translate['Import Theme'] + ' \n'
graphicsStr += ' \n'
graphicsStr += \
' ' + \
translate['Export Theme'] + ' \n'
graphicsStr += \
' ➤ \n'
graphicsStr += \
editCheckBox(translate['Low Bandwidth'], 'low_bandwidth',
bool(low_bandwidth))
graphicsStr += endEditSection()
return graphicsStr
def _htmlEditProfileTwitter(base_dir: str, translate: {},
removeTwitter: str) -> str:
"""Edit twitter settings within profile
"""
# Twitter section
twitterStr = beginEditSection(translate['Twitter'])
twitterStr += \
editCheckBox(translate['Remove Twitter posts'],
'removeTwitter', removeTwitter)
twitter_replacement_domain = get_config_param(base_dir, "twitterdomain")
if not twitter_replacement_domain:
twitter_replacement_domain = ''
twitterStr += \
editTextField(translate['Twitter Replacement Domain'],
'twitterdomain', twitter_replacement_domain)
twitterStr += endEditSection()
return twitterStr
def _htmlEditProfileInstance(base_dir: str, translate: {},
peertube_instances: [],
media_instanceStr: str,
blogs_instanceStr: str,
news_instanceStr: str) -> (str, str, str, str):
"""Edit profile instance settings
"""
imageFormats = get_image_formats()
# Instance details section
instanceDescription = \
get_config_param(base_dir, 'instanceDescription')
customSubmitText = \
get_config_param(base_dir, 'customSubmitText')
instanceDescriptionShort = \
get_config_param(base_dir, 'instanceDescriptionShort')
instanceTitle = \
get_config_param(base_dir, 'instanceTitle')
content_license_url = \
get_config_param(base_dir, 'content_license_url')
if not content_license_url:
content_license_url = 'https://creativecommons.org/licenses/by/4.0'
instanceStr = beginEditSection(translate['Instance Settings'])
instanceStr += \
editTextField(translate['Instance Title'],
'instanceTitle', instanceTitle)
instanceStr += ' \n'
instanceStr += \
editTextField(translate['Instance Short Description'],
'instanceDescriptionShort', instanceDescriptionShort)
instanceStr += ' \n'
instanceStr += \
editTextArea(translate['Instance Description'],
'instanceDescription', instanceDescription, 200,
'', True)
instanceStr += \
editTextField(translate['Content License'],
'content_license_url', content_license_url)
instanceStr += ' \n'
instanceStr += \
editTextField(translate['Custom post submit button text'],
'customSubmitText', customSubmitText)
instanceStr += ' \n'
instanceStr += \
' ' + \
translate['Instance Logo'] + ' ' + \
' \n' + \
' ' + \
translate['Security'] + ' \n'
nodeInfoStr = \
translate['Show numbers of accounts within instance metadata']
if get_config_param(base_dir, "show_node_info_accounts"):
instanceStr += \
editCheckBox(nodeInfoStr, 'show_node_info_accounts', True)
else:
instanceStr += \
editCheckBox(nodeInfoStr, 'show_node_info_accounts', False)
nodeInfoStr = \
translate['Show version number within instance metadata']
if get_config_param(base_dir, "show_node_info_version"):
instanceStr += \
editCheckBox(nodeInfoStr, 'show_node_info_version', True)
else:
instanceStr += \
editCheckBox(nodeInfoStr, 'show_node_info_version', False)
if get_config_param(base_dir, "verify_all_signatures"):
instanceStr += \
editCheckBox(translate['Verify all signatures'],
'verifyallsignatures', True)
else:
instanceStr += \
editCheckBox(translate['Verify all signatures'],
'verifyallsignatures', False)
instanceStr += translate['Enabling broch mode'] + ' \n'
if get_config_param(base_dir, "broch_mode"):
instanceStr += \
editCheckBox(translate['Broch mode'], 'broch_mode', True)
else:
instanceStr += \
editCheckBox(translate['Broch mode'], 'broch_mode', False)
# Instance type
instanceStr += \
' ' + \
translate['Type of instance'] + ' \n'
instanceStr += \
editCheckBox(translate['This is a media instance'],
'media_instance', media_instanceStr)
instanceStr += \
editCheckBox(translate['This is a blogging instance'],
'blogs_instance', blogs_instanceStr)
instanceStr += \
editCheckBox(translate['This is a news instance'],
'news_instance', news_instanceStr)
instanceStr += endEditSection()
# Role assignments section
moderators = ''
moderatorsFile = base_dir + '/accounts/moderators.txt'
if os.path.isfile(moderatorsFile):
with open(moderatorsFile, 'r') as f:
moderators = f.read()
# site moderators
roleAssignStr = \
beginEditSection(translate['Role Assignment']) + \
' ' + \
translate['Moderators'] + ' \n' + \
' ' + \
translate['A list of moderator nicknames. One per line.'] + \
' '
# site editors
editors = ''
editorsFile = base_dir + '/accounts/editors.txt'
if os.path.isfile(editorsFile):
with open(editorsFile, 'r') as f:
editors = f.read()
roleAssignStr += \
' ' + \
translate['Site Editors'] + ' \n' + \
' ' + \
translate['A list of editor nicknames. One per line.'] + \
' '
# counselors
counselors = ''
counselorsFile = base_dir + '/accounts/counselors.txt'
if os.path.isfile(counselorsFile):
with open(counselorsFile, 'r') as f:
counselors = f.read()
roleAssignStr += \
editTextArea(translate['Counselors'], 'counselors', counselors,
200, '', False)
# artists
artists = ''
artistsFile = base_dir + '/accounts/artists.txt'
if os.path.isfile(artistsFile):
with open(artistsFile, 'r') as f:
artists = f.read()
roleAssignStr += \
editTextArea(translate['Artists'], 'artists', artists,
200, '', False)
roleAssignStr += endEditSection()
# Video section
peertubeStr = beginEditSection(translate['Video Settings'])
peertube_instancesStr = ''
for url in peertube_instances:
peertube_instancesStr += url + '\n'
peertubeStr += \
editTextArea(translate['Peertube Instances'], 'ptInstances',
peertube_instancesStr, 200, '', False)
peertubeStr += \
' \n'
yt_replace_domain = get_config_param(base_dir, "youtubedomain")
if not yt_replace_domain:
yt_replace_domain = ''
peertubeStr += \
editTextField(translate['YouTube Replacement Domain'],
'ytdomain', yt_replace_domain)
peertubeStr += endEditSection()
libretranslateUrl = get_config_param(base_dir, 'libretranslateUrl')
libretranslateApiKey = get_config_param(base_dir, 'libretranslateApiKey')
libretranslateStr = \
_htmlEditProfileLibreTranslate(translate,
libretranslateUrl,
libretranslateApiKey)
return instanceStr, roleAssignStr, peertubeStr, libretranslateStr
def _htmlEditProfileDangerZone(translate: {}) -> str:
"""danger zone section of Edit Profile screen
"""
editProfileForm = beginEditSection(translate['Danger Zone'])
editProfileForm += \
' ' + \
translate['Danger Zone'] + ' \n'
editProfileForm += \
editCheckBox(translate['Deactivate this account'],
'deactivateThisAccount', False)
editProfileForm += endEditSection()
return editProfileForm
def _htmlSystemMonitor(nickname: str, translate: {}) -> str:
"""Links to performance graphs
"""
systemMonitorStr = beginEditSection(translate['System Monitor'])
systemMonitorStr += '📊 GET
'
systemMonitorStr += '📊 POST
'
systemMonitorStr += endEditSection()
return systemMonitorStr
def _htmlEditProfileSkills(base_dir: str, nickname: str, domain: str,
translate: {}) -> str:
"""skills section of Edit Profile screen
"""
skills = getSkills(base_dir, nickname, domain)
skillsStr = ''
skillCtr = 1
if skills:
for skillDesc, skillValue in skills.items():
if isFiltered(base_dir, nickname, domain, skillDesc):
continue
skillsStr += \
' ' + \
'
'
skillCtr += 1
skillsStr += \
' ' + \
'
' + endEditSection()
idx = 'If you want to participate within organizations then you ' + \
'can indicate some skills that you have and approximate ' + \
'proficiency levels. This helps organizers to construct ' + \
'teams with an appropriate combination of skills.'
editProfileForm = \
beginEditSection(translate['Skills']) + \
' ' + \
translate['Skills'] + ' \n' + \
' ' + \
translate[idx] + ' \n' + skillsStr
return editProfileForm
def _htmlEditProfileGitProjects(base_dir: str, nickname: str, domain: str,
translate: {}) -> str:
"""git projects section of edit profile screen
"""
gitProjectsStr = ''
gitProjectsFilename = \
acct_dir(base_dir, nickname, domain) + '/gitprojects.txt'
if os.path.isfile(gitProjectsFilename):
with open(gitProjectsFilename, 'r') as gitProjectsFile:
gitProjectsStr = gitProjectsFile.read()
editProfileForm = beginEditSection(translate['Git Projects'])
idx = 'List of project names that you wish to receive git patches for'
editProfileForm += \
editTextArea(translate[idx], 'gitProjects', gitProjectsStr,
100, '', False)
editProfileForm += endEditSection()
return editProfileForm
def _htmlEditProfileSharedItems(base_dir: str, nickname: str, domain: str,
translate: {}) -> str:
"""shared items section of edit profile screen
"""
sharedItemsStr = ''
shared_items_federated_domainsStr = \
get_config_param(base_dir, 'shared_items_federated_domains')
if shared_items_federated_domainsStr:
shared_items_federated_domainsList = \
shared_items_federated_domainsStr.split(',')
for sharedFederatedDomain in shared_items_federated_domainsList:
sharedItemsStr += sharedFederatedDomain.strip() + '\n'
editProfileForm = beginEditSection(translate['Shares'])
idx = 'List of domains which can access the shared items catalog'
editProfileForm += \
editTextArea(translate[idx], 'shareDomainList',
sharedItemsStr, 200, '', False)
editProfileForm += endEditSection()
return editProfileForm
def _htmlEditProfileFiltering(base_dir: str, nickname: str, domain: str,
user_agents_blocked: str,
translate: {}, replyIntervalHours: int,
cw_lists: {}, lists_enabled: str) -> str:
"""Filtering and blocking section of edit profile screen
"""
filterStr = ''
filterFilename = \
acct_dir(base_dir, nickname, domain) + '/filters.txt'
if os.path.isfile(filterFilename):
with open(filterFilename, 'r') as filterfile:
filterStr = filterfile.read()
filterBioStr = ''
filterBioFilename = \
acct_dir(base_dir, nickname, domain) + '/filters_bio.txt'
if os.path.isfile(filterBioFilename):
with open(filterBioFilename, 'r') as filterfile:
filterBioStr = filterfile.read()
switchStr = ''
switchFilename = \
acct_dir(base_dir, nickname, domain) + '/replacewords.txt'
if os.path.isfile(switchFilename):
with open(switchFilename, 'r') as switchfile:
switchStr = switchfile.read()
autoTags = ''
autoTagsFilename = \
acct_dir(base_dir, nickname, domain) + '/autotags.txt'
if os.path.isfile(autoTagsFilename):
with open(autoTagsFilename, 'r') as autoTagsFile:
autoTags = autoTagsFile.read()
autoCW = ''
autoCWFilename = \
acct_dir(base_dir, nickname, domain) + '/autocw.txt'
if os.path.isfile(autoCWFilename):
with open(autoCWFilename, 'r') as autoCWFile:
autoCW = autoCWFile.read()
blockedStr = ''
blockedFilename = \
acct_dir(base_dir, nickname, domain) + '/blocking.txt'
if os.path.isfile(blockedFilename):
with open(blockedFilename, 'r') as blockedfile:
blockedStr = blockedfile.read()
dmAllowedInstancesStr = ''
dmAllowedInstancesFilename = \
acct_dir(base_dir, nickname, domain) + '/dmAllowedInstances.txt'
if os.path.isfile(dmAllowedInstancesFilename):
with open(dmAllowedInstancesFilename, 'r') as dmAllowedInstancesFile:
dmAllowedInstancesStr = dmAllowedInstancesFile.read()
allowedInstancesStr = ''
allowedInstancesFilename = \
acct_dir(base_dir, nickname, domain) + '/allowedinstances.txt'
if os.path.isfile(allowedInstancesFilename):
with open(allowedInstancesFilename, 'r') as allowedInstancesFile:
allowedInstancesStr = allowedInstancesFile.read()
editProfileForm = beginEditSection(translate['Filtering and Blocking'])
idx = 'Hours after posting during which replies are allowed'
editProfileForm += \
' ' + \
translate[idx] + \
': \n'
editProfileForm += \
'' + \
translate['City for spoofed GPS image metadata'] + \
' \n'
city = ''
cityFilename = acct_dir(base_dir, nickname, domain) + '/city.txt'
if os.path.isfile(cityFilename):
with open(cityFilename, 'r') as fp:
city = fp.read().replace('\n', '')
locationsFilename = base_dir + '/custom_locations.txt'
if not os.path.isfile(locationsFilename):
locationsFilename = base_dir + '/locations.txt'
cities = []
with open(locationsFilename, 'r') as f:
cities = f.readlines()
cities.sort()
editProfileForm += ' \n'
city = city.lower()
for cityName in cities:
if ':' not in cityName:
continue
citySelected = ''
cityName = cityName.split(':')[0]
cityName = cityName.lower()
if city:
if city in cityName:
citySelected = ' selected'
editProfileForm += \
' ' + \
cityName + ' \n'
editProfileForm += ' \n'
editProfileForm += \
' ' + \
translate['Filtered words'] + ' \n' + \
' ' + \
translate['One per line'] + ' \n' + \
' \n' + \
' ' + \
translate['Filtered words within bio'] + ' \n' + \
' ' + \
translate['One per line'] + ' \n' + \
' \n' + \
' ' + \
translate['Word Replacements'] + ' \n' + \
' A -> B \n' + \
' \n' + \
' ' + \
translate['Autogenerated Hashtags'] + ' \n' + \
' A -> #B \n' + \
' \n' + \
' ' + \
translate['Autogenerated Content Warnings'] + ' \n' + \
' A -> B \n' + \
' \n'
idx = 'Blocked accounts, one per line, in the form ' + \
'nickname@domain or *@blockeddomain'
editProfileForm += \
editTextArea(translate['Blocked accounts'], 'blocked', blockedStr,
200, '', False)
idx = 'Direct messages are always allowed from these instances.'
editProfileForm += \
editTextArea(translate['Direct Message permitted instances'],
'dmAllowedInstances', dmAllowedInstancesStr,
200, '', False)
idx = 'Federate only with a defined set of instances. ' + \
'One domain name per line.'
editProfileForm += \
' ' + \
translate['Federation list'] + ' \n' + \
' ' + \
translate[idx] + ' \n' + \
' \n'
if is_moderator(base_dir, nickname):
editProfileForm += \
'' + \
translate['Known Web Crawlers'] + ' \n'
user_agents_blockedStr = ''
for ua in user_agents_blocked:
if user_agents_blockedStr:
user_agents_blockedStr += '\n'
user_agents_blockedStr += ua
editProfileForm += \
editTextArea(translate['Blocked User Agents'],
'user_agents_blockedStr', user_agents_blockedStr,
200, '', False)
cw_listsStr = ''
for name, item in cw_lists.items():
variableName = get_cw_list_variable(name)
listIsEnabled = False
if lists_enabled:
if name in lists_enabled:
listIsEnabled = True
if translate.get(name):
name = translate[name]
cw_listsStr += editCheckBox(name, variableName, listIsEnabled)
if cw_listsStr:
idx = 'Add content warnings for the following sites'
editProfileForm += \
'' + translate[idx] + ': \n' + \
' ' + cw_listsStr
editProfileForm += endEditSection()
return editProfileForm
def _htmlEditProfileChangePassword(translate: {}) -> str:
"""Change password section of edit profile screen
"""
editProfileForm = \
beginEditSection(translate['Change Password']) + \
'' + translate['Change Password'] + \
' \n' + \
' \n' + \
'' + translate['Confirm Password'] + \
' \n' + \
' \n' + \
endEditSection()
return editProfileForm
def _htmlEditProfileLibreTranslate(translate: {},
libretranslateUrl: str,
libretranslateApiKey: str) -> str:
"""Change automatic translation settings
"""
editProfileForm = beginEditSection('LibreTranslate')
editProfileForm += \
editTextField('URL', 'libretranslateUrl', libretranslateUrl,
'http://0.0.0.0:5000')
editProfileForm += \
editTextField('API Key', 'libretranslateApiKey', libretranslateApiKey)
editProfileForm += endEditSection()
return editProfileForm
def _htmlEditProfileBackground(news_instance: bool, translate: {}) -> str:
"""Background images section of edit profile screen
"""
idx = 'The files attached below should be no larger than ' + \
'10MB in total uploaded at once.'
editProfileForm = \
beginEditSection(translate['Background Images']) + \
' ' + translate[idx] + ' \n'
if not news_instance:
imageFormats = get_image_formats()
editProfileForm += \
' ' + \
translate['Background image'] + ' \n' + \
' \n' + \
' ' + \
translate['Timeline banner image'] + ' \n' + \
' \n' + \
' ' + \
translate['Search banner image'] + ' \n' + \
' \n' + \
' ' + \
translate['Left column image'] + ' \n' + \
' \n' + \
' ' + \
translate['Right column image'] + ' \n' + \
' \n'
editProfileForm += endEditSection()
return editProfileForm
def _htmlEditProfileContactInfo(nickname: str,
emailAddress: str,
xmppAddress: str,
matrixAddress: str,
ssbAddress: str,
toxAddress: str,
briarAddress: str,
jamiAddress: str,
cwtchAddress: str,
translate: {}) -> str:
"""Contact Information section of edit profile screen
"""
editProfileForm = beginEditSection(translate['Contact Details'])
editProfileForm += editTextField(translate['Email'],
'email', emailAddress)
editProfileForm += editTextField(translate['XMPP'],
'xmppAddress', xmppAddress)
editProfileForm += editTextField(translate['Matrix'],
'matrixAddress', matrixAddress)
editProfileForm += editTextField('SSB', 'ssbAddress', ssbAddress)
editProfileForm += editTextField('Tox', 'toxAddress', toxAddress)
editProfileForm += editTextField('Briar', 'briarAddress', briarAddress)
editProfileForm += editTextField('Jami', 'jamiAddress', jamiAddress)
editProfileForm += editTextField('Cwtch', 'cwtchAddress', cwtchAddress)
editProfileForm += \
'' + \
translate['Following'] + ' \n'
editProfileForm += endEditSection()
return editProfileForm
def _htmlEditProfileEncryptionKeys(PGPfingerprint: str,
PGPpubKey: str,
EnigmaPubKey: str,
translate: {}) -> str:
"""Contact Information section of edit profile screen
"""
editProfileForm = beginEditSection(translate['Encryption Keys'])
enigmaUrl = 'https://github.com/enigma-reloaded/enigma-reloaded'
editProfileForm += \
editTextField('Enigma ',
'enigmapubkey', EnigmaPubKey)
editProfileForm += editTextField(translate['PGP Fingerprint'],
'openpgp', PGPfingerprint)
editProfileForm += \
editTextArea(translate['PGP'], 'pgp', PGPpubKey, 600,
'-----BEGIN PGP PUBLIC KEY BLOCK-----', False)
editProfileForm += endEditSection()
return editProfileForm
def _htmlEditProfileOptions(isAdmin: bool,
manuallyApprovesFollowers: str,
isBot: str, isGroup: str,
followDMs: str, removeTwitter: str,
notifyLikes: str, notifyReactions: str,
hideLikeButton: str,
hideReactionButton: str,
translate: {}) -> str:
"""option checkboxes section of edit profile screen
"""
editProfileForm = ' \n'
editProfileForm += \
editCheckBox(translate['Approve follower requests'],
'approveFollowers', manuallyApprovesFollowers)
editProfileForm += \
editCheckBox(translate['This is a bot account'],
'isBot', isBot)
if isAdmin:
editProfileForm += \
editCheckBox(translate['This is a group account'],
'isGroup', isGroup)
editProfileForm += \
editCheckBox(translate['Only people I follow can send me DMs'],
'followDMs', followDMs)
editProfileForm += \
editCheckBox(translate['Remove Twitter posts'],
'removeTwitter', removeTwitter)
editProfileForm += \
editCheckBox(translate['Notify when posts are liked'],
'notifyLikes', notifyLikes)
editProfileForm += \
editCheckBox(translate['Notify on emoji reactions'],
'notifyReactions', notifyReactions)
editProfileForm += \
editCheckBox(translate["Don't show the Like button"],
'hideLikeButton', hideLikeButton)
editProfileForm += \
editCheckBox(translate["Don't show the Reaction button"],
'hideReactionButton', hideReactionButton)
editProfileForm += '
\n'
return editProfileForm
def _get_supported_languagesSorted(base_dir: str) -> str:
"""Returns a list of supported languages
"""
lang_list = get_supported_languages(base_dir)
if not lang_list:
return ''
lang_list.sort()
languagesStr = ''
for lang in lang_list:
if languagesStr:
languagesStr += ' / ' + lang
else:
languagesStr = lang
return languagesStr
def _htmlEditProfileMain(base_dir: str, displayNickname: str, bioStr: str,
movedTo: str, donateUrl: str, websiteUrl: str,
blogAddress: str, actor_json: {},
translate: {}) -> str:
"""main info on edit profile screen
"""
imageFormats = get_image_formats()
editProfileForm = ' \n'
editProfileForm += \
editTextField(translate['Nickname'], 'displayNickname',
displayNickname)
editProfileForm += \
editTextArea(translate['Your bio'], 'bio', bioStr, 200, '', True)
editProfileForm += \
' ' + translate['Avatar image'] + \
' \n' + \
' \n'
occupationName = ''
if actor_json.get('hasOccupation'):
occupationName = get_occupation_name(actor_json)
editProfileForm += \
editTextField(translate['Occupation'], 'occupationName',
occupationName)
alsoKnownAsStr = ''
if actor_json.get('alsoKnownAs'):
alsoKnownAs = actor_json['alsoKnownAs']
ctr = 0
for altActor in alsoKnownAs:
if ctr > 0:
alsoKnownAsStr += ', '
ctr += 1
alsoKnownAsStr += altActor
editProfileForm += \
editTextField(translate['Other accounts'], 'alsoKnownAs',
alsoKnownAsStr, 'https://...')
editProfileForm += \
editTextField(translate['Moved to new account address'], 'movedTo',
movedTo, 'https://...')
editProfileForm += \
editTextField(translate['Donations link'], 'donateUrl',
donateUrl, 'https://...')
editProfileForm += \
editTextField(translate['Website'], 'websiteUrl',
websiteUrl, 'https://...')
editProfileForm += \
editTextField('Blog', 'blogAddress', blogAddress, 'https://...')
languagesListStr = _get_supported_languagesSorted(base_dir)
showLanguages = getActorLanguages(actor_json)
editProfileForm += \
editTextField(translate['Languages'], 'showLanguages',
showLanguages, languagesListStr)
editProfileForm += '
\n'
return editProfileForm
def _htmlEditProfileTopBanner(base_dir: str,
nickname: str, domain: str, domain_full: str,
defaultTimeline: str, bannerFile: str,
path: str, accessKeys: {}, translate: {}) -> str:
"""top banner on edit profile screen
"""
editProfileForm = \
'' + \
' \n'
editProfileForm += \
'\n'
editProfileForm += htmlFooter()
return editProfileForm
def _individualFollowAsHtml(signing_priv_key_pem: str,
translate: {},
base_dir: str, session,
cached_webfingers: {},
person_cache: {}, domain: str,
followUrl: str,
authorized: bool,
actorNickname: str,
http_prefix: str,
project_version: str,
dormant: bool,
debug: bool,
buttons=[]) -> str:
"""An individual follow entry on the profile screen
"""
followUrlNickname = get_nickname_from_actor(followUrl)
followUrlDomain, followUrlPort = get_domain_from_actor(followUrl)
followUrlDomainFull = get_full_domain(followUrlDomain, followUrlPort)
titleStr = '@' + followUrlNickname + '@' + followUrlDomainFull
avatarUrl = getPersonAvatarUrl(base_dir, followUrl, person_cache, True)
if not avatarUrl:
avatarUrl = followUrl + '/avatar.png'
displayName = get_display_name(base_dir, followUrl, person_cache)
isGroup = False
if not displayName:
# lookup the correct webfinger for the followUrl
followUrlHandle = followUrlNickname + '@' + followUrlDomainFull
followUrlWf = \
webfingerHandle(session, followUrlHandle, http_prefix,
cached_webfingers,
domain, __version__, debug, False,
signing_priv_key_pem)
originDomain = domain
(inboxUrl, pubKeyId, pubKey, fromPersonId, sharedInbox, avatarUrl2,
displayName, isGroup) = getPersonBox(signing_priv_key_pem,
originDomain,
base_dir, session,
followUrlWf,
person_cache, project_version,
http_prefix, followUrlNickname,
domain, 'outbox', 43036)
if avatarUrl2:
avatarUrl = avatarUrl2
if displayName:
displayName = \
addEmojiToDisplayName(None, base_dir, http_prefix,
actorNickname, domain,
displayName, False)
titleStr = displayName
if dormant:
titleStr += ' 💤'
buttonsStr = ''
if authorized:
for b in buttons:
if b == 'block':
buttonsStr += \
'' + \
translate['Block'] + ' \n'
elif b == 'unfollow':
unfollowStr = 'Unfollow'
if isGroup or \
is_group_account(base_dir,
followUrlNickname, followUrlDomain):
unfollowStr = 'Leave'
buttonsStr += \
'' + \
translate[unfollowStr] + ' \n'
resultStr = '\n'
return resultStr