__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 getDisplayName
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 getNicknameFromActor
from utils import getDomainFromActor
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 getReplyIntervalHours
from languages import getActorLanguages
from skills import getSkills
from theme import getThemesList
from person import personBoxJson
from person import getActorJson
from person import getPersonAvatarUrl
from posts import getPersonBox
from posts import isModerator
from posts import parseUserFeed
from posts import isCreateInsideAnnounce
from donate import getDonationUrl
from donate import getWebsite
from xmpp import getXmppAddress
from matrix import getMatrixAddress
from ssb import getSSBAddress
from pgp import getEmailAddress
from pgp import getPGPfingerprint
from pgp import getPGPpubKey
from enigma import getEnigmaPubKey
from tox import getToxAddress
from briar import getBriarAddress
from jami import getJamiAddress
from cwtch import getCwtchAddress
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 getBlogAddress
from webapp_post import individualPostAsHtml
from webapp_timeline import htmlIndividualShare
from blocking import getCWlistVariable
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(cssCache: {},
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 = getDomainFromActor(personUrl)
if not searchDomain:
return None
searchNickname = getNicknameFromActor(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,
cssCache: {}, 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,
cssCache, 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 = getDomainFromActor(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 = getDonationUrl(profile_json)
websiteUrl = getWebsite(profile_json, translate)
blogAddress = getBlogAddress(profile_json)
EnigmaPubKey = getEnigmaPubKey(profile_json)
PGPpubKey = getPGPpubKey(profile_json)
PGPfingerprint = getPGPfingerprint(profile_json)
emailAddress = getEmailAddress(profile_json)
xmppAddress = getXmppAddress(profile_json)
matrixAddress = getMatrixAddress(profile_json)
ssbAddress = getSSBAddress(profile_json)
toxAddress = getToxAddress(profile_json)
briarAddress = getBriarAddress(profile_json)
jamiAddress = getJamiAddress(profile_json)
cwtchAddress = getCwtchAddress(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, _ = \
getDomainFromActor(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 = \
personBoxJson({}, 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 isModerator(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 = getCWlistVariable(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 = getNicknameFromActor(followUrl)
followUrlDomain, followUrlPort = getDomainFromActor(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 = getDisplayName(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