epicyon/webapp_profile.py

2169 lines
86 KiB
Python
Raw Normal View History

2020-11-09 22:44:03 +00:00
__filename__ = "webapp_profile.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2021-01-26 10:07:42 +00:00
__version__ = "1.2.0"
2020-11-09 22:44:03 +00:00
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
2021-06-15 15:08:12 +00:00
__module_group__ = "Web Interface"
2020-11-09 22:44:03 +00:00
import os
from pprint import pprint
from utils import hasObjectDict
2021-05-16 15:10:39 +00:00
from utils import getOccupationName
from utils import getLockedAccount
2020-12-16 11:19:16 +00:00
from utils import getFullDomain
from utils import isArtist
from utils import isDormant
2020-11-09 22:44:03 +00:00
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import isSystemAccount
from utils import removeHtml
from utils import loadJson
from utils import getConfigParam
2020-11-21 11:21:05 +00:00
from utils import getImageFormats
2021-07-13 21:59:53 +00:00
from utils import acctDir
2020-11-09 22:44:03 +00:00
from skills import getSkills
from theme import getThemesList
from person import personBoxJson
2021-06-03 19:46:35 +00:00
from person import getActorJson
2021-06-25 14:33:16 +00:00
from person import getPersonAvatarUrl
2020-11-09 22:44:03 +00:00
from webfinger import webfingerHandle
from posts import parseUserFeed
from posts import getPersonBox
from donate import getDonationUrl
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 tox import getToxAddress
2020-12-24 16:49:57 +00:00
from briar import getBriarAddress
2020-11-29 12:50:41 +00:00
from jami import getJamiAddress
2021-06-27 11:48:03 +00:00
from cwtch import getCwtchAddress
2020-12-19 10:32:48 +00:00
from filters import isFiltered
from follow import isFollowerOfPerson
from webapp_frontscreen import htmlFrontScreen
2021-02-05 17:05:53 +00:00
from webapp_utils import htmlKeyboardNavigation
2021-02-06 10:35:47 +00:00
from webapp_utils import htmlHideFromScreenReader
2020-11-09 22:44:03 +00:00
from webapp_utils import scheduledPostsExist
2020-11-12 17:05:38 +00:00
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlHeaderWithPersonMarkup
2020-11-09 22:44:03 +00:00
from webapp_utils import htmlFooter
from webapp_utils import addEmojiToDisplayName
from webapp_utils import getBannerFile
from webapp_utils import htmlPostSeparator
2021-06-26 11:16:41 +00:00
from blog import getBlogAddress
2020-11-09 22:44:03 +00:00
from webapp_post import individualPostAsHtml
from webapp_timeline import htmlIndividualShare
def htmlProfileAfterSearch(cssCache: {},
recentPostsCache: {}, maxRecentPosts: int,
translate: {},
baseDir: str, path: str, httpPrefix: str,
nickname: str, domain: str, port: int,
profileHandle: str,
session, cachedWebfingers: {}, personCache: {},
debug: bool, projectVersion: str,
YTReplacementDomain: str,
showPublishedDateOnly: bool,
2020-12-23 23:59:49 +00:00
defaultTimeline: str,
peertubeInstances: [],
2021-03-09 19:52:10 +00:00
allowLocalNetworkAccess: bool,
2021-04-23 12:04:42 +00:00
themeName: str,
accessKeys: {},
systemLanguage: str) -> str:
2020-11-09 22:44:03 +00:00
"""Show a profile page after a search for a fediverse address
"""
2021-06-03 19:46:35 +00:00
http = False
gnunet = False
if httpPrefix == 'http':
http = True
elif httpPrefix == 'gnunet':
gnunet = True
profileJson, asHeader = \
2021-06-18 09:22:16 +00:00
getActorJson(domain, profileHandle, http, gnunet, debug, False)
2021-06-03 19:46:35 +00:00
if not profileJson:
2020-11-09 22:44:03 +00:00
return None
2021-06-03 19:46:35 +00:00
personUrl = profileJson['id']
searchDomain, searchPort = getDomainFromActor(personUrl)
searchNickname = getNicknameFromActor(personUrl)
2020-12-16 11:19:16 +00:00
searchDomainFull = getFullDomain(searchDomain, searchPort)
2020-11-09 22:44:03 +00:00
profileStr = ''
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2020-11-12 17:05:38 +00:00
avatarUrl = ''
if profileJson.get('icon'):
if profileJson['icon'].get('url'):
avatarUrl = profileJson['icon']['url']
if not avatarUrl:
avatarUrl = getPersonAvatarUrl(baseDir, personUrl,
personCache, True)
displayName = searchNickname
if profileJson.get('name'):
displayName = profileJson['name']
lockedAccount = getLockedAccount(profileJson)
if lockedAccount:
displayName += '🔒'
movedTo = ''
if profileJson.get('movedTo'):
movedTo = profileJson['movedTo']
displayName += ''
followsYou = \
isFollowerOfPerson(baseDir,
nickname, domain,
searchNickname,
searchDomainFull)
2020-11-12 17:05:38 +00:00
profileDescription = ''
if profileJson.get('summary'):
profileDescription = profileJson['summary']
outboxUrl = None
if not profileJson.get('outbox'):
if debug:
pprint(profileJson)
print('DEBUG: No outbox found')
return None
outboxUrl = profileJson['outbox']
# profileBackgroundImage = ''
# if profileJson.get('image'):
# if profileJson['image'].get('url'):
# profileBackgroundImage = profileJson['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:
2020-11-09 22:44:03 +00:00
profileDescriptionShort = ''
2020-11-12 17:05:38 +00:00
else:
if '<br>' in profileDescription:
if len(profileDescription.split('<br>')) > 2:
profileDescriptionShort = ''
# keep the profile description short
if len(profileDescriptionShort) > 256:
profileDescriptionShort = ''
# remove formatting from profile description used on title
avatarDescription = ''
if profileJson.get('summary'):
if isinstance(profileJson['summary'], str):
avatarDescription = \
profileJson['summary'].replace('<br>', '\n')
avatarDescription = avatarDescription.replace('<p>', '')
avatarDescription = avatarDescription.replace('</p>', '')
if '<' in avatarDescription:
avatarDescription = removeHtml(avatarDescription)
2020-11-12 22:41:41 +00:00
2020-11-12 23:44:16 +00:00
imageUrl = ''
if profileJson.get('image'):
if profileJson['image'].get('url'):
imageUrl = profileJson['image']['url']
2021-01-22 20:48:52 +00:00
alsoKnownAs = None
if profileJson.get('alsoKnownAs'):
alsoKnownAs = profileJson['alsoKnownAs']
joinedDate = None
if profileJson.get('published'):
if 'T' in profileJson['published']:
joinedDate = profileJson['published']
2020-11-12 23:38:58 +00:00
profileStr = \
_getProfileHeaderAfterSearch(baseDir,
nickname, defaultTimeline,
searchNickname,
searchDomainFull,
translate,
displayName, followsYou,
profileDescriptionShort,
avatarUrl, imageUrl,
movedTo, profileJson['id'],
alsoKnownAs, accessKeys,
joinedDate)
2020-11-12 22:41:41 +00:00
2020-12-16 11:19:16 +00:00
domainFull = getFullDomain(domain, port)
2020-11-20 12:14:22 +00:00
2020-11-20 12:20:34 +00:00
followIsPermitted = True
if searchNickname == 'news' and searchDomainFull == domainFull:
# currently the news actor is not something you can follow
followIsPermitted = False
elif searchNickname == nickname and searchDomainFull == domainFull:
# don't follow yourself!
followIsPermitted = False
if followIsPermitted:
2020-11-20 12:14:22 +00:00
profileStr += '<div class="container">\n'
profileStr += ' <form method="POST" action="' + \
backUrl + '/followconfirm">\n'
profileStr += ' <center>\n'
profileStr += \
' <input type="hidden" name="actor" value="' + \
personUrl + '">\n'
profileStr += \
2021-04-23 12:04:42 +00:00
' <button type="submit" class="button" name="submitYes" ' + \
'accesskey="' + accessKeys['followButton'] + '">' + \
2020-11-20 12:14:22 +00:00
translate['Follow'] + '</button>\n'
profileStr += \
2021-04-23 12:04:42 +00:00
' <button type="submit" class="button" name="submitView" ' + \
'accesskey="' + accessKeys['viewButton'] + '">' + \
2020-11-20 12:14:22 +00:00
translate['View'] + '</button>\n'
profileStr += ' </center>\n'
profileStr += ' </form>\n'
profileStr += '</div>\n'
2020-11-12 17:05:38 +00:00
i = 0
for item in parseUserFeed(session, outboxUrl, asHeader,
projectVersion, httpPrefix, domain):
if not item.get('actor'):
continue
if item['actor'] != personUrl:
continue
2020-11-12 17:05:38 +00:00
if not item.get('type'):
continue
if item['type'] != 'Create':
2020-11-12 17:05:38 +00:00
continue
if not hasObjectDict(item):
2020-11-12 17:05:38 +00:00
continue
2020-12-18 18:12:33 +00:00
2020-11-09 22:44:03 +00:00
profileStr += \
2020-11-12 17:05:38 +00:00
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
2020-12-09 13:31:54 +00:00
translate, None, baseDir,
2020-12-31 12:23:15 +00:00
session, cachedWebfingers, personCache,
2020-11-12 17:05:38 +00:00
nickname, domain, port,
item, avatarUrl, False, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
showPublishedDateOnly,
peertubeInstances, allowLocalNetworkAccess,
themeName, systemLanguage,
2020-11-12 17:05:38 +00:00
False, False, False, False, False)
i += 1
if i >= 20:
break
2020-11-09 22:44:03 +00:00
2021-01-11 19:46:21 +00:00
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
return htmlHeaderWithExternalStyle(cssFilename, instanceTitle) + \
profileStr + htmlFooter()
2020-11-09 22:44:03 +00:00
def _getProfileHeader(baseDir: str, httpPrefix: str,
nickname: str, domain: str,
domainFull: str, translate: {},
defaultTimeline: str,
displayName: str,
avatarDescription: str,
profileDescriptionShort: str,
loginButton: str, avatarUrl: str,
2021-01-22 20:59:43 +00:00
theme: str, movedTo: str,
2021-01-24 18:09:21 +00:00
alsoKnownAs: [],
2021-04-23 12:04:42 +00:00
pinnedContent: str,
accessKeys: {},
2021-05-12 18:31:41 +00:00
joinedDate: str,
occupationName: str) -> str:
2020-11-12 22:33:00 +00:00
"""The header of the profile screen, containing background
image and avatar
"""
2020-11-12 23:00:36 +00:00
htmlStr = '\n\n <figure class="profileHeader">\n'
htmlStr += ' <a href="/users/' + \
2020-11-12 22:33:00 +00:00
nickname + '/' + defaultTimeline + '" title="' + \
2021-05-30 17:05:09 +00:00
translate['Switch to timeline view'] + '">\n'
2020-11-12 23:10:25 +00:00
htmlStr += ' <img class="profileBackground" ' + \
'alt="" ' + \
2020-12-20 18:23:54 +00:00
'src="/users/' + nickname + '/image_' + theme + '.png" /></a>\n'
2020-11-12 23:00:36 +00:00
htmlStr += ' <figcaption>\n'
2020-11-12 22:33:00 +00:00
htmlStr += \
2020-11-12 23:17:37 +00:00
' <a href="/users/' + \
nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n' + \
' <img loading="lazy" src="' + avatarUrl + '" ' + \
'alt="" class="title"></a>\n'
2021-05-12 18:31:41 +00:00
occupationStr = ''
if occupationName:
occupationStr += \
' <b>' + occupationName + '</b><br>\n'
htmlStr += ' <h1>' + displayName + '</h1>\n' + occupationStr
2020-11-12 22:33:00 +00:00
htmlStr += \
2020-11-12 23:00:36 +00:00
' <p><b>@' + nickname + '@' + domainFull + '</b><br>\n'
if joinedDate:
htmlStr += \
2021-05-08 18:00:59 +00:00
' <p>' + translate['Joined'] + ' ' + \
joinedDate.split('T')[0] + '<br>\n'
if movedTo:
newNickname = getNicknameFromActor(movedTo)
newDomain, newPort = getDomainFromActor(movedTo)
newDomainFull = getFullDomain(newDomain, newPort)
if newNickname and newDomain:
htmlStr += \
' <p>' + translate['New account'] + ': ' + \
'<a href="' + movedTo + '">@' + \
newNickname + '@' + newDomainFull + '</a><br>\n'
2021-01-22 20:59:43 +00:00
elif alsoKnownAs:
otherAccountsHtml = \
2021-01-22 20:59:43 +00:00
' <p>' + translate['Other accounts'] + ': '
actor = httpPrefix + '://' + domainFull + '/users/' + nickname
ctr = 0
2021-01-22 20:59:43 +00:00
if isinstance(alsoKnownAs, list):
for altActor in alsoKnownAs:
if altActor == actor:
continue
2021-01-22 20:59:43 +00:00
if ctr > 0:
otherAccountsHtml += ' '
2021-01-22 20:59:43 +00:00
ctr += 1
altDomain, altPort = getDomainFromActor(altActor)
otherAccountsHtml += \
2021-01-22 20:59:43 +00:00
'<a href="' + altActor + '">' + altDomain + '</a>'
elif isinstance(alsoKnownAs, str):
if alsoKnownAs != actor:
ctr += 1
altDomain, altPort = getDomainFromActor(alsoKnownAs)
otherAccountsHtml += \
'<a href="' + alsoKnownAs + '">' + altDomain + '</a>'
otherAccountsHtml += '</p>\n'
if ctr > 0:
htmlStr += otherAccountsHtml
2020-11-12 22:33:00 +00:00
htmlStr += \
2020-11-12 23:00:36 +00:00
' <a href="/users/' + nickname + \
2020-11-12 22:33:00 +00:00
'/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \
translate['QR Code'] + '">' + \
2021-02-01 18:42:39 +00:00
'<img class="qrcode" alt="' + translate['QR Code'] + \
'" src="/icons/qrcode.png" /></a></p>\n'
2020-11-12 23:00:36 +00:00
htmlStr += ' <p>' + profileDescriptionShort + '</p>\n'
2020-11-12 22:33:00 +00:00
htmlStr += loginButton
2021-01-24 18:09:21 +00:00
if pinnedContent:
2021-01-28 14:21:51 +00:00
htmlStr += pinnedContent.replace('<p>', '<p>📎', 1)
2021-01-24 18:18:19 +00:00
htmlStr += ' </figcaption>\n'
htmlStr += ' </figure>\n\n'
2020-11-12 22:33:00 +00:00
return htmlStr
def _getProfileHeaderAfterSearch(baseDir: str,
nickname: str, defaultTimeline: str,
searchNickname: str,
searchDomainFull: str,
translate: {},
displayName: str,
followsYou: bool,
profileDescriptionShort: str,
avatarUrl: str, imageUrl: str,
movedTo: str, actor: str,
2021-04-23 12:04:42 +00:00
alsoKnownAs: [],
accessKeys: {},
joinedDate: str) -> str:
2020-11-12 23:38:58 +00:00
"""The header of a searched for handle, containing background
image and avatar
"""
htmlStr = '\n\n <figure class="profileHeader">\n'
htmlStr += ' <a href="/users/' + \
nickname + '/' + defaultTimeline + '" title="' + \
2021-04-23 12:04:42 +00:00
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n'
2020-11-12 23:44:16 +00:00
htmlStr += ' <img class="profileBackground" ' + \
'alt="" ' + \
2020-11-12 23:44:16 +00:00
'src="' + imageUrl + '" /></a>\n'
2020-11-12 23:38:58 +00:00
htmlStr += ' <figcaption>\n'
if avatarUrl:
htmlStr += ' <a href="/users/' + \
nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n'
2020-11-12 23:38:58 +00:00
htmlStr += \
' <img loading="lazy" src="' + avatarUrl + '" ' + \
'alt="" class="title"></a>\n'
2020-11-12 23:38:58 +00:00
htmlStr += ' <h1>' + displayName + '</h1>\n'
htmlStr += \
' <p><b>@' + searchNickname + '@' + searchDomainFull + '</b><br>\n'
if joinedDate:
2021-05-08 18:00:59 +00:00
htmlStr += ' <p>' + translate['Joined'] + ' ' + \
joinedDate.split('T')[0] + '</p>\n'
if followsYou:
htmlStr += ' <p><b>' + translate['Follows you'] + '</b></p>\n'
if movedTo:
newNickname = getNicknameFromActor(movedTo)
newDomain, newPort = getDomainFromActor(movedTo)
newDomainFull = getFullDomain(newDomain, newPort)
if newNickname and newDomain:
newHandle = newNickname + '@' + newDomainFull
htmlStr += ' <p>' + translate['New account'] + \
': < a href="' + movedTo + '">@' + newHandle + '</a></p>\n'
2021-01-22 20:48:52 +00:00
elif alsoKnownAs:
otherAccountshtml = \
2021-01-22 20:48:52 +00:00
' <p>' + translate['Other accounts'] + ': '
ctr = 0
2021-01-22 20:48:52 +00:00
if isinstance(alsoKnownAs, list):
for altActor in alsoKnownAs:
if altActor == actor:
continue
2021-01-22 20:48:52 +00:00
if ctr > 0:
otherAccountshtml += ' '
2021-01-22 20:48:52 +00:00
ctr += 1
altDomain, altPort = getDomainFromActor(altActor)
otherAccountshtml += \
2021-01-22 20:48:52 +00:00
'<a href="' + altActor + '">' + altDomain + '</a>'
elif isinstance(alsoKnownAs, str):
if alsoKnownAs != actor:
ctr += 1
altDomain, altPort = getDomainFromActor(alsoKnownAs)
otherAccountshtml += \
'<a href="' + alsoKnownAs + '">' + altDomain + '</a>'
otherAccountshtml += '</p>\n'
if ctr > 0:
htmlStr += otherAccountshtml
2020-11-12 23:38:58 +00:00
htmlStr += ' <p>' + profileDescriptionShort + '</p>\n'
htmlStr += ' </figcaption>\n'
htmlStr += ' </figure>\n\n'
return htmlStr
2020-11-09 22:44:03 +00:00
def htmlProfile(rssIconAtTop: bool,
cssCache: {}, iconsAsButtons: bool,
defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, projectVersion: str,
baseDir: str, httpPrefix: str, authorized: bool,
profileJson: {}, selected: str,
session, cachedWebfingers: {}, personCache: {},
2020-11-09 22:44:03 +00:00
YTReplacementDomain: str,
showPublishedDateOnly: bool,
2020-12-20 17:26:38 +00:00
newswire: {}, theme: str, dormantMonths: int,
2020-12-23 23:59:49 +00:00
peertubeInstances: [],
allowLocalNetworkAccess: bool,
2021-02-05 19:14:27 +00:00
textModeBanner: str,
debug: bool, accessKeys: {}, city: str,
systemLanguage: str,
2021-06-20 11:28:35 +00:00
extraJson: {} = None, pageNumber: int = None,
maxItemsPerPage: int = None) -> str:
2020-11-09 22:44:03 +00:00
"""Show the profile page as html
"""
nickname = profileJson['preferredUsername']
if not nickname:
return ""
if isSystemAccount(nickname):
return htmlFrontScreen(rssIconAtTop,
cssCache, iconsAsButtons,
defaultTimeline,
recentPostsCache, maxRecentPosts,
translate, projectVersion,
baseDir, httpPrefix, authorized,
profileJson, selected,
session, cachedWebfingers, personCache,
YTReplacementDomain,
showPublishedDateOnly,
2020-12-20 17:26:38 +00:00
newswire, theme, extraJson,
2021-04-23 16:29:03 +00:00
allowLocalNetworkAccess, accessKeys,
systemLanguage,
pageNumber, maxItemsPerPage)
2020-11-09 22:44:03 +00:00
domain, port = getDomainFromActor(profileJson['id'])
if not domain:
return ""
displayName = \
addEmojiToDisplayName(baseDir, httpPrefix,
nickname, domain,
profileJson['name'], True)
2021-06-13 08:48:59 +00:00
domainFull = getFullDomain(domain, port)
2020-11-09 22:44:03 +00:00
profileDescription = \
addEmojiToDisplayName(baseDir, httpPrefix,
nickname, domain,
profileJson['summary'], False)
postsButton = 'button'
followingButton = 'button'
followersButton = 'button'
rolesButton = 'button'
skillsButton = 'button'
sharesButton = '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'
loginButton = ''
followApprovalsSection = ''
followApprovals = False
editProfileStr = ''
logoutStr = ''
actor = profileJson['id']
usersPath = '/users/' + actor.split('/users/')[1]
donateSection = ''
donateUrl = getDonationUrl(profileJson)
PGPpubKey = getPGPpubKey(profileJson)
PGPfingerprint = getPGPfingerprint(profileJson)
emailAddress = getEmailAddress(profileJson)
xmppAddress = getXmppAddress(profileJson)
matrixAddress = getMatrixAddress(profileJson)
ssbAddress = getSSBAddress(profileJson)
toxAddress = getToxAddress(profileJson)
2020-12-24 16:48:03 +00:00
briarAddress = getBriarAddress(profileJson)
2020-11-29 12:50:41 +00:00
jamiAddress = getJamiAddress(profileJson)
2021-06-27 11:48:03 +00:00
cwtchAddress = getCwtchAddress(profileJson)
2020-11-09 22:44:03 +00:00
if donateUrl or xmppAddress or matrixAddress or \
2020-12-24 16:48:03 +00:00
ssbAddress or toxAddress or briarAddress or \
2021-06-27 11:48:03 +00:00
jamiAddress or cwtchAddress or PGPpubKey or \
2020-11-09 22:44:03 +00:00
PGPfingerprint or emailAddress:
donateSection = '<div class="container">\n'
donateSection += ' <center>\n'
if donateUrl and not isSystemAccount(nickname):
donateSection += \
' <p><a href="' + donateUrl + \
'"><button class="donateButton">' + translate['Donate'] + \
'</button></a></p>\n'
if emailAddress:
donateSection += \
'<p>' + translate['Email'] + ': <a href="mailto:' + \
emailAddress + '">' + emailAddress + '</a></p>\n'
if xmppAddress:
donateSection += \
'<p>' + translate['XMPP'] + ': <a href="xmpp:' + \
2021-06-22 12:42:52 +00:00
xmppAddress + '">' + xmppAddress + '</a></p>\n'
2020-11-09 22:44:03 +00:00
if matrixAddress:
donateSection += \
'<p>' + translate['Matrix'] + ': ' + matrixAddress + '</p>\n'
if ssbAddress:
donateSection += \
'<p>SSB: <label class="ssbaddr">' + \
ssbAddress + '</label></p>\n'
if toxAddress:
donateSection += \
'<p>Tox: <label class="toxaddr">' + \
toxAddress + '</label></p>\n'
2020-12-24 16:48:03 +00:00
if briarAddress:
2020-12-24 17:11:18 +00:00
if briarAddress.startswith('briar://'):
donateSection += \
'<p><label class="toxaddr">' + \
briarAddress + '</label></p>\n'
else:
donateSection += \
'<p>briar://<label class="toxaddr">' + \
briarAddress + '</label></p>\n'
2020-11-29 12:50:41 +00:00
if jamiAddress:
donateSection += \
'<p>Jami: <label class="toxaddr">' + \
jamiAddress + '</label></p>\n'
2021-06-27 11:48:03 +00:00
if cwtchAddress:
donateSection += \
'<p>Cwtch: <label class="toxaddr">' + \
cwtchAddress + '</label></p>\n'
2020-11-09 22:44:03 +00:00
if PGPfingerprint:
donateSection += \
'<p class="pgp">PGP: ' + \
PGPfingerprint.replace('\n', '<br>') + '</p>\n'
if PGPpubKey:
donateSection += \
'<p class="pgp">' + PGPpubKey.replace('\n', '<br>') + '</p>\n'
donateSection += ' </center>\n'
donateSection += '</div>\n'
if authorized:
2020-11-09 22:44:03 +00:00
editProfileStr = \
'<a class="imageAnchor" href="' + usersPath + '/editprofile">' + \
2020-12-09 13:08:26 +00:00
'<img loading="lazy" src="/icons' + \
2020-11-09 22:44:03 +00:00
'/edit.png" title="' + translate['Edit'] + \
'" alt="| ' + translate['Edit'] + '" class="timelineicon"/></a>\n'
logoutStr = \
'<a class="imageAnchor" href="/logout">' + \
2020-12-09 13:08:26 +00:00
'<img loading="lazy" src="/icons' + \
2020-11-09 22:44:03 +00:00
'/logout.png" title="' + translate['Logout'] + \
'" alt="| ' + translate['Logout'] + \
'" class="timelineicon"/></a>\n'
# are there any follow requests?
followRequestsFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/followrequests.txt'
2020-11-09 22:44:03 +00:00
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:
with open(followRequestsFilename, 'r') as f:
for followerHandle in f:
if len(line) > 0:
if '://' in followerHandle:
followerActor = followerHandle
else:
followerActor = \
httpPrefix + '://' + \
followerHandle.split('@')[1] + \
'/users/' + followerHandle.split('@')[0]
basePath = '/users/' + nickname
followApprovalsSection += '<div class="container">'
followApprovalsSection += \
'<a href="' + followerActor + '">'
followApprovalsSection += \
'<span class="followRequestHandle">' + \
followerHandle + '</span></a>'
followApprovalsSection += \
'<a href="' + basePath + \
'/followapprove=' + followerHandle + '">'
followApprovalsSection += \
'<button class="followApprove">' + \
translate['Approve'] + '</button></a><br><br>'
followApprovalsSection += \
'<a href="' + basePath + \
'/followdeny=' + followerHandle + '">'
followApprovalsSection += \
'<button class="followDeny">' + \
translate['Deny'] + '</button></a>'
followApprovalsSection += '</div>'
profileDescriptionShort = profileDescription
if '\n' in profileDescription:
if len(profileDescription.split('\n')) > 2:
profileDescriptionShort = ''
else:
if '<br>' in profileDescription:
if len(profileDescription.split('<br>')) > 2:
profileDescriptionShort = ''
profileDescription = profileDescription.replace('<br>', '\n')
# keep the profile description short
if len(profileDescriptionShort) > 256:
profileDescriptionShort = ''
# remove formatting from profile description used on title
avatarDescription = ''
if profileJson.get('summary'):
avatarDescription = profileJson['summary'].replace('<br>', '\n')
avatarDescription = avatarDescription.replace('<p>', '')
avatarDescription = avatarDescription.replace('</p>', '')
movedTo = ''
if profileJson.get('movedTo'):
movedTo = profileJson['movedTo']
2021-01-22 20:59:43 +00:00
alsoKnownAs = None
if profileJson.get('alsoKnownAs'):
alsoKnownAs = profileJson['alsoKnownAs']
joinedDate = None
if profileJson.get('published'):
if 'T' in profileJson['published']:
joinedDate = profileJson['published']
2021-05-12 18:31:41 +00:00
occupationName = None
2021-05-13 11:35:36 +00:00
if profileJson.get('hasOccupation'):
2021-05-16 15:10:39 +00:00
occupationName = getOccupationName(profileJson)
avatarUrl = profileJson['icon']['url']
2021-06-13 08:48:59 +00:00
# use alternate path for local avatars to avoid any caching issues
if '://' + domainFull + '/accounts/avatars/' in avatarUrl:
avatarUrl = \
avatarUrl.replace('://' + domainFull + '/accounts/avatars/',
'://' + domainFull + '/users/')
2021-01-24 18:09:21 +00:00
# get pinned post content
2021-07-13 21:59:53 +00:00
accountDir = acctDir(baseDir, nickname, domain)
2021-01-24 18:09:21 +00:00
pinnedFilename = accountDir + '/pinToProfile.txt'
pinnedContent = None
if os.path.isfile(pinnedFilename):
with open(pinnedFilename, 'r') as pinFile:
pinnedContent = pinFile.read()
profileHeaderStr = \
_getProfileHeader(baseDir, httpPrefix,
nickname, domain,
domainFull, translate,
defaultTimeline, displayName,
avatarDescription,
profileDescriptionShort,
loginButton, avatarUrl, theme,
2021-01-24 18:09:21 +00:00
movedTo, alsoKnownAs,
pinnedContent, accessKeys,
2021-05-12 18:31:41 +00:00
joinedDate, occupationName)
2020-11-09 22:44:03 +00:00
2021-02-05 15:30:51 +00:00
# keyboard navigation
2021-02-05 17:05:53 +00:00
userPathStr = '/users/' + nickname
2021-02-05 17:44:50 +00:00
deft = defaultTimeline
2021-02-06 10:35:47 +00:00
menuTimeline = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('🏠') + ' ' + \
translate['Switch to timeline view']
2021-02-06 10:35:47 +00:00
menuEdit = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('') + ' ' + translate['Edit']
2021-02-06 10:35:47 +00:00
menuFollowing = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('👥') + ' ' + translate['Following']
2021-02-06 10:35:47 +00:00
menuFollowers = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('👪') + ' ' + translate['Followers']
2021-02-06 10:35:47 +00:00
menuRoles = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('🤚') + ' ' + translate['Roles']
2021-02-06 10:35:47 +00:00
menuSkills = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('🛠') + ' ' + translate['Skills']
2021-02-06 10:35:47 +00:00
menuShares = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('🤝') + ' ' + translate['Shares']
2021-02-06 10:35:47 +00:00
menuLogout = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('') + ' ' + translate['Logout']
2021-02-05 17:05:53 +00:00
navLinks = {
2021-02-06 10:35:47 +00:00
menuTimeline: userPathStr + '/' + deft,
menuEdit: userPathStr + '/editprofile',
menuFollowing: userPathStr + '/following#timeline',
menuFollowers: userPathStr + '/followers#timeline',
menuRoles: userPathStr + '/roles#timeline',
menuSkills: userPathStr + '/skills#timeline',
menuShares: userPathStr + '/shares#timeline',
menuLogout: '/logout'
2021-02-05 17:55:49 +00:00
}
2021-04-22 14:12:59 +00:00
navAccessKeys = {}
for variableName, key in accessKeys.items():
if not locals().get(variableName):
continue
navAccessKeys[locals()[variableName]] = key
2021-04-22 11:51:19 +00:00
profileStr = htmlKeyboardNavigation(textModeBanner,
2021-04-22 11:57:35 +00:00
navLinks, navAccessKeys)
profileStr += profileHeaderStr + donateSection
profileStr += '<div class="container" id="buttonheader">\n'
profileStr += ' <center>'
profileStr += \
' <a href="' + usersPath + '#buttonheader"><button class="' + \
postsButton + '"><span>' + translate['Posts'] + \
' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/following#buttonheader">' + \
'<button class="' + followingButton + '"><span>' + \
translate['Following'] + ' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/followers#buttonheader">' + \
'<button class="' + followersButton + \
'"><span>' + translate['Followers'] + ' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/roles#buttonheader">' + \
'<button class="' + rolesButton + '"><span>' + \
translate['Roles'] + \
' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/skills#buttonheader">' + \
'<button class="' + skillsButton + '"><span>' + \
translate['Skills'] + ' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/shares#buttonheader">' + \
'<button class="' + sharesButton + '"><span>' + \
translate['Shares'] + ' </span></button></a>'
profileStr += logoutStr + editProfileStr
profileStr += ' </center>'
profileStr += '</div>'
2020-11-09 22:44:03 +00:00
2021-02-05 17:55:49 +00:00
# start of #timeline
profileStr += '<div id="timeline">\n'
2020-11-09 22:44:03 +00:00
profileStr += followApprovalsSection
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
licenseStr = \
'<a href="https://gitlab.com/bashrc2/epicyon">' + \
'<img loading="lazy" class="license" alt="' + \
translate['Get the source code'] + '" title="' + \
translate['Get the source code'] + '" src="/icons/agpl.png" /></a>'
if selected == 'posts':
2020-11-27 18:50:54 +00:00
profileStr += \
_htmlProfilePosts(recentPostsCache, maxRecentPosts,
translate,
baseDir, httpPrefix, authorized,
nickname, domain, port,
session, cachedWebfingers, personCache,
projectVersion,
YTReplacementDomain,
2020-12-23 23:59:49 +00:00
showPublishedDateOnly,
peertubeInstances,
2021-03-09 19:52:10 +00:00
allowLocalNetworkAccess,
theme, systemLanguage) + licenseStr
elif selected == 'following':
profileStr += \
_htmlProfileFollowing(translate, baseDir, httpPrefix,
authorized, nickname,
domain, port, session,
cachedWebfingers, personCache, extraJson,
projectVersion, ["unfollow"], selected,
usersPath, pageNumber, maxItemsPerPage,
2021-03-14 19:22:58 +00:00
dormantMonths, debug)
elif selected == 'followers':
profileStr += \
_htmlProfileFollowing(translate, baseDir, httpPrefix,
authorized, nickname,
domain, port, session,
cachedWebfingers, personCache, extraJson,
projectVersion, ["block"],
selected, usersPath, pageNumber,
2021-03-14 19:22:58 +00:00
maxItemsPerPage, dormantMonths, debug)
elif selected == 'roles':
profileStr += \
_htmlProfileRoles(translate, nickname, domainFull,
extraJson)
elif selected == 'skills':
profileStr += \
_htmlProfileSkills(translate, nickname, domainFull, extraJson)
elif selected == 'shares':
profileStr += \
_htmlProfileShares(actor, translate,
nickname, domainFull,
extraJson) + licenseStr
2021-02-05 17:55:49 +00:00
# end of #timeline
profileStr += '</div>'
2020-11-12 17:05:38 +00:00
2021-01-11 19:46:21 +00:00
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
2020-11-12 17:05:38 +00:00
profileStr = \
htmlHeaderWithPersonMarkup(cssFilename, instanceTitle,
profileJson, city) + \
profileStr + htmlFooter()
2020-11-09 22:44:03 +00:00
return profileStr
def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
translate: {},
baseDir: str, httpPrefix: str,
authorized: bool,
nickname: str, domain: str, port: int,
session, cachedWebfingers: {}, personCache: {},
projectVersion: str,
YTReplacementDomain: str,
2020-12-23 23:59:49 +00:00
showPublishedDateOnly: bool,
peertubeInstances: [],
2021-03-09 19:52:10 +00:00
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str) -> str:
2020-11-09 22:44:03 +00:00
"""Shows posts on the profile screen
These should only be public posts
"""
separatorStr = htmlPostSeparator(baseDir, None)
profileStr = ''
maxItems = 4
ctr = 0
currPage = 1
2020-11-27 12:29:20 +00:00
boxName = 'outbox'
2020-11-09 22:44:03 +00:00
while ctr < maxItems and currPage < 4:
outboxFeedPathStr = \
'/users/' + nickname + '/' + boxName + '?page=' + \
str(currPage)
2020-11-09 22:44:03 +00:00
outboxFeed = \
personBoxJson({}, session, baseDir, domain,
port,
outboxFeedPathStr,
2020-11-09 22:44:03 +00:00
httpPrefix,
2020-11-27 12:29:20 +00:00
10, boxName,
2020-11-09 22:44:03 +00:00
authorized, 0, False, 0)
if not outboxFeed:
2020-11-27 16:14:54 +00:00
break
2020-11-09 22:44:03 +00:00
if len(outboxFeed['orderedItems']) == 0:
break
for item in outboxFeed['orderedItems']:
if item['type'] == 'Create':
postStr = \
individualPostAsHtml(True, recentPostsCache,
maxRecentPosts,
2020-12-09 13:31:54 +00:00
translate, None,
baseDir, session, cachedWebfingers,
2020-11-09 22:44:03 +00:00
personCache,
nickname, domain, port, item,
None, True, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
showPublishedDateOnly,
2020-12-23 23:59:49 +00:00
peertubeInstances,
allowLocalNetworkAccess,
themeName, systemLanguage,
2020-11-09 22:44:03 +00:00
False, False, False, True, False)
if postStr:
2020-11-18 21:18:51 +00:00
profileStr += postStr + separatorStr
2020-11-09 22:44:03 +00:00
ctr += 1
if ctr >= maxItems:
break
currPage += 1
return profileStr
def _htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
authorized: bool,
nickname: str, domain: str, port: int,
session, cachedWebfingers: {}, personCache: {},
followingJson: {}, projectVersion: str,
buttons: [],
feedName: str, actor: str,
pageNumber: int,
maxItemsPerPage: int,
2021-03-14 19:22:58 +00:00
dormantMonths: int, debug: bool) -> str:
2020-11-09 22:44:03 +00:00
"""Shows following on the profile screen
"""
profileStr = ''
if authorized and pageNumber:
if authorized and pageNumber > 1:
# page up arrow
profileStr += \
' <center>\n' + \
' <a href="' + actor + '/' + feedName + \
2020-11-24 14:49:07 +00:00
'?page=' + str(pageNumber - 1) + '#buttonheader' + \
2020-11-09 22:44:03 +00:00
'"><img loading="lazy" class="pageicon" src="/' + \
2020-12-09 13:08:26 +00:00
'icons/pageup.png" title="' + \
2020-11-09 22:44:03 +00:00
translate['Page up'] + '" alt="' + \
translate['Page up'] + '"></a>\n' + \
' </center>\n'
for followingActor in followingJson['orderedItems']:
2020-12-13 12:48:04 +00:00
# is this a dormant followed account?
dormant = False
2020-12-13 12:45:29 +00:00
if authorized and feedName == 'following':
dormant = \
isDormant(baseDir, nickname, domain, followingActor,
dormantMonths)
2020-12-13 12:48:04 +00:00
2020-11-09 22:44:03 +00:00
profileStr += \
_individualFollowAsHtml(translate, baseDir, session,
cachedWebfingers, personCache,
domain, followingActor,
authorized, nickname,
httpPrefix, projectVersion, dormant,
2021-03-14 19:22:58 +00:00
debug, buttons)
2020-12-13 12:48:04 +00:00
2020-11-09 22:44:03 +00:00
if authorized and maxItemsPerPage and pageNumber:
if len(followingJson['orderedItems']) >= maxItemsPerPage:
# page down arrow
profileStr += \
' <center>\n' + \
' <a href="' + actor + '/' + feedName + \
2020-11-24 14:49:07 +00:00
'?page=' + str(pageNumber + 1) + '#buttonheader' + \
2020-11-09 22:44:03 +00:00
'"><img loading="lazy" class="pageicon" src="/' + \
2020-12-09 13:08:26 +00:00
'icons/pagedown.png" title="' + \
2020-11-09 22:44:03 +00:00
translate['Page down'] + '" alt="' + \
translate['Page down'] + '"></a>\n' + \
' </center>\n'
return profileStr
def _htmlProfileRoles(translate: {}, nickname: str, domain: str,
rolesList: []) -> str:
2020-11-09 22:44:03 +00:00
"""Shows roles on the profile screen
"""
profileStr = ''
profileStr += \
'<div class="roles">\n<div class="roles-inner">\n'
for role in rolesList:
if translate.get(role):
profileStr += '<h3>' + translate[role] + '</h3>\n'
else:
profileStr += '<h3>' + role + '</h3>\n'
profileStr += '</div></div>\n'
2020-11-09 22:44:03 +00:00
if len(profileStr) == 0:
profileStr += \
'<p>@' + nickname + '@' + domain + ' has no roles assigned</p>\n'
else:
profileStr = '<div>' + profileStr + '</div>\n'
return profileStr
def _htmlProfileSkills(translate: {}, nickname: str, domain: str,
skillsJson: {}) -> str:
2020-11-09 22:44:03 +00:00
"""Shows skills on the profile screen
"""
profileStr = ''
for skill, level in skillsJson.items():
profileStr += \
'<div>' + skill + \
'<br><div id="myProgress"><div id="myBar" style="width:' + \
str(level) + '%"></div></div></div>\n<br>\n'
if len(profileStr) > 0:
profileStr = '<center><div class="skill-title">' + \
profileStr + '</div></center>\n'
return profileStr
def _htmlProfileShares(actor: str, translate: {},
nickname: str, domain: str, sharesJson: {}) -> str:
2020-11-09 22:44:03 +00:00
"""Shows shares on the profile screen
"""
profileStr = ''
for item in sharesJson['orderedItems']:
profileStr += htmlIndividualShare(actor, item, translate, False, False)
if len(profileStr) > 0:
profileStr = '<div class="share-title">' + profileStr + '</div>\n'
return profileStr
2021-06-27 17:59:46 +00:00
def _grayscaleEnabled(baseDir: str) -> bool:
"""Is grayscale UI enabled?
"""
return os.path.isfile(baseDir + '/accounts/.grayscale')
def _htmlThemesDropdown(baseDir: str, translate: {}) -> str:
"""Returns the html for theme selection dropdown
"""
# Themes section
themes = getThemesList(baseDir)
themesDropdown = ' <label class="labels">' + \
translate['Theme'] + '</label><br>\n'
grayscale = ''
if _grayscaleEnabled(baseDir):
grayscale = 'checked'
themesDropdown += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="grayscale" ' + grayscale + \
'> ' + translate['Grayscale'] + '<br>'
themesDropdown += ' <select id="themeDropdown" ' + \
'name="themeDropdown" class="theme">'
for themeName in themes:
translatedThemeName = themeName
if translate.get(themeName):
translatedThemeName = translate[themeName]
themesDropdown += ' <option value="' + \
themeName.lower() + '">' + \
translatedThemeName + '</option>'
themesDropdown += ' </select><br>'
if os.path.isfile(baseDir + '/fonts/custom.woff') or \
os.path.isfile(baseDir + '/fonts/custom.woff2') or \
os.path.isfile(baseDir + '/fonts/custom.otf') or \
os.path.isfile(baseDir + '/fonts/custom.ttf'):
themesDropdown += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="removeCustomFont"> ' + \
translate['Remove the custom font'] + '<br>'
themeName = getConfigParam(baseDir, 'theme')
themesDropdown = \
themesDropdown.replace('<option value="' + themeName + '">',
'<option value="' + themeName +
'" selected>')
return themesDropdown
def _htmlEditProfileGraphicDesign(baseDir: str, translate: {}) -> str:
"""Graphic design section on Edit Profile screen
"""
themeFormats = '.zip, .gz'
graphicsStr = '<details><summary class="cw">' + \
translate['Graphic Design'] + '</summary>\n'
graphicsStr += '<div class="container">'
graphicsStr += _htmlThemesDropdown(baseDir, translate)
graphicsStr += \
' <label class="labels">' + \
translate['Import Theme'] + '</label>\n'
graphicsStr += ' <input type="file" id="importTheme" '
graphicsStr += 'name="submitImportTheme" '
graphicsStr += 'accept="' + themeFormats + '">\n'
graphicsStr += \
' <label class="labels">' + \
translate['Export Theme'] + '</label><br>\n'
graphicsStr += \
' <button type="submit" class="button" ' + \
'name="submitExportTheme">➤</button>\n'
graphicsStr += ' </div></details>\n'
return graphicsStr
def _htmlEditProfileInstance(baseDir: str, translate: {},
peertubeInstances: [],
mediaInstanceStr: str,
blogsInstanceStr: str,
newsInstanceStr: str) -> (str, str, str):
"""Edit profile instance settings
"""
imageFormats = getImageFormats()
# Instance details section
instanceDescription = \
getConfigParam(baseDir, 'instanceDescription')
customSubmitText = \
getConfigParam(baseDir, 'customSubmitText')
instanceDescriptionShort = \
getConfigParam(baseDir, 'instanceDescriptionShort')
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
instanceStr = '<details><summary class="cw">' + \
translate['Instance Settings'] + '</summary>\n'
instanceStr += '<div class="container">'
instanceStr += \
' <label class="labels">' + \
translate['Instance Title'] + '</label>'
if instanceTitle:
instanceStr += \
' <input type="text" name="instanceTitle" value="' + \
instanceTitle + '"><br>'
else:
instanceStr += \
' <input type="text" name="instanceTitle" value=""><br>'
instanceStr += \
' <label class="labels">' + \
translate['Instance Short Description'] + '</label>'
if instanceDescriptionShort:
instanceStr += \
' <input type="text" ' + \
'name="instanceDescriptionShort" value="' + \
instanceDescriptionShort + '"><br>'
else:
instanceStr += \
' <input type="text" ' + \
'name="instanceDescriptionShort" value=""><br>'
instanceStr += \
' <label class="labels">' + \
translate['Instance Description'] + '</label>'
if instanceDescription:
instanceStr += \
' <textarea id="message" name="instanceDescription" ' + \
'style="height:200px" spellcheck="true">' + \
instanceDescription + '</textarea>'
else:
instanceStr += \
' <textarea id="message" name="instanceDescription" ' + \
'style="height:200px" spellcheck="true"></textarea>'
instanceStr += \
' <label class="labels">' + \
translate['Custom post submit button text'] + '</label>'
if customSubmitText:
instanceStr += \
' <input type="text" ' + \
'name="customSubmitText" value="' + \
customSubmitText + '"><br>'
else:
instanceStr += \
' <input type="text" ' + \
'name="customSubmitText" value=""><br>'
instanceStr += \
' <label class="labels">' + \
translate['Instance Logo'] + '</label>'
instanceStr += \
' <input type="file" id="instanceLogo" name="instanceLogo"'
instanceStr += ' accept="' + imageFormats + '"><br>\n'
instanceStr += \
' <br><label class="labels">' + \
translate['Security'] + '</label><br>\n'
nodeInfoStr = \
translate['Show numbers of accounts within instance metadata']
if getConfigParam(baseDir, "showNodeInfoAccounts"):
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="showNodeInfoAccounts" checked> ' + \
nodeInfoStr + '<br>\n'
else:
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="showNodeInfoAccounts"> ' + \
nodeInfoStr + '<br>\n'
nodeInfoStr = \
translate['Show version number within instance metadata']
if getConfigParam(baseDir, "showNodeInfoVersion"):
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="showNodeInfoVersion" checked> ' + \
nodeInfoStr + '<br>\n'
else:
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="showNodeInfoVersion"> ' + \
nodeInfoStr + '<br>\n'
if getConfigParam(baseDir, "verifyAllSignatures"):
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="verifyallsignatures" checked> ' + \
translate['Verify all signatures'] + '<br>\n'
else:
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="verifyallsignatures"> ' + \
translate['Verify all signatures'] + '<br>\n'
instanceStr += translate['Enabling broch mode'] + '<br>\n'
if getConfigParam(baseDir, "brochMode"):
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="brochMode" checked> ' + \
translate['Broch mode'] + '<br>\n'
else:
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="brochMode"> ' + \
translate['Broch mode'] + '<br>\n'
# Instance type
instanceStr += \
' <br><label class="labels">' + \
translate['Type of instance'] + '</label><br>\n'
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="mediaInstance" ' + mediaInstanceStr + '> ' + \
translate['This is a media instance'] + '<br>\n'
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="blogsInstance" ' + blogsInstanceStr + '> ' + \
translate['This is a blogging instance'] + '<br>\n'
instanceStr += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="newsInstance" ' + newsInstanceStr + '> ' + \
translate['This is a news instance'] + '<br>\n'
instanceStr += ' </div></details>\n'
# Role assignments section
moderators = ''
moderatorsFile = baseDir + '/accounts/moderators.txt'
if os.path.isfile(moderatorsFile):
2021-07-13 14:40:49 +00:00
with open(moderatorsFile, 'r') as f:
moderators = f.read()
# site moderators
roleAssignStr = '<details><summary class="cw">' + \
translate['Role Assignment'] + '</summary>\n'
roleAssignStr += '<div class="container">'
roleAssignStr += ' <b><label class="labels">' + \
translate['Moderators'] + '</label></b><br>\n'
roleAssignStr += ' ' + \
translate['A list of moderator nicknames. One per line.']
roleAssignStr += \
' <textarea id="message" name="moderators" placeholder="' + \
translate['List of moderator nicknames'] + \
'..." style="height:200px" spellcheck="false">' + \
moderators + '</textarea>'
# site editors
editors = ''
editorsFile = baseDir + '/accounts/editors.txt'
if os.path.isfile(editorsFile):
2021-07-13 14:40:49 +00:00
with open(editorsFile, 'r') as f:
editors = f.read()
roleAssignStr += ' <b><label class="labels">' + \
translate['Site Editors'] + '</label></b><br>\n'
roleAssignStr += ' ' + \
translate['A list of editor nicknames. One per line.']
roleAssignStr += \
' <textarea id="message" name="editors" placeholder="" ' + \
'style="height:200px" spellcheck="false">' + \
editors + '</textarea>'
# counselors
counselors = ''
counselorsFile = baseDir + '/accounts/counselors.txt'
if os.path.isfile(counselorsFile):
2021-07-13 14:40:49 +00:00
with open(counselorsFile, 'r') as f:
counselors = f.read()
roleAssignStr += ' <b><label class="labels">' + \
translate['Counselors'] + '</label></b><br>\n'
roleAssignStr += \
' <textarea id="message" name="counselors" ' + \
'placeholder="" ' + \
'style="height:200px" spellcheck="false">' + \
counselors + '</textarea>'
# artists
artists = ''
artistsFile = baseDir + '/accounts/artists.txt'
if os.path.isfile(artistsFile):
2021-07-13 14:40:49 +00:00
with open(artistsFile, 'r') as f:
artists = f.read()
roleAssignStr += ' <b><label class="labels">' + \
translate['Artists'] + '</label></b><br>\n'
roleAssignStr += \
' <textarea id="message" name="artists" ' + \
'placeholder="" ' + \
'style="height:200px" spellcheck="false">' + \
artists + '</textarea>'
roleAssignStr += ' </div></details>\n'
# Video section
peertubeStr = ' <details><summary class="cw">' + \
translate['Video Settings'] + '</summary>\n'
peertubeStr += ' <div class="container">\n'
peertubeStr += \
' <b><label class="labels">' + \
translate['Peertube Instances'] + '</label></b>\n'
idx = 'Show video previews for the following Peertube sites.'
peertubeStr += \
' <br><label class="labels">' + \
translate[idx] + '</label>\n'
peertubeInstancesStr = ''
for url in peertubeInstances:
peertubeInstancesStr += url + '\n'
peertubeStr += \
' <textarea id="message" name="ptInstances" ' + \
'style="height:200px" spellcheck="false">' + \
peertubeInstancesStr + '</textarea>\n'
peertubeStr += \
' <br><b><label class="labels">' + \
translate['YouTube Replacement Domain'] + '</label></b>\n'
YTReplacementDomain = getConfigParam(baseDir, "youtubedomain")
if not YTReplacementDomain:
YTReplacementDomain = ''
peertubeStr += \
' <input type="text" name="ytdomain" value="' + \
YTReplacementDomain + '">\n'
peertubeStr += ' </div></details>\n'
return instanceStr, roleAssignStr, peertubeStr
def _htmlEditProfileDangerZone(translate: {}) -> str:
"""danger zone section of Edit Profile screen
"""
editProfileForm = ' <details><summary class="cw">' + \
translate['Danger Zone'] + '</summary>\n'
editProfileForm += ' <div class="container">\n'
editProfileForm += ' <b><label class="labels">' + \
translate['Danger Zone'] + '</label></b><br>\n'
editProfileForm += \
' <input type="checkbox" class=dangercheckbox" ' + \
'name="deactivateThisAccount"> ' + \
translate['Deactivate this account'] + '<br>\n'
editProfileForm += ' </div></details>\n'
return editProfileForm
def _htmlEditProfileSkills(baseDir: str, nickname: str, domain: str,
translate: {}) -> str:
"""skills section of Edit Profile screen
"""
skills = getSkills(baseDir, nickname, domain)
skillsStr = ''
skillCtr = 1
if skills:
for skillDesc, skillValue in skills.items():
if isFiltered(baseDir, nickname, domain, skillDesc):
continue
skillsStr += \
'<p><input type="text" placeholder="' + translate['Skill'] + \
' ' + str(skillCtr) + '" name="skillName' + str(skillCtr) + \
'" value="' + skillDesc + '" style="width:40%">'
skillsStr += \
'<input type="range" min="1" max="100" ' + \
'class="slider" name="skillValue' + \
str(skillCtr) + '" value="' + str(skillValue) + '"></p>'
skillCtr += 1
skillsStr += \
'<p><input type="text" placeholder="Skill ' + str(skillCtr) + \
'" name="skillName' + str(skillCtr) + \
'" value="" style="width:40%">'
skillsStr += \
'<input type="range" min="1" max="100" ' + \
'class="slider" name="skillValue' + \
str(skillCtr) + '" value="50"></p>'
skillsStr += ' </div></details>\n'
editProfileForm = '<details><summary class="cw">' + \
translate['Skills'] + '</summary>\n'
editProfileForm += ' <div class="container">\n'
editProfileForm += \
' <b><label class="labels">' + \
translate['Skills'] + '</label></b><br>\n'
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 += ' <label class="labels">' + \
translate[idx] + '</label>\n'
editProfileForm += skillsStr
return editProfileForm
def _htmlEditProfileGitProjects(baseDir: str, nickname: str, domain: str,
translate: {}) -> str:
"""git projects section of edit profile screen
"""
gitProjectsStr = ''
gitProjectsFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/gitprojects.txt'
if os.path.isfile(gitProjectsFilename):
with open(gitProjectsFilename, 'r') as gitProjectsFile:
gitProjectsStr = gitProjectsFile.read()
editProfileForm = '<details><summary class="cw">' + \
translate['Git Projects'] + '</summary>\n'
editProfileForm += ' <div class="container">\n'
idx = 'List of project names that you wish to receive git patches for'
editProfileForm += \
' <label class="labels">' + \
translate[idx] + '</label>\n'
editProfileForm += \
' <textarea id="message" name="gitProjects" ' + \
'style="height:100px" spellcheck="false">' + \
gitProjectsStr + '</textarea>\n'
editProfileForm += ' </div></details>\n'
return editProfileForm
def _htmlEditProfileFiltering(baseDir: str, nickname: str, domain: str,
userAgentsBlocked: str, translate: {}) -> str:
"""Filtering and blocking section of edit profile screen
"""
filterStr = ''
filterFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/filters.txt'
if os.path.isfile(filterFilename):
with open(filterFilename, 'r') as filterfile:
filterStr = filterfile.read()
switchStr = ''
switchFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/replacewords.txt'
if os.path.isfile(switchFilename):
with open(switchFilename, 'r') as switchfile:
switchStr = switchfile.read()
autoTags = ''
autoTagsFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/autotags.txt'
if os.path.isfile(autoTagsFilename):
with open(autoTagsFilename, 'r') as autoTagsFile:
autoTags = autoTagsFile.read()
autoCW = ''
autoCWFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/autocw.txt'
if os.path.isfile(autoCWFilename):
with open(autoCWFilename, 'r') as autoCWFile:
autoCW = autoCWFile.read()
blockedStr = ''
blockedFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/blocking.txt'
if os.path.isfile(blockedFilename):
with open(blockedFilename, 'r') as blockedfile:
blockedStr = blockedfile.read()
dmAllowedInstancesStr = ''
dmAllowedInstancesFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/dmAllowedInstances.txt'
if os.path.isfile(dmAllowedInstancesFilename):
with open(dmAllowedInstancesFilename, 'r') as dmAllowedInstancesFile:
dmAllowedInstancesStr = dmAllowedInstancesFile.read()
allowedInstancesStr = ''
allowedInstancesFilename = \
2021-07-13 21:59:53 +00:00
acctDir(baseDir, nickname, domain) + '/allowedinstances.txt'
if os.path.isfile(allowedInstancesFilename):
with open(allowedInstancesFilename, 'r') as allowedInstancesFile:
allowedInstancesStr = allowedInstancesFile.read()
editProfileForm = '<details><summary class="cw">' + \
translate['Filtering and Blocking'] + '</summary>\n'
editProfileForm += ' <div class="container">\n'
editProfileForm += \
'<label class="labels">' + \
translate['City for spoofed GPS image metadata'] + \
'</label><br>\n'
2021-07-13 21:59:53 +00:00
cityFilename = acctDir(baseDir, nickname, domain) + '/city.txt'
if os.path.isfile(cityFilename):
with open(cityFilename, 'r') as fp:
city = fp.read().replace('\n', '')
locationsFilename = baseDir + '/custom_locations.txt'
if not os.path.isfile(locationsFilename):
locationsFilename = baseDir + '/locations.txt'
cities = []
2021-07-13 14:40:49 +00:00
with open(locationsFilename, 'r') as f:
cities = f.readlines()
cities.sort()
editProfileForm += ' <select id="cityDropdown" ' + \
'name="cityDropdown" class="theme">\n'
city = city.lower()
for cityName in cities:
if ':' not in cityName:
continue
citySelected = ''
cityName = cityName.split(':')[0]
cityName = cityName.lower()
if city in cityName:
citySelected = ' selected'
editProfileForm += \
' <option value="' + cityName + \
'"' + citySelected.title() + '>' + \
cityName + '</option>\n'
editProfileForm += ' </select><br>\n'
editProfileForm += \
' <b><label class="labels">' + \
translate['Filtered words'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">' + \
translate['One per line'] + '</label>\n'
editProfileForm += ' <textarea id="message" ' + \
'name="filteredWords" style="height:200px" spellcheck="false">' + \
filterStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Word Replacements'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">A -> B</label>\n'
editProfileForm += \
' <textarea id="message" name="switchWords" ' + \
'style="height:200px" spellcheck="false">' + \
switchStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Autogenerated Hashtags'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">A -> #B</label>\n'
editProfileForm += \
' <textarea id="message" name="autoTags" ' + \
'style="height:200px" spellcheck="false">' + \
autoTags + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Autogenerated Content Warnings'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">A -> B</label>\n'
editProfileForm += \
' <textarea id="message" name="autoCW" ' + \
'style="height:200px" spellcheck="true">' + autoCW + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Blocked accounts'] + '</label></b>\n'
idx = 'Blocked accounts, one per line, in the form ' + \
'nickname@domain or *@blockeddomain'
editProfileForm += \
' <br><label class="labels">' + translate[idx] + '</label>\n'
editProfileForm += \
' <textarea id="message" name="blocked" style="height:200px" ' + \
'spellcheck="false">' + blockedStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Direct Message permitted instances'] + '</label></b>\n'
idx = 'Direct messages are always allowed from these instances.'
editProfileForm += \
' <br><label class="labels">' + \
translate[idx] + '</label>\n'
editProfileForm += \
' <textarea id="message" name="dmAllowedInstances" ' + \
'style="height:200px" spellcheck="false">' + \
dmAllowedInstancesStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Federation list'] + '</label></b>\n'
idx = 'Federate only with a defined set of instances. ' + \
'One domain name per line.'
editProfileForm += \
' <br><label class="labels">' + \
translate[idx] + '</label>\n'
editProfileForm += \
' <textarea id="message" name="allowedInstances" ' + \
'style="height:200px" spellcheck="false">' + \
allowedInstancesStr + '</textarea>\n'
userAgentsBlockedStr = ''
2021-07-02 21:18:54 +00:00
for ua in userAgentsBlocked:
if userAgentsBlockedStr:
userAgentsBlockedStr += '\n'
userAgentsBlockedStr += ua
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Blocked User Agents'] + '</label></b>\n'
editProfileForm += \
' <textarea id="message" name="userAgentsBlockedStr" ' + \
'style="height:200px" spellcheck="false">' + \
userAgentsBlockedStr + '</textarea>\n'
editProfileForm += ' </div></details>\n'
return editProfileForm
def _htmlEditProfileChangePassword(translate: {}) -> str:
"""Change password section of edit profile screen
"""
2021-07-06 10:18:56 +00:00
editProfileForm = \
' <details><summary class="cw">' + \
translate['Change Password'] + '</summary>\n' + \
' <div class="container">\n' + \
'<label class="labels">' + translate['Change Password'] + \
2021-07-06 10:18:56 +00:00
'</label><br>\n' + \
' <input type="text" name="password" ' + \
'value=""><br>\n' + \
'<label class="labels">' + translate['Confirm Password'] + \
2021-07-06 10:18:56 +00:00
'</label><br>\n' + \
' <input type="text" name="passwordconfirm" value="">\n' + \
' </div></details>\n'
return editProfileForm
def _htmlEditProfileBackground(newsInstance: 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.'
2021-07-06 10:18:56 +00:00
editProfileForm = \
' <details><summary class="cw">' + \
translate['Background Images'] + '</summary>\n' + \
' <div class="container">\n' + \
' <label class="labels">' + translate[idx] + '</label><br><br>\n'
if not newsInstance:
imageFormats = getImageFormats()
editProfileForm += \
' <label class="labels">' + \
2021-07-06 10:18:56 +00:00
translate['Background image'] + '</label>\n' + \
' <input type="file" id="image" name="image"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += \
' <br><label class="labels">' + \
translate['Timeline banner image'] + '</label>\n' + \
' <input type="file" id="banner" name="banner"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += \
' <br><label class="labels">' + \
translate['Search banner image'] + '</label>\n' + \
' <input type="file" id="search_banner" ' + \
'name="search_banner"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += \
' <br><label class="labels">' + \
translate['Left column image'] + '</label>\n' + \
' <input type="file" id="left_col_image" ' + \
'name="left_col_image"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += \
' <br><label class="labels">' + \
translate['Right column image'] + '</label>\n' + \
' <input type="file" id="right_col_image" ' + \
'name="right_col_image"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += ' </div></details>\n'
return editProfileForm
def _htmlEditProfileContactInfo(nickname: str,
emailAddress: str,
xmppAddress: str,
matrixAddress: str,
ssbAddress: str,
toxAddress: str,
briarAddress: str,
jamiAddress: str,
cwtchAddress: str,
PGPfingerprint: str,
PGPpubKey: str,
translate: {}) -> str:
"""Contact Information section of edit profile screen
"""
editProfileForm = ' <details><summary class="cw">' + \
translate['Contact Details'] + '</summary>\n'
editProfileForm += '<div class="container">'
editProfileForm += '<label class="labels">' + \
translate['Email'] + '</label><br>\n'
editProfileForm += \
' <input type="text" name="email" value="' + emailAddress + '">\n'
editProfileForm += \
'<label class="labels">' + translate['XMPP'] + '</label><br>\n'
editProfileForm += \
' <input type="text" name="xmppAddress" value="' + \
xmppAddress + '">\n'
editProfileForm += '<label class="labels">' + \
translate['Matrix'] + '</label><br>\n'
editProfileForm += \
' <input type="text" name="matrixAddress" value="' + \
matrixAddress + '">\n'
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">SSB</label><br>\n' + \
' <input type="text" name="ssbAddress" value="' + \
ssbAddress + '">\n'
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">Tox</label><br>\n' + \
' <input type="text" name="toxAddress" value="' + \
toxAddress + '">\n'
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">Briar</label><br>\n' + \
' <input type="text" name="briarAddress" value="' + \
briarAddress + '">\n'
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">Jami</label><br>\n' + \
' <input type="text" name="jamiAddress" value="' + \
jamiAddress + '">\n'
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">Cwtch</label><br>\n' + \
' <input type="text" name="cwtchAddress" value="' + \
cwtchAddress + '">\n'
editProfileForm += \
'<label class="labels">' + \
2021-07-06 10:18:56 +00:00
translate['PGP Fingerprint'] + '</label><br>\n' + \
' <input type="text" name="openpgp" value="' + \
2021-07-06 10:18:56 +00:00
PGPfingerprint + '">\n' + \
'<label class="labels">' + translate['PGP'] + '</label><br>\n' + \
' <textarea id="message" placeholder=' + \
'"-----BEGIN PGP PUBLIC KEY BLOCK-----" name="pgp" ' + \
'style="height:600px" spellcheck="false">' + \
2021-07-06 10:18:56 +00:00
PGPpubKey + '</textarea>\n' + \
'<a href="/users/' + nickname + \
'/followingaccounts"><label class="labels">' + \
2021-07-06 10:18:56 +00:00
translate['Following'] + '</label></a><br>\n' + \
' </div></details>\n'
return editProfileForm
def _htmlEditProfileOptions(manuallyApprovesFollowers: str,
isBot: str, isGroup: str,
followDMs: str, removeTwitter: str,
notifyLikes: str, hideLikeButton: str,
translate: {}) -> str:
"""option checkboxes section of edit profile screen
"""
editProfileForm = ' <div class="container">\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="approveFollowers" ' + manuallyApprovesFollowers + \
'> ' + translate['Approve follower requests'] + '<br>\n'
editProfileForm += \
' <input type="checkbox" ' + \
'class="profilecheckbox" name="isBot" ' + \
isBot + '> ' + translate['This is a bot account'] + '<br>\n'
editProfileForm += \
' <input type="checkbox" ' + \
'class="profilecheckbox" name="isGroup" ' + isGroup + '> ' + \
translate['This is a group account'] + '<br>\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="followDMs" ' + followDMs + '> ' + \
translate['Only people I follow can send me DMs'] + '<br>\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="removeTwitter" ' + removeTwitter + '> ' + \
translate['Remove Twitter posts'] + '<br>\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="notifyLikes" ' + notifyLikes + '> ' + \
translate['Notify when posts are liked'] + '<br>\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="hideLikeButton" ' + hideLikeButton + '> ' + \
translate["Don't show the Like button"] + '<br>\n'
editProfileForm += ' </div>\n'
return editProfileForm
def _htmlEditProfileMain(displayNickname: str, bioStr: str,
movedTo: str, donateUrl: str,
blogAddress: str, actorJson: {},
translate: {}) -> str:
"""main info on edit profile screen
"""
imageFormats = getImageFormats()
editProfileForm = ' <div class="container">\n'
2021-07-06 10:18:56 +00:00
editProfileForm += \
2021-07-06 10:18:56 +00:00
' <label class="labels">' + \
translate['Nickname'] + '</label>\n' + \
' <input type="text" name="displayNickname" value="' + \
displayNickname + '"><br>\n'
2021-07-06 10:18:56 +00:00
editProfileForm += \
2021-07-06 10:18:56 +00:00
' <label class="labels">' + \
translate['Your bio'] + '</label>\n' + \
' <textarea id="message" name="bio" style="height:200px" ' + \
'spellcheck="true">' + bioStr + '</textarea>\n'
2021-07-06 10:18:56 +00:00
editProfileForm += \
' <label class="labels">' + translate['Avatar image'] + \
2021-07-06 10:18:56 +00:00
'</label>\n' + \
' <input type="file" id="avatar" name="avatar"' + \
' accept="' + imageFormats + '">\n'
occupationName = ''
if actorJson.get('hasOccupation'):
occupationName = getOccupationName(actorJson)
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">' + \
translate['Occupation'] + ':</label><br>\n' + \
' <input type="text" ' + \
'name="occupationName" value="' + occupationName + '">\n'
alsoKnownAsStr = ''
if actorJson.get('alsoKnownAs'):
alsoKnownAs = actorJson['alsoKnownAs']
ctr = 0
for altActor in alsoKnownAs:
if ctr > 0:
alsoKnownAsStr += ', '
ctr += 1
alsoKnownAsStr += altActor
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">' + \
translate['Other accounts'] + ':</label><br>\n' + \
' <input type="text" placeholder="https://..." ' + \
'name="alsoKnownAs" value="' + alsoKnownAsStr + '">\n'
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">' + \
translate['Moved to new account address'] + ':</label><br>\n' + \
' <input type="text" placeholder="https://..." ' + \
'name="movedTo" value="' + movedTo + '">\n'
2021-07-06 10:18:56 +00:00
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">' + \
translate['Donations link'] + '</label><br>\n' + \
' <input type="text" placeholder="https://..." ' + \
'name="donateUrl" value="' + donateUrl + '">\n'
2021-07-06 10:18:56 +00:00
editProfileForm += \
2021-07-06 10:18:56 +00:00
'<label class="labels">Blog</label><br>\n' + \
' <input type="text" name="blogAddress" value="' + \
2021-07-06 10:18:56 +00:00
blogAddress + '">\n' + \
' </div>\n'
return editProfileForm
def _htmlEditProfileTopBanner(baseDir: str,
nickname: str, domain: str, domainFull: str,
defaultTimeline: str, bannerFile: str,
path: str, accessKeys: {}, translate: {}) -> str:
"""top banner on edit profile screen
"""
editProfileForm = \
2021-07-06 10:18:56 +00:00
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" alt="" /></a>\n'
editProfileForm += \
'<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="' + path + '/profiledata">\n'
editProfileForm += ' <div class="vertical-center">\n'
editProfileForm += \
' <h1>' + translate['Profile for'] + \
' ' + nickname + '@' + domainFull + '</h1>'
editProfileForm += ' <div class="container">\n'
editProfileForm += \
' <center>\n' + \
' <input type="submit" name="submitProfile" ' + \
'accesskey="' + accessKeys['submitButton'] + '" ' + \
'value="' + translate['Submit'] + '">\n' + \
' </center>\n'
editProfileForm += ' </div>\n'
if scheduledPostsExist(baseDir, nickname, domain):
editProfileForm += ' <div class="container">\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="removeScheduledPosts"> ' + \
translate['Remove scheduled posts'] + '<br>\n'
editProfileForm += ' </div>\n'
return editProfileForm
2020-11-09 22:44:03 +00:00
def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str,
defaultTimeline: str, theme: str,
2021-02-05 19:14:27 +00:00
peertubeInstances: [],
2021-05-30 14:27:26 +00:00
textModeBanner: str, city: str,
userAgentsBlocked: str,
2021-05-30 14:27:26 +00:00
accessKeys: {}) -> str:
2020-11-09 22:44:03 +00:00
"""Shows the edit profile screen
"""
path = path.replace('/inbox', '').replace('/outbox', '')
path = path.replace('/shares', '')
nickname = getNicknameFromActor(path)
if not nickname:
return ''
2020-12-16 11:19:16 +00:00
domainFull = getFullDomain(domain, port)
2020-11-09 22:44:03 +00:00
2021-07-13 21:59:53 +00:00
actorFilename = acctDir(baseDir, nickname, domain) + '.json'
2020-11-09 22:44:03 +00:00
if not os.path.isfile(actorFilename):
return ''
# filename of the banner shown at the top
2020-12-20 18:16:53 +00:00
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
2020-11-09 22:44:03 +00:00
displayNickname = nickname
2021-06-27 16:22:55 +00:00
isBot = isGroup = followDMs = removeTwitter = ''
notifyLikes = hideLikeButton = mediaInstanceStr = ''
blogsInstanceStr = newsInstanceStr = movedTo = ''
bioStr = donateUrl = emailAddress = PGPpubKey = ''
PGPfingerprint = xmppAddress = matrixAddress = ''
ssbAddress = blogAddress = toxAddress = jamiAddress = ''
cwtchAddress = briarAddress = manuallyApprovesFollowers = ''
2020-11-09 22:44:03 +00:00
actorJson = loadJson(actorFilename)
if actorJson:
2021-01-12 12:35:30 +00:00
if actorJson.get('movedTo'):
movedTo = actorJson['movedTo']
2020-11-09 22:44:03 +00:00
donateUrl = getDonationUrl(actorJson)
xmppAddress = getXmppAddress(actorJson)
matrixAddress = getMatrixAddress(actorJson)
ssbAddress = getSSBAddress(actorJson)
blogAddress = getBlogAddress(actorJson)
toxAddress = getToxAddress(actorJson)
2020-12-24 16:48:03 +00:00
briarAddress = getBriarAddress(actorJson)
2020-11-29 12:50:41 +00:00
jamiAddress = getJamiAddress(actorJson)
2021-06-27 11:48:03 +00:00
cwtchAddress = getCwtchAddress(actorJson)
2020-11-09 22:44:03 +00:00
emailAddress = getEmailAddress(actorJson)
PGPpubKey = getPGPpubKey(actorJson)
PGPfingerprint = getPGPfingerprint(actorJson)
if actorJson.get('name'):
2020-12-19 12:55:40 +00:00
if not isFiltered(baseDir, nickname, domain, actorJson['name']):
displayNickname = actorJson['name']
2020-11-09 22:44:03 +00:00
if actorJson.get('summary'):
bioStr = \
actorJson['summary'].replace('<p>', '').replace('</p>', '')
2020-12-19 12:51:13 +00:00
if isFiltered(baseDir, nickname, domain, bioStr):
bioStr = ''
2020-11-09 22:44:03 +00:00
if actorJson.get('manuallyApprovesFollowers'):
if actorJson['manuallyApprovesFollowers']:
manuallyApprovesFollowers = 'checked'
else:
manuallyApprovesFollowers = ''
if actorJson.get('type'):
if actorJson['type'] == 'Service':
isBot = 'checked'
isGroup = ''
elif actorJson['type'] == 'Group':
isGroup = 'checked'
isBot = ''
2021-07-13 21:59:53 +00:00
accountDir = acctDir(baseDir, nickname, domain)
if os.path.isfile(accountDir + '/.followDMs'):
2020-11-09 22:44:03 +00:00
followDMs = 'checked'
2021-07-13 21:59:53 +00:00
if os.path.isfile(accountDir + '/.removeTwitter'):
2020-11-09 22:44:03 +00:00
removeTwitter = 'checked'
2021-07-13 21:59:53 +00:00
if os.path.isfile(accountDir + '/.notifyLikes'):
2020-11-09 22:44:03 +00:00
notifyLikes = 'checked'
2021-07-13 21:59:53 +00:00
if os.path.isfile(accountDir + '/.hideLikeButton'):
2020-11-09 22:44:03 +00:00
hideLikeButton = 'checked'
mediaInstance = getConfigParam(baseDir, "mediaInstance")
if mediaInstance:
if mediaInstance is True:
mediaInstanceStr = 'checked'
2021-06-27 16:24:12 +00:00
blogsInstanceStr = newsInstanceStr = ''
2020-11-09 22:44:03 +00:00
newsInstance = getConfigParam(baseDir, "newsInstance")
if newsInstance:
if newsInstance is True:
newsInstanceStr = 'checked'
2021-06-27 16:24:12 +00:00
blogsInstanceStr = mediaInstanceStr = ''
2020-11-09 22:44:03 +00:00
blogsInstance = getConfigParam(baseDir, "blogsInstance")
if blogsInstance:
if blogsInstance is True:
blogsInstanceStr = 'checked'
2021-06-27 16:24:12 +00:00
mediaInstanceStr = newsInstanceStr = ''
2020-11-09 22:44:03 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
instanceStr = ''
2021-03-06 13:40:47 +00:00
roleAssignStr = ''
2020-12-24 11:56:17 +00:00
peertubeStr = ''
2020-11-09 22:44:03 +00:00
adminNickname = getConfigParam(baseDir, 'admin')
if isArtist(baseDir, nickname) or \
path.startswith('/users/' + str(adminNickname) + '/'):
graphicsStr = _htmlEditProfileGraphicDesign(baseDir, translate)
2020-11-09 22:44:03 +00:00
if adminNickname:
if path.startswith('/users/' + adminNickname + '/'):
instanceStr, roleAssignStr, peertubeStr = \
_htmlEditProfileInstance(baseDir, translate,
peertubeInstances,
mediaInstanceStr,
blogsInstanceStr,
newsInstanceStr)
2020-12-24 11:56:17 +00:00
2021-01-11 19:46:21 +00:00
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
editProfileForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
2020-11-09 22:44:03 +00:00
# keyboard navigation
2021-02-05 17:05:53 +00:00
userPathStr = '/users/' + nickname
userTimalineStr = '/users/' + nickname + '/' + defaultTimeline
2021-02-06 10:35:47 +00:00
menuTimeline = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('🏠') + ' ' + \
translate['Switch to timeline view']
2021-02-06 10:35:47 +00:00
menuProfile = \
2021-02-06 10:46:03 +00:00
htmlHideFromScreenReader('👤') + ' ' + \
translate['Switch to profile view']
2021-02-05 17:05:53 +00:00
navLinks = {
2021-02-06 10:35:47 +00:00
menuProfile: userPathStr,
menuTimeline: userTimalineStr
2021-02-05 17:05:53 +00:00
}
2021-04-22 11:51:19 +00:00
navAccessKeys = {
menuProfile: 'p',
menuTimeline: 't'
}
editProfileForm += htmlKeyboardNavigation(textModeBanner,
navLinks, navAccessKeys)
# top banner
2021-02-05 15:46:05 +00:00
editProfileForm += \
_htmlEditProfileTopBanner(baseDir, nickname, domain, domainFull,
defaultTimeline, bannerFile,
path, accessKeys, translate)
2020-11-09 22:44:03 +00:00
# main info
2021-01-22 22:12:31 +00:00
editProfileForm += \
_htmlEditProfileMain(displayNickname, bioStr, movedTo, donateUrl,
blogAddress, actorJson, translate)
2021-03-06 11:57:40 +00:00
# Option checkboxes
editProfileForm += \
_htmlEditProfileOptions(manuallyApprovesFollowers,
isBot, isGroup, followDMs, removeTwitter,
notifyLikes, hideLikeButton, translate)
2021-03-06 11:57:40 +00:00
# Contact information
2020-11-09 22:44:03 +00:00
editProfileForm += \
_htmlEditProfileContactInfo(nickname, emailAddress,
xmppAddress, matrixAddress,
ssbAddress, toxAddress,
briarAddress, jamiAddress,
cwtchAddress, PGPfingerprint,
PGPpubKey, translate)
2021-03-06 11:55:09 +00:00
# Customize images and banners
editProfileForm += _htmlEditProfileBackground(newsInstance, translate)
2021-03-06 11:55:09 +00:00
# Change password
editProfileForm += _htmlEditProfileChangePassword(translate)
2020-11-09 22:44:03 +00:00
# Filtering and blocking section
2020-11-09 22:44:03 +00:00
editProfileForm += \
_htmlEditProfileFiltering(baseDir, nickname, domain,
userAgentsBlocked, translate)
# git projects section
2020-11-09 22:44:03 +00:00
editProfileForm += \
_htmlEditProfileGitProjects(baseDir, nickname, domain, translate)
# Skills section
2020-11-09 22:44:03 +00:00
editProfileForm += \
_htmlEditProfileSkills(baseDir, nickname, domain, translate)
editProfileForm += roleAssignStr + peertubeStr + graphicsStr + instanceStr
2021-03-06 15:18:56 +00:00
# danger zone section
editProfileForm += _htmlEditProfileDangerZone(translate)
editProfileForm += ' <div class="container">\n'
editProfileForm += \
' <center>\n' + \
' <input type="submit" name="submitProfile" value="' + \
translate['Submit'] + '">\n' + \
' </center>\n'
editProfileForm += ' </div>\n'
2020-11-09 22:44:03 +00:00
editProfileForm += ' </div>\n'
editProfileForm += '</form>\n'
editProfileForm += htmlFooter()
return editProfileForm
def _individualFollowAsHtml(translate: {},
baseDir: str, session,
cachedWebfingers: {},
personCache: {}, domain: str,
followUrl: str,
authorized: bool,
actorNickname: str,
httpPrefix: str,
projectVersion: str,
dormant: bool,
2021-03-14 19:22:58 +00:00
debug: bool,
buttons=[]) -> str:
2020-11-09 22:44:03 +00:00
"""An individual follow entry on the profile screen
"""
followUrlNickname = getNicknameFromActor(followUrl)
followUrlDomain, followUrlPort = getDomainFromActor(followUrl)
followUrlDomainFull = getFullDomain(followUrlDomain, followUrlPort)
titleStr = '@' + followUrlNickname + '@' + followUrlDomainFull
2020-11-09 22:44:03 +00:00
avatarUrl = getPersonAvatarUrl(baseDir, followUrl, personCache, True)
if not avatarUrl:
avatarUrl = followUrl + '/avatar.png'
# lookup the correct webfinger for the followUrl
followUrlHandle = followUrlNickname + '@' + followUrlDomainFull
followUrlWf = \
webfingerHandle(session, followUrlHandle, httpPrefix,
cachedWebfingers,
2021-03-14 19:22:58 +00:00
domain, __version__, debug)
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
avatarUrl2, displayName) = getPersonBox(baseDir, session,
followUrlWf,
personCache, projectVersion,
httpPrefix, followUrlNickname,
domain, 'outbox', 43036)
if avatarUrl2:
avatarUrl = avatarUrl2
if displayName:
2021-02-12 17:08:48 +00:00
displayName = \
addEmojiToDisplayName(baseDir, httpPrefix,
actorNickname, domain,
displayName, False)
2021-01-12 10:23:25 +00:00
titleStr = displayName
if dormant:
titleStr += ' 💤'
2020-11-09 22:44:03 +00:00
buttonsStr = ''
if authorized:
for b in buttons:
if b == 'block':
buttonsStr += \
'<a href="/users/' + actorNickname + \
'?options=' + followUrl + \
';1;' + avatarUrl + '"><button class="buttonunfollow">' + \
translate['Block'] + '</button></a>\n'
2021-07-06 10:18:56 +00:00
elif b == 'unfollow':
2020-11-09 22:44:03 +00:00
buttonsStr += \
'<a href="/users/' + actorNickname + \
'?options=' + followUrl + \
';1;' + avatarUrl + '"><button class="buttonunfollow">' + \
translate['Unfollow'] + '</button></a>\n'
resultStr = '<div class="container">\n'
resultStr += \
'<a href="/users/' + actorNickname + '?options=' + \
followUrl + ';1;' + avatarUrl + '">\n'
resultStr += '<p><img loading="lazy" src="' + avatarUrl + '" alt=" ">'
resultStr += titleStr + '</a>' + buttonsStr + '</p>\n'
resultStr += '</div>\n'
return resultStr