Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon

main
Bob Mottram 2021-07-06 15:38:32 +01:00
commit e5af948446
26 changed files with 391 additions and 468 deletions

View File

@ -77,7 +77,7 @@ def outboxAnnounce(recentPostsCache: {},
nickname, domain, debug) nickname, domain, debug)
return True return True
elif messageJson['type'] == 'Undo': elif messageJson['type'] == 'Undo':
if not isinstance(messageJson['object'], dict): if not hasObjectDict(messageJson):
return False return False
if not messageJson['object'].get('type'): if not messageJson['object'].get('type'):
return False return False

View File

@ -298,7 +298,7 @@ def inboxMessageHasParams(messageJson: {}) -> bool:
return False return False
# object should be a dict or a string # object should be a dict or a string
if not isinstance(messageJson['object'], dict): if not hasObjectDict(messageJson):
if not isinstance(messageJson['object'], str): if not isinstance(messageJson['object'], str):
print('WARN: object from ' + str(messageJson['actor']) + print('WARN: object from ' + str(messageJson['actor']) +
' should be a dict or string, but is actually: ' + ' should be a dict or string, but is actually: ' +

View File

@ -18,6 +18,7 @@ from utils import getImageExtensions
from utils import getVideoExtensions from utils import getVideoExtensions
from utils import getAudioExtensions from utils import getAudioExtensions
from utils import getMediaExtensions from utils import getMediaExtensions
from utils import hasObjectDict
from shutil import copyfile from shutil import copyfile
from shutil import rmtree from shutil import rmtree
from shutil import move from shutil import move
@ -30,7 +31,7 @@ def replaceYouTube(postJsonObject: {}, replacementDomain: str) -> None:
""" """
if not replacementDomain: if not replacementDomain:
return return
if not isinstance(postJsonObject['object'], dict): if not hasObjectDict(postJsonObject):
return return
if not postJsonObject['object'].get('content'): if not postJsonObject['object'].get('content'):
return return

View File

@ -1296,7 +1296,7 @@ def _postIsAddressedToFollowers(baseDir: str,
toList = [] toList = []
ccList = [] ccList = []
if postJsonObject['type'] != 'Update' and \ if postJsonObject['type'] != 'Update' and \
isinstance(postJsonObject['object'], dict): hasObjectDict(postJsonObject):
if postJsonObject['object'].get('to'): if postJsonObject['object'].get('to'):
toList = postJsonObject['object']['to'] toList = postJsonObject['object']['to']
if postJsonObject['object'].get('cc'): if postJsonObject['object'].get('cc'):
@ -2169,7 +2169,7 @@ def _addFollowersToPublicPost(postJsonObject: {}) -> None:
if postJsonObject.get('cc'): if postJsonObject.get('cc'):
return return
postJsonObject['cc'] = postJsonObject['actor'] + '/followers' postJsonObject['cc'] = postJsonObject['actor'] + '/followers'
elif isinstance(postJsonObject['object'], dict): elif hasObjectDict(postJsonObject):
if not postJsonObject['object'].get('to'): if not postJsonObject['object'].get('to'):
return return
if len(postJsonObject['object']['to']) > 1: if len(postJsonObject['object']['to']) > 1:
@ -2433,7 +2433,7 @@ def sendToNamedAddresses(session, baseDir: str,
if not postJsonObject.get('object'): if not postJsonObject.get('object'):
return return
isProfileUpdate = False isProfileUpdate = False
if isinstance(postJsonObject['object'], dict): if hasObjectDict(postJsonObject):
if _isProfileUpdate(postJsonObject): if _isProfileUpdate(postJsonObject):
# use the original object, which has a 'to' # use the original object, which has a 'to'
recipientsObject = postJsonObject recipientsObject = postJsonObject

View File

@ -126,7 +126,7 @@ def isQuestion(postObjectJson: {}) -> bool:
if postObjectJson['type'] != 'Create' and \ if postObjectJson['type'] != 'Create' and \
postObjectJson['type'] != 'Update': postObjectJson['type'] != 'Update':
return False return False
if not isinstance(postObjectJson['object'], dict): if not hasObjectDict(postObjectJson):
return False return False
if not postObjectJson['object'].get('type'): if not postObjectJson['object'].get('type'):
return False return False

View File

@ -366,16 +366,12 @@ def _setThemeFromDict(baseDir: str, name: str,
with open(filename, 'w+') as cssfile: with open(filename, 'w+') as cssfile:
cssfile.write(css) cssfile.write(css)
if bgParams.get('login'): screenName = (
_setBackgroundFormat(baseDir, name, 'login', bgParams['login']) 'login', 'follow', 'options', 'search', 'welcome'
if bgParams.get('follow'): )
_setBackgroundFormat(baseDir, name, 'follow', bgParams['follow']) for s in screenName:
if bgParams.get('options'): if bgParams.get(s):
_setBackgroundFormat(baseDir, name, 'options', bgParams['options']) _setBackgroundFormat(baseDir, name, s, bgParams[s])
if bgParams.get('search'):
_setBackgroundFormat(baseDir, name, 'search', bgParams['search'])
if bgParams.get('welcome'):
_setBackgroundFormat(baseDir, name, 'welcome', bgParams['welcome'])
def _setBackgroundFormat(baseDir: str, name: str, def _setBackgroundFormat(baseDir: str, name: str,

View File

@ -1401,7 +1401,7 @@ def deletePost(baseDir: str, httpPrefix: str,
# remove from moderation index file # remove from moderation index file
if hasObject: if hasObject:
if isinstance(postJsonObject['object'], dict): if hasObjectDict(postJsonObject):
if postJsonObject['object'].get('moderationStatus'): if postJsonObject['object'].get('moderationStatus'):
if postJsonObject.get('id'): if postJsonObject.get('id'):
postId = removeIdEnding(postJsonObject['id']) postId = removeIdEnding(postJsonObject['id'])
@ -2536,3 +2536,15 @@ def getPortFromDomain(domain: str) -> int:
if portStr.isdigit(): if portStr.isdigit():
return int(portStr) return int(portStr)
return None return None
def validUrlPrefix(url: str) -> bool:
"""Does the given url have a valid prefix?
"""
if '/' not in url:
return False
prefixes = ('https:', 'http:', 'hyper:', 'i2p:', 'gnunet:')
for pre in prefixes:
if url.startswith(pre):
return True
return False

View File

@ -117,8 +117,7 @@ def _htmlCalendarDay(personCache: {}, cssCache: {}, translate: {},
if '/users/' in actor: if '/users/' in actor:
calActor = '/users/' + actor.split('/users/')[1] calActor = '/users/' + actor.split('/users/')[1]
instanceTitle = \ instanceTitle = getConfigParam(baseDir, 'instanceTitle')
getConfigParam(baseDir, 'instanceTitle')
calendarStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) calendarStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
calendarStr += '<main><table class="calendar">\n' calendarStr += '<main><table class="calendar">\n'
calendarStr += '<caption class="calendar__banner--month">\n' calendarStr += '<caption class="calendar__banner--month">\n'
@ -369,20 +368,10 @@ def htmlCalendar(personCache: {}, cssCache: {}, translate: {},
calendarStr += '</caption>\n' calendarStr += '</caption>\n'
calendarStr += '<thead>\n' calendarStr += '<thead>\n'
calendarStr += '<tr>\n' calendarStr += '<tr>\n'
calendarStr += ' <th scope="col" class="calendar__day__header">' + \ days = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
translate['Sun'] + '</th>\n' for d in days:
calendarStr += ' <th scope="col" class="calendar__day__header">' + \ calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate['Mon'] + '</th>\n' translate[d] + '</th>\n'
calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate['Tue'] + '</th>\n'
calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate['Wed'] + '</th>\n'
calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate['Thu'] + '</th>\n'
calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate['Fri'] + '</th>\n'
calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate['Sat'] + '</th>\n'
calendarStr += '</tr>\n' calendarStr += '</tr>\n'
calendarStr += '</thead>\n' calendarStr += '</thead>\n'
calendarStr += '<tbody>\n' calendarStr += '<tbody>\n'

View File

@ -51,10 +51,8 @@ def _getLeftColumnShares(baseDir: str,
if '<' in sharedesc or '?' in sharedesc: if '<' in sharedesc or '?' in sharedesc:
continue continue
contactActor = item['actor'] contactActor = item['actor']
shareLink = actor + \ shareLink = actor + '?replydm=sharedesc:' + \
'?replydm=sharedesc:' + \ sharedesc.replace(' ', '_') + '?mention=' + contactActor
sharedesc.replace(' ', '_') + \
'?mention=' + contactActor
linksList.append(sharedesc + ' ' + shareLink) linksList.append(sharedesc + ' ' + shareLink)
ctr += 1 ctr += 1
if ctr >= maxSharesInLeftColumn: if ctr >= maxSharesInLeftColumn:
@ -89,16 +87,14 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
if os.path.isfile(leftColumnImageFilename): if os.path.isfile(leftColumnImageFilename):
editImageClass = 'leftColEditImage' editImageClass = 'leftColEditImage'
htmlStr += \ htmlStr += \
'\n <center>\n' + \ '\n <center>\n <img class="leftColImg" ' + \
' <img class="leftColImg" ' + \
'alt="" loading="lazy" src="/users/' + \ 'alt="" loading="lazy" src="/users/' + \
nickname + '/' + leftImageFile + '" />\n' + \ nickname + '/' + leftImageFile + '" />\n' + \
' </center>\n' ' </center>\n'
if showBackButton: if showBackButton:
htmlStr += \ htmlStr += \
' <div>' + \ ' <div> <a href="' + timelinePath + '">' + \
' <a href="' + timelinePath + '">' + \
'<button class="cancelbtn">' + \ '<button class="cancelbtn">' + \
translate['Go Back'] + '</button></a>\n' translate['Go Back'] + '</button></a>\n'
@ -112,14 +108,11 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
if editor: if editor:
# show the edit icon # show the edit icon
htmlStr += \ htmlStr += \
' <a href="' + \ ' <a href="/users/' + nickname + '/editlinks" ' + \
'/users/' + nickname + '/editlinks" ' + \
'accesskey="' + accessKeys['menuEdit'] + '">' + \ 'accesskey="' + accessKeys['menuEdit'] + '">' + \
'<img class="' + editImageClass + \ '<img class="' + editImageClass + '" loading="lazy" alt="' + \
'" loading="lazy" alt="' + \
translate['Edit Links'] + ' | " title="' + \ translate['Edit Links'] + ' | " title="' + \
translate['Edit Links'] + '" src="/' + \ translate['Edit Links'] + '" src="/icons/edit.png" /></a>\n'
'icons/edit.png" /></a>\n'
# RSS icon # RSS icon
if nickname != 'news': if nickname != 'news':
@ -134,10 +127,8 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
else: else:
rssTitle = translate['RSS feed for this site'] rssTitle = translate['RSS feed for this site']
rssIconStr = \ rssIconStr = \
' <a href="' + rssUrl + '">' + \ ' <a href="' + rssUrl + '"><img class="' + editImageClass + \
'<img class="' + editImageClass + \ '" loading="lazy" alt="' + rssTitle + '" title="' + rssTitle + \
'" loading="lazy" alt="' + rssTitle + \
'" title="' + rssTitle + \
'" src="/icons/logorss.png" /></a>\n' '" src="/icons/logorss.png" /></a>\n'
if rssIconAtTop: if rssIconAtTop:
htmlStr += rssIconStr htmlStr += rssIconStr
@ -326,8 +317,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
theme, accessKeys) theme, accessKeys)
else: else:
if editor: if editor:
htmlStr += '<br><br><br>\n' htmlStr += '<br><br><br>\n<center>\n '
htmlStr += '<center>\n '
htmlStr += translate['Select the edit icon to add web links'] htmlStr += translate['Select the edit icon to add web links']
htmlStr += '\n</center>\n' htmlStr += '\n</center>\n'
@ -376,7 +366,8 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
translate['Switch to timeline view'] + '" alt="' + \ translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '" ' + \ translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n' 'accesskey="' + accessKeys['menuTimeline'] + '">\n'
editLinksForm += '<img loading="lazy" class="timeline-banner" ' + \ editLinksForm += \
'<img loading="lazy" class="timeline-banner" ' + \
'alt = "" src="' + \ 'alt = "" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \ '/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \
'</header>\n' '</header>\n'
@ -411,8 +402,7 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
'<br>' '<br>'
editLinksForm += \ editLinksForm += \
' <textarea id="message" name="editedLinks" ' + \ ' <textarea id="message" name="editedLinks" ' + \
'style="height:80vh" spellcheck="false">' + \ 'style="height:80vh" spellcheck="false">' + linksStr + '</textarea>'
linksStr + '</textarea>'
editLinksForm += \ editLinksForm += \
'</div>' '</div>'

View File

@ -253,8 +253,7 @@ def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
if faviconUrl: if faviconUrl:
faviconLink = \ faviconLink = \
'<img loading="lazy" src="' + faviconUrl + '" ' + \ '<img loading="lazy" src="' + faviconUrl + '" ' + \
'alt="" ' + \ 'alt="" ' + _getBrokenFavSubstitute() + '/>'
_getBrokenFavSubstitute() + '/>'
moderatedItem = item[5] moderatedItem = item[5]
htmlStr += separatorStr htmlStr += separatorStr
if moderatedItem and 'vote:' + nickname in item[2]: if moderatedItem and 'vote:' + nickname in item[2]:
@ -270,8 +269,7 @@ def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
'<a href="' + url + '" target="_blank" ' + \ '<a href="' + url + '" target="_blank" ' + \
'rel="nofollow noopener noreferrer">' + \ 'rel="nofollow noopener noreferrer">' + \
'<span class="newswireItemVotedOn">' + \ '<span class="newswireItemVotedOn">' + \
faviconLink + title + \ faviconLink + title + '</span></a>' + totalVotesStr
'</span></a>' + totalVotesStr
if moderator: if moderator:
htmlStr += \ htmlStr += \
' ' + dateShown + '<a href="/users/' + nickname + \ ' ' + dateShown + '<a href="/users/' + nickname + \
@ -312,8 +310,7 @@ def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
htmlStr += '<p class="newswireItem">' + \ htmlStr += '<p class="newswireItem">' + \
'<a href="' + url + '" target="_blank" ' + \ '<a href="' + url + '" target="_blank" ' + \
'rel="nofollow noopener noreferrer">' + \ 'rel="nofollow noopener noreferrer">' + \
faviconLink + title + '</a>' + \ faviconLink + title + '</a>' + totalVotesStr
totalVotesStr
htmlStr += ' <span class="newswireDate">' htmlStr += ' <span class="newswireDate">'
htmlStr += dateShown + '</span></p>\n' htmlStr += dateShown + '</span></p>\n'

View File

@ -137,8 +137,7 @@ def htmlConfirmRemoveSharedItem(cssCache: {}, translate: {}, baseDir: str,
if os.path.isfile(baseDir + '/follow.css'): if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css' cssFilename = baseDir + '/follow.css'
instanceTitle = \ instanceTitle = getConfigParam(baseDir, 'instanceTitle')
getConfigParam(baseDir, 'instanceTitle')
sharesStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) sharesStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
sharesStr += '<div class="follow">\n' sharesStr += '<div class="follow">\n'
sharesStr += ' <div class="followAvatar">\n' sharesStr += ' <div class="followAvatar">\n'
@ -186,8 +185,7 @@ def htmlConfirmFollow(cssCache: {}, translate: {}, baseDir: str,
if os.path.isfile(baseDir + '/follow.css'): if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css' cssFilename = baseDir + '/follow.css'
instanceTitle = \ instanceTitle = getConfigParam(baseDir, 'instanceTitle')
getConfigParam(baseDir, 'instanceTitle')
followStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) followStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
followStr += '<div class="follow">\n' followStr += '<div class="follow">\n'
followStr += ' <div class="followAvatar">\n' followStr += ' <div class="followAvatar">\n'
@ -232,8 +230,7 @@ def htmlConfirmUnfollow(cssCache: {}, translate: {}, baseDir: str,
if os.path.isfile(baseDir + '/follow.css'): if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css' cssFilename = baseDir + '/follow.css'
instanceTitle = \ instanceTitle = getConfigParam(baseDir, 'instanceTitle')
getConfigParam(baseDir, 'instanceTitle')
followStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) followStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
followStr += '<div class="follow">\n' followStr += '<div class="follow">\n'
followStr += ' <div class="followAvatar">\n' followStr += ' <div class="followAvatar">\n'
@ -279,8 +276,7 @@ def htmlConfirmUnblock(cssCache: {}, translate: {}, baseDir: str,
if os.path.isfile(baseDir + '/follow.css'): if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css' cssFilename = baseDir + '/follow.css'
instanceTitle = \ instanceTitle = getConfigParam(baseDir, 'instanceTitle')
getConfigParam(baseDir, 'instanceTitle')
blockStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) blockStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
blockStr += '<div class="block">\n' blockStr += '<div class="block">\n'
blockStr += ' <div class="blockAvatar">\n' blockStr += ' <div class="blockAvatar">\n'

View File

@ -26,35 +26,35 @@ def _htmlFollowingDataList(baseDir: str, nickname: str,
listStr = '<datalist id="followingHandles">\n' listStr = '<datalist id="followingHandles">\n'
followingFilename = \ followingFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '/following.txt' baseDir + '/accounts/' + nickname + '@' + domain + '/following.txt'
msg = None
if os.path.isfile(followingFilename): if os.path.isfile(followingFilename):
with open(followingFilename, 'r') as followingFile: with open(followingFilename, 'r') as followingFile:
msg = followingFile.read() msg = followingFile.read()
# add your own handle, so that you can send DMs # add your own handle, so that you can send DMs
# to yourself as reminders # to yourself as reminders
msg += nickname + '@' + domainFull + '\n' msg += nickname + '@' + domainFull + '\n'
# include petnames if msg:
petnamesFilename = \ # include petnames
baseDir + '/accounts/' + \ petnamesFilename = \
nickname + '@' + domain + '/petnames.txt' baseDir + '/accounts/' + nickname + '@' + domain + '/petnames.txt'
if os.path.isfile(petnamesFilename): if os.path.isfile(petnamesFilename):
followingList = [] followingList = []
with open(petnamesFilename, 'r') as petnamesFile: with open(petnamesFilename, 'r') as petnamesFile:
petStr = petnamesFile.read() petStr = petnamesFile.read()
# extract each petname and append it # extract each petname and append it
petnamesList = petStr.split('\n') petnamesList = petStr.split('\n')
for pet in petnamesList: for pet in petnamesList:
followingList.append(pet.split(' ')[0]) followingList.append(pet.split(' ')[0])
# add the following.txt entries # add the following.txt entries
followingList += msg.split('\n') followingList += msg.split('\n')
else: else:
# no petnames list exists - just use following.txt # no petnames list exists - just use following.txt
followingList = msg.split('\n') followingList = msg.split('\n')
followingList.sort() followingList.sort()
if followingList: if followingList:
for followingAddress in followingList: for followingAddress in followingList:
if followingAddress: if followingAddress:
listStr += \ listStr += '<option>@' + followingAddress + '</option>\n'
'<option>@' + followingAddress + '</option>\n'
listStr += '</datalist>\n' listStr += '</datalist>\n'
return listStr return listStr
@ -84,8 +84,7 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
dropDownContent += ' <label for="my-newPostDropdown"\n' dropDownContent += ' <label for="my-newPostDropdown"\n'
dropDownContent += ' data-toggle="newPostDropdown">\n' dropDownContent += ' data-toggle="newPostDropdown">\n'
dropDownContent += ' <img loading="lazy" alt="" title="" src="/' + \ dropDownContent += ' <img loading="lazy" alt="" title="" src="/' + \
'icons/' + scopeIcon + '"/><b>' + \ 'icons/' + scopeIcon + '"/><b>' + scopeDescription + '</b></label>\n'
scopeDescription + '</b></label>\n'
if noDropDown: if noDropDown:
dropDownContent += '</div></nav>\n' dropDownContent += '</div></nav>\n'
@ -144,12 +143,6 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
'icons/scope_reminder.png"/><b>' + \ 'icons/scope_reminder.png"/><b>' + \
translate['Reminder'] + '</b><br>' + \ translate['Reminder'] + '</b><br>' + \
translate['Scheduled note to yourself'] + '</a></li>\n' translate['Scheduled note to yourself'] + '</a></li>\n'
# dropDownContent += \
# '<li><a href="' + pathBase + dropdownEventSuffix + \
# '"><img loading="lazy" alt="" title="" src="/' + \
# 'icons/scope_event.png"/><b>' + \
# translate['Event'] + '</b><br>' + \
# translate['Create an event'] + '</a></li>\n'
dropDownContent += \ dropDownContent += \
'<li><a href="' + pathBase + dropdownReportSuffix + \ '<li><a href="' + pathBase + dropdownReportSuffix + \
'" accesskey="' + accessKeys['reportButton'] + '">' + \ '" accesskey="' + accessKeys['reportButton'] + '">' + \
@ -230,8 +223,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
showPublicOnDropdown = False showPublicOnDropdown = False
else: else:
newPostText = \ newPostText = \
'<h1>' + \ '<h1>' + translate['Write your report below.'] + '</h1>\n'
translate['Write your report below.'] + '</h1>\n'
# custom report header with any additional instructions # custom report header with any additional instructions
if os.path.isfile(baseDir + '/accounts/report.txt'): if os.path.isfile(baseDir + '/accounts/report.txt'):
@ -319,7 +311,6 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
translate['Subject or Content Warning (optional)'] + '...' translate['Subject or Content Warning (optional)'] + '...'
placeholderMentions = '' placeholderMentions = ''
if inReplyTo: if inReplyTo:
# mentionsAndContent = getMentionsString(content)
placeholderMentions = \ placeholderMentions = \
translate['Replying to'] + '...' translate['Replying to'] + '...'
placeholderMessage = translate['Write something'] + '...' placeholderMessage = translate['Write something'] + '...'
@ -582,8 +573,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
dateAndLocation += '<input type="text" name="category">\n' dateAndLocation += '<input type="text" name="category">\n'
dateAndLocation += '</div>\n' dateAndLocation += '</div>\n'
instanceTitle = \ instanceTitle = getConfigParam(baseDir, 'instanceTitle')
getConfigParam(baseDir, 'instanceTitle')
newPostForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) newPostForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
newPostForm += \ newPostForm += \
@ -711,17 +701,11 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
submitText + '" ' + \ submitText + '" ' + \
'accesskey="' + accessKeys['submitButton'] + '"></td>\n' 'accesskey="' + accessKeys['submitButton'] + '"></td>\n'
newPostForm += ' </tr>\n' newPostForm += ' </tr>\n</table>\n'
newPostForm += '</table>\n'
newPostForm += ' </div>\n' newPostForm += ' </div>\n'
newPostForm += ' <div class="containerSubmitNewPost"><center>\n' newPostForm += ' <div class="containerSubmitNewPost"><center>\n'
# newPostForm += \
# ' <a href="' + pathBase + \
# '/inbox"><button class="cancelbtn">' + \
# translate['Go Back'] + '</button></a>\n'
newPostForm += ' </center></div>\n' newPostForm += ' </center></div>\n'
newPostForm += replyStr newPostForm += replyStr
@ -777,14 +761,13 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
if not mediaInstance or replyStr: if not mediaInstance or replyStr:
newPostForm += newPostImageSection newPostForm += newPostImageSection
newPostForm += ' <div class="container">\n'
newPostForm += \ newPostForm += \
' <div class="container">\n' + \
' <input type="submit" name="submitPost" value="' + \ ' <input type="submit" name="submitPost" value="' + \
submitText + '">\n' submitText + '">\n' + \
newPostForm += ' </div>\n' ' </div>\n' + \
' </div>\n' + \
newPostForm += ' </div>\n' '</form>\n'
newPostForm += '</form>\n'
if not reportUrl: if not reportUrl:
newPostForm = \ newPostForm = \

View File

@ -128,22 +128,24 @@ def htmlFrontScreen(rssIconAtTop: bool,
if loginButton: if loginButton:
profileHeaderStr += '<center>' + loginButton + '</center>\n' profileHeaderStr += '<center>' + loginButton + '</center>\n'
profileHeaderStr += '<table class="timeline">\n' profileHeaderStr += \
profileHeaderStr += ' <colgroup>\n' '<table class="timeline">\n' + \
profileHeaderStr += ' <col span="1" class="column-left">\n' ' <colgroup>\n' + \
profileHeaderStr += ' <col span="1" class="column-center">\n' ' <col span="1" class="column-left">\n' + \
profileHeaderStr += ' <col span="1" class="column-right">\n' ' <col span="1" class="column-center">\n' + \
profileHeaderStr += ' </colgroup>\n' ' <col span="1" class="column-right">\n' + \
profileHeaderStr += ' <tbody>\n' ' </colgroup>\n' + \
profileHeaderStr += ' <tr>\n' ' <tbody>\n' + \
profileHeaderStr += ' <td valign="top" class="col-left">\n' ' <tr>\n' + \
' <td valign="top" class="col-left">\n'
profileHeaderStr += \ profileHeaderStr += \
getLeftColumnContent(baseDir, 'news', domainFull, getLeftColumnContent(baseDir, 'news', domainFull,
httpPrefix, translate, httpPrefix, translate,
False, False, None, rssIconAtTop, True, False, False, None, rssIconAtTop, True,
True, theme, accessKeys) True, theme, accessKeys)
profileHeaderStr += ' </td>\n' profileHeaderStr += \
profileHeaderStr += ' <td valign="top" class="col-center">\n' ' </td>\n' + \
' <td valign="top" class="col-center">\n'
profileStr = profileHeaderStr profileStr = profileHeaderStr
@ -177,10 +179,11 @@ def htmlFrontScreen(rssIconAtTop: bool,
False, None, False, False, False, None, False, False,
False, True, authorized, True, theme, False, True, authorized, True, theme,
defaultTimeline, accessKeys) defaultTimeline, accessKeys)
profileFooterStr += ' </td>\n' profileFooterStr += \
profileFooterStr += ' </tr>\n' ' </td>\n' + \
profileFooterStr += ' </tbody>\n' ' </tr>\n' + \
profileFooterStr += '</table>\n' ' </tbody>\n' + \
'</table>\n'
instanceTitle = \ instanceTitle = \
getConfigParam(baseDir, 'instanceTitle') getConfigParam(baseDir, 'instanceTitle')

View File

@ -29,17 +29,19 @@ def getHashtagCategoriesFeed(baseDir: str,
if not hashtagCategories: if not hashtagCategories:
return None return None
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" rssStr = \
rssStr += "<rss version=\"2.0\">\n" "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + \
rssStr += '<channel>\n' "<rss version=\"2.0\">\n" + \
rssStr += ' <title>#categories</title>\n' '<channel>\n' + \
' <title>#categories</title>\n'
rssDateStr = \ rssDateStr = \
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S UT") datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S UT")
for categoryStr, hashtagList in hashtagCategories.items(): for categoryStr, hashtagList in hashtagCategories.items():
rssStr += '<item>\n' rssStr += \
rssStr += ' <title>' + categoryStr + '</title>\n' '<item>\n' + \
' <title>' + categoryStr + '</title>\n'
listStr = '' listStr = ''
for hashtag in hashtagList: for hashtag in hashtagList:
if ':' in hashtag: if ':' in hashtag:
@ -47,75 +49,18 @@ def getHashtagCategoriesFeed(baseDir: str,
if '&' in hashtag: if '&' in hashtag:
continue continue
listStr += hashtag + ' ' listStr += hashtag + ' '
rssStr += ' <description>' + listStr.strip() + '</description>\n' rssStr += \
rssStr += ' <link/>\n' ' <description>' + listStr.strip() + '</description>\n' + \
rssStr += ' <pubDate>' + rssDateStr + '</pubDate>\n' ' <link/>\n' + \
rssStr += '</item>\n' ' <pubDate>' + rssDateStr + '</pubDate>\n' + \
'</item>\n'
rssStr += '</channel>\n' rssStr += \
rssStr += '</rss>\n' '</channel>\n' + \
'</rss>\n'
return rssStr return rssStr
def _getHashtagDomainMax(domainHistogram: {}) -> str:
"""Returns the domain with the maximum number of hashtags
"""
maxCount = 1
maxDomain = None
for domain, count in domainHistogram.items():
if count > maxCount:
maxDomain = domain
maxCount = count
return maxDomain
def _getHashtagDomainHistogram(domainHistogram: {}, translate: {}) -> str:
"""Returns the html for a histogram of domains
from which hashtags are coming
"""
totalCount = 0
for domain, count in domainHistogram.items():
totalCount += count
if totalCount == 0:
return ''
htmlStr = ''
histogramHeaderStr = '<br><br><center>\n'
histogramHeaderStr += ' <h1>' + translate['Hashtag origins'] + '</h1>\n'
histogramHeaderStr += ' <table class="domainHistogram">\n'
histogramHeaderStr += ' <colgroup>\n'
histogramHeaderStr += ' <col span="1" class="domainHistogramLeft">\n'
histogramHeaderStr += ' <col span="1" class="domainHistogramRight">\n'
histogramHeaderStr += ' </colgroup>\n'
histogramHeaderStr += ' <tbody>\n'
histogramHeaderStr += ' <tr>\n'
leftColStr = ''
rightColStr = ''
for i in range(len(domainHistogram)):
domain = _getHashtagDomainMax(domainHistogram)
if not domain:
break
percent = int(domainHistogram[domain] * 100 / totalCount)
if histogramHeaderStr:
htmlStr += histogramHeaderStr
histogramHeaderStr = None
leftColStr += str(percent) + '%<br>'
rightColStr += domain + '<br>'
del domainHistogram[domain]
if htmlStr:
htmlStr += ' <td>' + leftColStr + '</td>\n'
htmlStr += ' <td>' + rightColStr + '</td>\n'
htmlStr += ' </tr>\n'
htmlStr += ' </tbody>\n'
htmlStr += ' </table>\n'
htmlStr += '</center>\n'
return htmlStr
def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str: def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
"""Returns a tag swarm of today's hashtags """Returns a tag swarm of today's hashtags
""" """
@ -244,7 +189,6 @@ def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
getContentWarningButton('alltags', translate, tagSwarmStr) getContentWarningButton('alltags', translate, tagSwarmStr)
tagSwarmHtml = categorySwarmStr + tagSwarmStr.strip() + '\n' tagSwarmHtml = categorySwarmStr + tagSwarmStr.strip() + '\n'
# tagSwarmHtml += _getHashtagDomainHistogram(domainHistogram, translate)
return tagSwarmHtml return tagSwarmHtml
@ -279,10 +223,11 @@ def htmlSearchHashtagCategory(cssCache: {}, translate: {},
htmlStr += '<img loading="lazy" class="timeline-banner" src="' + \ htmlStr += '<img loading="lazy" class="timeline-banner" src="' + \
actor + '/' + searchBannerFile + '" alt="" /></a>\n' actor + '/' + searchBannerFile + '" alt="" /></a>\n'
htmlStr += '<div class="follow">' htmlStr += \
htmlStr += '<center><br><br><br>' '<div class="follow">' + \
htmlStr += '<h1><a href="' + actor + '/search"><b>' '<center><br><br><br>' + \
htmlStr += translate['Category'] + ': ' + categoryStr + '</b></a></h1>' '<h1><a href="' + actor + '/search"><b>' + \
translate['Category'] + ': ' + categoryStr + '</b></a></h1>'
hashtagsDict = getHashtagCategories(baseDir, True, categoryStr) hashtagsDict = getHashtagCategories(baseDir, True, categoryStr)
if hashtagsDict: if hashtagsDict:
@ -293,7 +238,8 @@ def htmlSearchHashtagCategory(cssCache: {}, translate: {},
'<a href="' + actor + '/tags/' + tagName + \ '<a href="' + actor + '/tags/' + tagName + \
'" class="hashtagswarm">' + tagName + '</a>\n' '" class="hashtagswarm">' + tagName + '</a>\n'
htmlStr += '</center>' htmlStr += \
htmlStr += '</div>' '</center>' + \
'</div>'
htmlStr += htmlFooter() htmlStr += htmlFooter()
return htmlStr return htmlStr

View File

@ -54,8 +54,7 @@ def headerButtonsTimeline(defaultTimeline: str,
# first button # first button
if defaultTimeline == 'tlmedia': if defaultTimeline == 'tlmedia':
tlStr += \ tlStr += \
'<a href="' + usersPath + \ '<a href="' + usersPath + '/tlmedia" tabindex="-1" ' + \
'/tlmedia" tabindex="-1" ' + \
'accesskey="' + accessKeys['menuMedia'] + '"' + \ 'accesskey="' + accessKeys['menuMedia'] + '"' + \
'><button class="' + \ '><button class="' + \
mediaButton + '"><span>' + translate['Media'] + \ mediaButton + '"><span>' + translate['Media'] + \
@ -105,8 +104,7 @@ def headerButtonsTimeline(defaultTimeline: str,
if defaultTimeline != 'tlmedia': if defaultTimeline != 'tlmedia':
if not minimal and not featuresHeader: if not minimal and not featuresHeader:
tlStr += \ tlStr += \
'<a href="' + usersPath + \ '<a href="' + usersPath + '/tlmedia" tabindex="-1" ' + \
'/tlmedia" tabindex="-1" ' + \
'accesskey="' + accessKeys['menuMedia'] + '">' + \ 'accesskey="' + accessKeys['menuMedia'] + '">' + \
'<button class="' + \ '<button class="' + \
mediaButton + '"><span>' + translate['Media'] + \ mediaButton + '"><span>' + translate['Media'] + \
@ -206,10 +204,8 @@ def headerButtonsTimeline(defaultTimeline: str,
if not featuresHeader: if not featuresHeader:
# button for the outbox # button for the outbox
tlStr += \ tlStr += \
'<a href="' + usersPath + \ '<a href="' + usersPath + '/outbox"><button class="' + \
'/outbox"><button class="' + \ sentButton + '" tabindex="-1"><span>' + translate['Sent'] + \
sentButton + '" tabindex="-1">' + \
'<span>' + translate['Sent'] + \
'</span></button></a>' '</span></button></a>'
# add other buttons # add other buttons

View File

@ -36,18 +36,19 @@ def htmlGetLoginCredentials(loginParams: str,
password = None password = None
register = False register = False
for arg in loginArgs: for arg in loginArgs:
if '=' in arg: if '=' not in arg:
if arg.split('=', 1)[0] == 'username': continue
nickname = arg.split('=', 1)[1] if arg.split('=', 1)[0] == 'username':
if nickname.startswith('@'): nickname = arg.split('=', 1)[1]
nickname = nickname[1:] if nickname.startswith('@'):
if '@' in nickname: nickname = nickname[1:]
# the full nickname@domain has been entered if '@' in nickname:
nickname = nickname.split('@')[0] # the full nickname@domain has been entered
elif arg.split('=', 1)[0] == 'password': nickname = nickname.split('@')[0]
password = arg.split('=', 1)[1] elif arg.split('=', 1)[0] == 'password':
elif arg.split('=', 1)[0] == 'register': password = arg.split('=', 1)[1]
register = True elif arg.split('=', 1)[0] == 'register':
register = True
return nickname, password, register return nickname, password, register
@ -103,8 +104,7 @@ def htmlLogin(cssCache: {}, translate: {},
else: else:
loginText = \ loginText = \
'<p class="login-text">' + \ '<p class="login-text">' + \
translate['Please enter some credentials'] + '</p>' translate['Please enter some credentials'] + '</p>' + \
loginText += \
'<p class="login-text">' + \ '<p class="login-text">' + \
translate['You will become the admin of this site.'] + \ translate['You will become the admin of this site.'] + \
'</p>' '</p>'
@ -132,8 +132,7 @@ def htmlLogin(cssCache: {}, translate: {},
TOSstr = \ TOSstr = \
'<p class="login-text"><a href="/about">' + \ '<p class="login-text"><a href="/about">' + \
translate['About this Instance'] + '</a></p>' translate['About this Instance'] + '</a></p>' + \
TOSstr += \
'<p class="login-text"><a href="/terms">' + \ '<p class="login-text"><a href="/terms">' + \
translate['Terms of Service'] + '</a></p>' translate['Terms of Service'] + '</a></p>'
@ -153,34 +152,32 @@ def htmlLogin(cssCache: {}, translate: {},
htmlHeaderWithWebsiteMarkup(cssFilename, instanceTitle, htmlHeaderWithWebsiteMarkup(cssFilename, instanceTitle,
httpPrefix, domain, httpPrefix, domain,
systemLanguage) systemLanguage)
loginForm += '<br>\n'
loginForm += '<form method="POST" action="/login">\n'
loginForm += ' <div class="imgcontainer">\n'
instanceTitle = getConfigParam(baseDir, 'instanceTitle') instanceTitle = getConfigParam(baseDir, 'instanceTitle')
loginForm += textModeLogoHtml + '\n'
loginForm += \ loginForm += \
'<br>\n' + \
'<form method="POST" action="/login">\n' + \
' <div class="imgcontainer">\n' + \
textModeLogoHtml + '\n' + \
' <img loading="lazy" src="' + loginImage + \ ' <img loading="lazy" src="' + loginImage + \
'" alt="' + instanceTitle + '" class="loginimage">\n' '" alt="' + instanceTitle + '" class="loginimage">\n' + \
loginForm += loginText + TOSstr + '\n' loginText + TOSstr + '\n' + \
loginForm += ' </div>\n' ' </div>\n' + \
loginForm += '\n' '\n' + \
loginForm += ' <div class="container">\n' ' <div class="container">\n' + \
loginForm += ' <label for="nickname"><b>' + \ ' <label for="nickname"><b>' + \
translate['Nickname'] + '</b></label>\n' translate['Nickname'] + '</b></label>\n' + \
loginForm += \
' <input type="text" ' + autocompleteStr + ' placeholder="' + \ ' <input type="text" ' + autocompleteStr + ' placeholder="' + \
translate['Enter Nickname'] + '" name="username" required autofocus>\n' translate['Enter Nickname'] + \
loginForm += '\n' '" name="username" required autofocus>\n' + \
loginForm += ' <label for="password"><b>' + \ '\n' + \
translate['Password'] + '</b></label>\n' ' <label for="password"><b>' + \
loginForm += \ translate['Password'] + '</b></label>\n' + \
' <input type="password" ' + autocompleteStr + \ ' <input type="password" ' + autocompleteStr + \
' placeholder="' + translate['Enter Password'] + \ ' placeholder="' + translate['Enter Password'] + \
'" name="password" required>\n' '" name="password" required>\n' + \
loginForm += loginButtonStr + registerButtonStr + '\n' loginButtonStr + registerButtonStr + '\n' + \
loginForm += ' </div>\n' ' </div>\n' + \
loginForm += '</form>\n' '</form>\n' + \
loginForm += \
'<a href="https://gitlab.com/bashrc2/epicyon">' + \ '<a href="https://gitlab.com/bashrc2/epicyon">' + \
'<img loading="lazy" class="license" title="' + \ '<img loading="lazy" class="license" title="' + \
translate['Get the source code'] + '" alt="' + \ translate['Get the source code'] + '" alt="' + \

View File

@ -8,6 +8,7 @@ __status__ = "Production"
__module_group__ = "Timeline" __module_group__ = "Timeline"
import os import os
from utils import validUrlPrefix
def loadPeertubeInstances(baseDir: str, peertubeInstances: []) -> None: def loadPeertubeInstances(baseDir: str, peertubeInstances: []) -> None:
@ -110,6 +111,8 @@ def _addEmbeddedVideoFromSites(translate: {}, content: str,
if '"https://' in content: if '"https://' in content:
if peertubeInstances: if peertubeInstances:
# only create an embedded video for a limited set of
# peertube sites.
peerTubeSites = peertubeInstances peerTubeSites = peertubeInstances
else: else:
# A default selection of the current larger peertube sites, # A default selection of the current larger peertube sites,
@ -160,19 +163,21 @@ def _addEmbeddedVideoFromSites(translate: {}, content: str,
else: else:
siteStr = 'https://' + site siteStr = 'https://' + site
siteStr = '"' + siteStr siteStr = '"' + siteStr
if siteStr in content: if siteStr not in content:
url = content.split(siteStr)[1] continue
if '"' in url: url = content.split(siteStr)[1]
url = url.split('"')[0].replace('/watch/', '/embed/') if '"' not in url:
content = \ continue
content + "<center>\n<iframe loading=\"lazy\" " + \ url = url.split('"')[0].replace('/watch/', '/embed/')
"sandbox=\"allow-same-origin " + \ content = \
"allow-scripts\" src=\"https://" + \ content + "<center>\n<iframe loading=\"lazy\" " + \
site + url + "\" width=\"" + str(width) + \ "sandbox=\"allow-same-origin " + \
"\" height=\"" + str(height) + \ "allow-scripts\" src=\"https://" + \
"\" frameborder=\"0\" allow=\"autoplay; " + \ site + url + "\" width=\"" + str(width) + \
"fullscreen\" allowfullscreen></iframe>\n</center>\n" "\" height=\"" + str(height) + \
return content "\" frameborder=\"0\" allow=\"autoplay; " + \
"fullscreen\" allowfullscreen></iframe>\n</center>\n"
return content
return content return content
@ -205,19 +210,14 @@ def _addEmbeddedAudio(translate: {}, content: str) -> str:
if not w.endswith(extension): if not w.endswith(extension):
continue continue
if not (w.startswith('http') or w.startswith('dat:') or if not validUrlPrefix(w):
w.startswith('hyper:') or w.startswith('i2p:') or
w.startswith('gnunet:') or
'/' in w):
continue continue
url = w
content += '<center>\n<audio controls>\n'
content += \ content += \
'<source src="' + url + '" type="audio/' + \ '<center>\n<audio controls>\n' + \
extension.replace('.', '') + '">' '<source src="' + w + '" type="audio/' + \
content += \ extension.replace('.', '') + '">' + \
translate['Your browser does not support the audio element.'] translate['Your browser does not support the audio element.'] + \
content += '</audio>\n</center>\n' '</audio>\n</center>\n'
return content return content
@ -251,23 +251,17 @@ def _addEmbeddedVideo(translate: {}, content: str) -> str:
w = w[:-1] w = w[:-1]
if not w.endswith(extension): if not w.endswith(extension):
continue continue
if not (w.startswith('http') or w.startswith('dat:') or if not validUrlPrefix(w):
w.startswith('hyper:') or w.startswith('i2p:') or
w.startswith('gnunet:') or
'/' in w):
continue continue
url = w
content += \ content += \
'<center><figure id="videoContainer" ' + \ '<center><figure id="videoContainer" ' + \
'data-fullscreen="false">\n' + \ 'data-fullscreen="false">\n' + \
' <video id="video" controls ' + \ ' <video id="video" controls ' + \
'preload="metadata">\n' 'preload="metadata">\n' + \
content += \ '<source src="' + w + '" type="video/' + \
'<source src="' + url + '" type="video/' + \ extension.replace('.', '') + '">\n' + \
extension.replace('.', '') + '">\n' translate['Your browser does not support the video element.'] + \
content += \ '</video>\n</figure>\n</center>\n'
translate['Your browser does not support the video element.']
content += '</video>\n</figure>\n</center>\n'
return content return content

View File

@ -398,9 +398,10 @@ def htmlPersonOptions(defaultTimeline: str,
'accesskey="' + accessKeys['enterNotes'] + '">' + \ 'accesskey="' + accessKeys['enterNotes'] + '">' + \
personNotes + '</textarea>\n' personNotes + '</textarea>\n'
optionsStr += ' </form>\n' optionsStr += \
optionsStr += '</center>\n' ' </form>\n' + \
optionsStr += '</div>\n' '</center>\n' + \
optionsStr += '</div>\n' '</div>\n' + \
'</div>\n'
optionsStr += htmlFooter() optionsStr += htmlFooter()
return optionsStr return optionsStr

View File

@ -22,6 +22,7 @@ from posts import postIsMuted
from posts import getPersonBox from posts import getPersonBox
from posts import downloadAnnounce from posts import downloadAnnounce
from posts import populateRepliesJson from posts import populateRepliesJson
from utils import hasObjectDict
from utils import updateAnnounceCollection from utils import updateAnnounceCollection
from utils import isPGPEncrypted from utils import isPGPEncrypted
from utils import isDM from utils import isDM
@ -840,8 +841,7 @@ def _getPostTitleAnnounceHtml(baseDir: str,
postJsonObject) postJsonObject)
else: else:
titleStr += \ titleStr += \
_announceUnattributedHtml(translate, _announceUnattributedHtml(translate, postJsonObject)
postJsonObject)
else: else:
titleStr += \ titleStr += \
_announceUnattributedHtml(translate, postJsonObject) _announceUnattributedHtml(translate, postJsonObject)
@ -1339,7 +1339,7 @@ def individualPostAsHtml(allowDownloads: bool,
_logPostTiming(enableTimingLog, postStartTime, '8') _logPostTiming(enableTimingLog, postStartTime, '8')
if not isinstance(postJsonObject['object'], dict): if not hasObjectDict(postJsonObject):
return '' return ''
# if this post should be public then check its recipients # if this post should be public then check its recipients
@ -1762,7 +1762,7 @@ def htmlIndividualPost(cssCache: {},
messageId = removeIdEnding(postJsonObject['id']) messageId = removeIdEnding(postJsonObject['id'])
# show the previous posts # show the previous posts
if isinstance(postJsonObject['object'], dict): if hasObjectDict(postJsonObject):
while postJsonObject['object'].get('inReplyTo'): while postJsonObject['object'].get('inReplyTo'):
postFilename = \ postFilename = \
locatePost(baseDir, nickname, domain, locatePost(baseDir, nickname, domain,

View File

@ -1601,64 +1601,66 @@ def _htmlEditProfileFiltering(baseDir: str, nickname: str, domain: str,
def _htmlEditProfileChangePassword(translate: {}) -> str: def _htmlEditProfileChangePassword(translate: {}) -> str:
"""Change password section of edit profile screen """Change password section of edit profile screen
""" """
editProfileForm = ' <details><summary class="cw">' + \ editProfileForm = \
translate['Change Password'] + '</summary>\n' ' <details><summary class="cw">' + \
editProfileForm += ' <div class="container">\n' translate['Change Password'] + '</summary>\n' + \
editProfileForm += \ ' <div class="container">\n' + \
'<label class="labels">' + translate['Change Password'] + \ '<label class="labels">' + translate['Change Password'] + \
'</label><br>\n' '</label><br>\n' + \
editProfileForm += ' <input type="text" name="password" ' + \ ' <input type="text" name="password" ' + \
'value=""><br>\n' 'value=""><br>\n' + \
editProfileForm += \
'<label class="labels">' + translate['Confirm Password'] + \ '<label class="labels">' + translate['Confirm Password'] + \
'</label><br>\n' '</label><br>\n' + \
editProfileForm += \ ' <input type="text" name="passwordconfirm" value="">\n' + \
' <input type="text" name="passwordconfirm" value="">\n' ' </div></details>\n'
editProfileForm += ' </div></details>\n'
return editProfileForm return editProfileForm
def _htmlEditProfileBackground(newsInstance: bool, translate: {}) -> str: def _htmlEditProfileBackground(newsInstance: bool, translate: {}) -> str:
"""Background images section of edit profile screen """Background images section of edit profile screen
""" """
editProfileForm = ' <details><summary class="cw">' + \
translate['Background Images'] + '</summary>\n'
editProfileForm += ' <div class="container">\n'
idx = 'The files attached below should be no larger than ' + \ idx = 'The files attached below should be no larger than ' + \
'10MB in total uploaded at once.' '10MB in total uploaded at once.'
editProfileForm += \ editProfileForm = \
' <details><summary class="cw">' + \
translate['Background Images'] + '</summary>\n' + \
' <div class="container">\n' + \
' <label class="labels">' + translate[idx] + '</label><br><br>\n' ' <label class="labels">' + translate[idx] + '</label><br><br>\n'
if not newsInstance: if not newsInstance:
imageFormats = getImageFormats() imageFormats = getImageFormats()
editProfileForm += \ editProfileForm += \
' <label class="labels">' + \ ' <label class="labels">' + \
translate['Background image'] + '</label>\n' translate['Background image'] + '</label>\n' + \
editProfileForm += ' <input type="file" id="image" name="image"' ' <input type="file" id="image" name="image"' + \
editProfileForm += ' accept="' + imageFormats + '">\n' ' accept="' + imageFormats + '">\n'
editProfileForm += ' <br><label class="labels">' + \ editProfileForm += \
translate['Timeline banner image'] + '</label>\n' ' <br><label class="labels">' + \
editProfileForm += ' <input type="file" id="banner" name="banner"' translate['Timeline banner image'] + '</label>\n' + \
editProfileForm += ' accept="' + imageFormats + '">\n' ' <input type="file" id="banner" name="banner"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += ' <br><label class="labels">' + \ editProfileForm += \
translate['Search banner image'] + '</label>\n' ' <br><label class="labels">' + \
editProfileForm += ' <input type="file" id="search_banner" ' translate['Search banner image'] + '</label>\n' + \
editProfileForm += 'name="search_banner"' ' <input type="file" id="search_banner" ' + \
editProfileForm += ' accept="' + imageFormats + '">\n' 'name="search_banner"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += ' <br><label class="labels">' + \ editProfileForm += \
translate['Left column image'] + '</label>\n' ' <br><label class="labels">' + \
editProfileForm += ' <input type="file" id="left_col_image" ' translate['Left column image'] + '</label>\n' + \
editProfileForm += 'name="left_col_image"' ' <input type="file" id="left_col_image" ' + \
editProfileForm += ' accept="' + imageFormats + '">\n' 'name="left_col_image"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += ' <br><label class="labels">' + \ editProfileForm += \
translate['Right column image'] + '</label>\n' ' <br><label class="labels">' + \
editProfileForm += ' <input type="file" id="right_col_image" ' translate['Right column image'] + '</label>\n' + \
editProfileForm += 'name="right_col_image"' ' <input type="file" id="right_col_image" ' + \
editProfileForm += ' accept="' + imageFormats + '">\n' 'name="right_col_image"' + \
' accept="' + imageFormats + '">\n'
editProfileForm += ' </div></details>\n' editProfileForm += ' </div></details>\n'
return editProfileForm return editProfileForm
@ -1696,48 +1698,45 @@ def _htmlEditProfileContactInfo(nickname: str,
' <input type="text" name="matrixAddress" value="' + \ ' <input type="text" name="matrixAddress" value="' + \
matrixAddress + '">\n' matrixAddress + '">\n'
editProfileForm += '<label class="labels">SSB</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">SSB</label><br>\n' + \
' <input type="text" name="ssbAddress" value="' + \ ' <input type="text" name="ssbAddress" value="' + \
ssbAddress + '">\n' ssbAddress + '">\n'
editProfileForm += '<label class="labels">Tox</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">Tox</label><br>\n' + \
' <input type="text" name="toxAddress" value="' + \ ' <input type="text" name="toxAddress" value="' + \
toxAddress + '">\n' toxAddress + '">\n'
editProfileForm += '<label class="labels">Briar</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">Briar</label><br>\n' + \
' <input type="text" name="briarAddress" value="' + \ ' <input type="text" name="briarAddress" value="' + \
briarAddress + '">\n' briarAddress + '">\n'
editProfileForm += '<label class="labels">Jami</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">Jami</label><br>\n' + \
' <input type="text" name="jamiAddress" value="' + \ ' <input type="text" name="jamiAddress" value="' + \
jamiAddress + '">\n' jamiAddress + '">\n'
editProfileForm += '<label class="labels">Cwtch</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">Cwtch</label><br>\n' + \
' <input type="text" name="cwtchAddress" value="' + \ ' <input type="text" name="cwtchAddress" value="' + \
cwtchAddress + '">\n' cwtchAddress + '">\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + \ '<label class="labels">' + \
translate['PGP Fingerprint'] + '</label><br>\n' translate['PGP Fingerprint'] + '</label><br>\n' + \
editProfileForm += \
' <input type="text" name="openpgp" value="' + \ ' <input type="text" name="openpgp" value="' + \
PGPfingerprint + '">\n' PGPfingerprint + '">\n' + \
editProfileForm += \ '<label class="labels">' + translate['PGP'] + '</label><br>\n' + \
'<label class="labels">' + translate['PGP'] + '</label><br>\n'
editProfileForm += \
' <textarea id="message" placeholder=' + \ ' <textarea id="message" placeholder=' + \
'"-----BEGIN PGP PUBLIC KEY BLOCK-----" name="pgp" ' + \ '"-----BEGIN PGP PUBLIC KEY BLOCK-----" name="pgp" ' + \
'style="height:600px" spellcheck="false">' + \ 'style="height:600px" spellcheck="false">' + \
PGPpubKey + '</textarea>\n' PGPpubKey + '</textarea>\n' + \
editProfileForm += '<a href="/users/' + nickname + \ '<a href="/users/' + nickname + \
'/followingaccounts"><label class="labels">' + \ '/followingaccounts"><label class="labels">' + \
translate['Following'] + '</label></a><br>\n' translate['Following'] + '</label></a><br>\n' + \
editProfileForm += ' </div></details>\n' ' </div></details>\n'
return editProfileForm return editProfileForm
@ -1790,30 +1789,32 @@ def _htmlEditProfileMain(displayNickname: str, bioStr: str,
imageFormats = getImageFormats() imageFormats = getImageFormats()
editProfileForm = ' <div class="container">\n' editProfileForm = ' <div class="container">\n'
editProfileForm += ' <label class="labels">' + \
translate['Nickname'] + '</label>\n'
editProfileForm += \ editProfileForm += \
' <label class="labels">' + \
translate['Nickname'] + '</label>\n' + \
' <input type="text" name="displayNickname" value="' + \ ' <input type="text" name="displayNickname" value="' + \
displayNickname + '"><br>\n' displayNickname + '"><br>\n'
editProfileForm += \ editProfileForm += \
' <label class="labels">' + translate['Your bio'] + '</label>\n' ' <label class="labels">' + \
editProfileForm += \ translate['Your bio'] + '</label>\n' + \
' <textarea id="message" name="bio" style="height:200px" ' + \ ' <textarea id="message" name="bio" style="height:200px" ' + \
'spellcheck="true">' + bioStr + '</textarea>\n' 'spellcheck="true">' + bioStr + '</textarea>\n'
editProfileForm += \ editProfileForm += \
' <label class="labels">' + translate['Avatar image'] + \ ' <label class="labels">' + translate['Avatar image'] + \
'</label>\n' '</label>\n' + \
editProfileForm += \ ' <input type="file" id="avatar" name="avatar"' + \
' <input type="file" id="avatar" name="avatar"' ' accept="' + imageFormats + '">\n'
editProfileForm += ' accept="' + imageFormats + '">\n'
occupationName = '' occupationName = ''
if actorJson.get('hasOccupation'): if actorJson.get('hasOccupation'):
occupationName = getOccupationName(actorJson) occupationName = getOccupationName(actorJson)
editProfileForm += '<label class="labels">' + \
translate['Occupation'] + ':</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + \
translate['Occupation'] + ':</label><br>\n' + \
' <input type="text" ' + \ ' <input type="text" ' + \
'name="occupationName" value="' + occupationName + '">\n' 'name="occupationName" value="' + occupationName + '">\n'
@ -1827,27 +1828,29 @@ def _htmlEditProfileMain(displayNickname: str, bioStr: str,
ctr += 1 ctr += 1
alsoKnownAsStr += altActor alsoKnownAsStr += altActor
editProfileForm += '<label class="labels">' + \
translate['Other accounts'] + ':</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + \
translate['Other accounts'] + ':</label><br>\n' + \
' <input type="text" placeholder="https://..." ' + \ ' <input type="text" placeholder="https://..." ' + \
'name="alsoKnownAs" value="' + alsoKnownAsStr + '">\n' 'name="alsoKnownAs" value="' + alsoKnownAsStr + '">\n'
editProfileForm += '<label class="labels">' + \
translate['Moved to new account address'] + ':</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + \
translate['Moved to new account address'] + ':</label><br>\n' + \
' <input type="text" placeholder="https://..." ' + \ ' <input type="text" placeholder="https://..." ' + \
'name="movedTo" value="' + movedTo + '">\n' 'name="movedTo" value="' + movedTo + '">\n'
editProfileForm += '<label class="labels">' + \
translate['Donations link'] + '</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + \
translate['Donations link'] + '</label><br>\n' + \
' <input type="text" placeholder="https://..." ' + \ ' <input type="text" placeholder="https://..." ' + \
'name="donateUrl" value="' + donateUrl + '">\n' 'name="donateUrl" value="' + donateUrl + '">\n'
editProfileForm += '<label class="labels">Blog</label><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">Blog</label><br>\n' + \
' <input type="text" name="blogAddress" value="' + \ ' <input type="text" name="blogAddress" value="' + \
blogAddress + '">\n' blogAddress + '">\n' + \
editProfileForm += ' </div>\n' ' </div>\n'
return editProfileForm return editProfileForm
@ -1858,8 +1861,8 @@ def _htmlEditProfileTopBanner(baseDir: str,
"""top banner on edit profile screen """top banner on edit profile screen
""" """
editProfileForm = \ editProfileForm = \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' '<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
editProfileForm += '<img loading="lazy" class="timeline-banner" src="' + \ '<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" alt="" /></a>\n' '/users/' + nickname + '/' + bannerFile + '" alt="" /></a>\n'
editProfileForm += \ editProfileForm += \
@ -2157,7 +2160,7 @@ def _individualFollowAsHtml(translate: {},
'?options=' + followUrl + \ '?options=' + followUrl + \
';1;' + avatarUrl + '"><button class="buttonunfollow">' + \ ';1;' + avatarUrl + '"><button class="buttonunfollow">' + \
translate['Block'] + '</button></a>\n' translate['Block'] + '</button></a>\n'
if b == 'unfollow': elif b == 'unfollow':
buttonsStr += \ buttonsStr += \
'<a href="/users/' + actorNickname + \ '<a href="/users/' + actorNickname + \
'?options=' + followUrl + \ '?options=' + followUrl + \

View File

@ -338,8 +338,7 @@ def htmlSearch(cssCache: {}, translate: {},
if os.path.isfile(baseDir + '/search.css'): if os.path.isfile(baseDir + '/search.css'):
cssFilename = baseDir + '/search.css' cssFilename = baseDir + '/search.css'
instanceTitle = \ instanceTitle = getConfigParam(baseDir, 'instanceTitle')
getConfigParam(baseDir, 'instanceTitle')
followStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) followStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
# show a banner above the search box # show a banner above the search box

View File

@ -24,9 +24,10 @@ def htmlSuspended(cssCache: {}, baseDir: str) -> str:
instanceTitle = \ instanceTitle = \
getConfigParam(baseDir, 'instanceTitle') getConfigParam(baseDir, 'instanceTitle')
suspendedForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) suspendedForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
suspendedForm += '<div><center>\n' suspendedForm += \
suspendedForm += ' <p class="screentitle">Account Suspended</p>\n' '<div><center>\n' + \
suspendedForm += ' <p>See <a href="/terms">Terms of Service</a></p>\n' ' <p class="screentitle">Account Suspended</p>\n' + \
suspendedForm += '</center></div>\n' ' <p>See <a href="/terms">Terms of Service</a></p>\n' + \
'</center></div>\n'
suspendedForm += htmlFooter() suspendedForm += htmlFooter()
return suspendedForm return suspendedForm

View File

@ -600,14 +600,15 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
iconsAsButtons, accessKeys) iconsAsButtons, accessKeys)
# start the timeline # start the timeline
tlStr += '<table class="timeline">\n' tlStr += \
tlStr += ' <colgroup>\n' '<table class="timeline">\n' + \
tlStr += ' <col span="1" class="column-left">\n' ' <colgroup>\n' + \
tlStr += ' <col span="1" class="column-center">\n' ' <col span="1" class="column-left">\n' + \
tlStr += ' <col span="1" class="column-right">\n' ' <col span="1" class="column-center">\n' + \
tlStr += ' </colgroup>\n' ' <col span="1" class="column-right">\n' + \
tlStr += ' <tbody>\n' ' </colgroup>\n' + \
tlStr += ' <tr>\n' ' <tbody>\n' + \
' <tr>\n'
domainFull = getFullDomain(domain, port) domainFull = getFullDomain(domain, port)

View File

@ -452,17 +452,18 @@ def getRightImageFile(baseDir: str,
def htmlHeaderWithExternalStyle(cssFilename: str, instanceTitle: str, def htmlHeaderWithExternalStyle(cssFilename: str, instanceTitle: str,
lang='en') -> str: lang='en') -> str:
htmlStr = '<!DOCTYPE html>\n'
htmlStr += '<html lang="' + lang + '">\n'
htmlStr += ' <head>\n'
htmlStr += ' <meta charset="utf-8">\n'
cssFile = '/' + cssFilename.split('/')[-1] cssFile = '/' + cssFilename.split('/')[-1]
htmlStr += ' <link rel="stylesheet" href="' + cssFile + '">\n' htmlStr = \
htmlStr += ' <link rel="manifest" href="/manifest.json">\n' '<!DOCTYPE html>\n' + \
htmlStr += ' <meta name="theme-color" content="grey">\n' '<html lang="' + lang + '">\n' + \
htmlStr += ' <title>' + instanceTitle + '</title>\n' ' <head>\n' + \
htmlStr += ' </head>\n' ' <meta charset="utf-8">\n' + \
htmlStr += ' <body>\n' ' <link rel="stylesheet" href="' + cssFile + '">\n' + \
' <link rel="manifest" href="/manifest.json">\n' + \
' <meta name="theme-color" content="grey">\n' + \
' <title>' + instanceTitle + '</title>\n' + \
' </head>\n' + \
' <body>\n'
return htmlStr return htmlStr
@ -511,37 +512,32 @@ def htmlHeaderWithPersonMarkup(cssFilename: str, instanceTitle: str,
sk['occupationalCategory']['codeValue'] sk['occupationalCategory']['codeValue']
categoryUrl = \ categoryUrl = \
'https://www.onetonline.org/link/summary/' + category 'https://www.onetonline.org/link/summary/' + category
skillsMarkup += ' {\n'
skillsMarkup += ' "@type": "Role",\n'
skillsMarkup += ' "hasOccupation": {\n'
skillsMarkup += ' "@type": "Occupation",\n'
skillsMarkup += ' "name": "' + roleName + '",\n'
skillsMarkup += ' "description": ' + \
'"Fediverse instance role",\n'
skillsMarkup += ' "occupationLocation": {\n'
skillsMarkup += \
' "@type": "City",\n'
skillsMarkup += \
' "name": "' + city + '"\n'
skillsMarkup += ' },\n'
skillsMarkup += ' "occupationalCategory": {\n'
skillsMarkup += ' "@type": "CategoryCode",\n'
skillsMarkup += ' "inCodeSet": {\n'
skillsMarkup += \
' "@type": "CategoryCodeSet",\n'
skillsMarkup += ' "name": "O*Net-SOC",\n'
skillsMarkup += ' "dateModified": "2019",\n'
skillsMarkup += \ skillsMarkup += \
' {\n' + \
' "@type": "Role",\n' + \
' "hasOccupation": {\n' + \
' "@type": "Occupation",\n' + \
' "name": "' + roleName + '",\n' + \
' "description": ' + \
'"Fediverse instance role",\n' + \
' "occupationLocation": {\n' + \
' "@type": "City",\n' + \
' "name": "' + city + '"\n' + \
' },\n' + \
' "occupationalCategory": {\n' + \
' "@type": "CategoryCode",\n' + \
' "inCodeSet": {\n' + \
' "@type": "CategoryCodeSet",\n' + \
' "name": "O*Net-SOC",\n' + \
' "dateModified": "2019",\n' + \
' ' + \ ' ' + \
'"url": "https://www.onetonline.org/"\n' '"url": "https://www.onetonline.org/"\n' + \
skillsMarkup += ' },\n' ' },\n' + \
skillsMarkup += \ ' "codeValue": "' + category + '",\n' + \
' "codeValue": "' + category + '",\n' ' "url": "' + categoryUrl + '"\n' + \
skillsMarkup += \ ' }\n' + \
' "url": "' + categoryUrl + '"\n' ' }\n' + \
skillsMarkup += ' }\n' ' }'
skillsMarkup += ' }\n'
skillsMarkup += ' }'
elif skillDict['@type'] == 'Occupation': elif skillDict['@type'] == 'Occupation':
if not firstEntry: if not firstEntry:
skillsMarkup += ',\n' skillsMarkup += ',\n'
@ -555,19 +551,18 @@ def htmlHeaderWithPersonMarkup(cssFilename: str, instanceTitle: str,
skillsListStr += ', ' skillsListStr += ', '
skillsListStr += '"' + skillStr + '"' skillsListStr += '"' + skillStr + '"'
skillsListStr += ']' skillsListStr += ']'
skillsMarkup += ' {\n'
skillsMarkup += ' "@type": "Occupation",\n'
skillsMarkup += ' "name": "' + ocName + '",\n'
skillsMarkup += ' "description": ' + \
'"Fediverse instance occupation",\n'
skillsMarkup += ' "occupationLocation": {\n'
skillsMarkup += ' "@type": "City",\n'
skillsMarkup += \ skillsMarkup += \
' "name": "' + city + '"\n' ' {\n' + \
skillsMarkup += ' },\n' ' "@type": "Occupation",\n' + \
skillsMarkup += \ ' "name": "' + ocName + '",\n' + \
' "skills": ' + skillsListStr + '\n' ' "description": ' + \
skillsMarkup += ' }' '"Fediverse instance occupation",\n' + \
' "occupationLocation": {\n' + \
' "@type": "City",\n' + \
' "name": "' + city + '"\n' + \
' },\n' + \
' "skills": ' + skillsListStr + '\n' + \
' }'
firstEntry = False firstEntry = False
skillsMarkup += '\n ],\n' skillsMarkup += '\n ],\n'

View File

@ -63,19 +63,17 @@ def htmlWelcomeFinal(baseDir: str, nickname: str, domain: str,
finalForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) finalForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
finalForm += '<div class="container">' + finalText + '</div>\n'
finalForm += \ finalForm += \
'<div class="container">' + finalText + '</div>\n' + \
'<form enctype="multipart/form-data" method="POST" ' + \ '<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" ' + \ 'accept-charset="UTF-8" ' + \
'action="/users/' + nickname + '/profiledata">\n' 'action="/users/' + nickname + '/profiledata">\n' + \
finalForm += '<div class="container next">\n' '<div class="container next">\n' + \
finalForm += \
' <button type="submit" class="button" ' + \ ' <button type="submit" class="button" ' + \
'name="previewAvatar">' + translate['Go Back'] + '</button>\n' 'name="previewAvatar">' + translate['Go Back'] + '</button>\n' + \
finalForm += \
' <button type="submit" class="button" ' + \ ' <button type="submit" class="button" ' + \
'name="welcomeCompleteButton">' + translate['Next'] + '</button>\n' 'name="welcomeCompleteButton">' + translate['Next'] + '</button>\n' + \
finalForm += '</div>\n' '</div>\n'
finalForm += '</form>\n' finalForm += '</form>\n'
finalForm += htmlFooter() finalForm += htmlFooter()

View File

@ -174,18 +174,19 @@ def webfingerNodeInfo(httpPrefix: str, domainFull: str) -> {}:
def webfingerMeta(httpPrefix: str, domainFull: str) -> str: def webfingerMeta(httpPrefix: str, domainFull: str) -> str:
"""Return /.well-known/host-meta """Return /.well-known/host-meta
""" """
metaStr = "<?xml version=1.0' encoding=UTF-8'?>" metaStr = \
metaStr += "<XRD xmlns=http://docs.oasis-open.org/ns/xri/xrd-1.0'" "<?xml version=1.0' encoding=UTF-8'?>" + \
metaStr += " xmlns:hm=http://host-meta.net/xrd/1.0'>" "<XRD xmlns=http://docs.oasis-open.org/ns/xri/xrd-1.0'" + \
metaStr += "" " xmlns:hm=http://host-meta.net/xrd/1.0'>" + \
metaStr += "<hm:Host>" + domainFull + "</hm:Host>" "" + \
metaStr += "" "<hm:Host>" + domainFull + "</hm:Host>" + \
metaStr += "<Link rel=lrdd" "" + \
metaStr += " template=" + httpPrefix + "://" + domainFull + \ "<Link rel=lrdd" + \
"/describe?uri={uri}'>" " template=" + httpPrefix + "://" + domainFull + \
metaStr += " <Title>Resource Descriptor</Title>" "/describe?uri={uri}'>" + \
metaStr += " </Link>" " <Title>Resource Descriptor</Title>" + \
metaStr += "</XRD>" " </Link>" + \
"</XRD>"
return metaStr return metaStr
@ -264,18 +265,28 @@ def _webfingerUpdateFromProfile(wfJson: {}, actorJson: {}) -> bool:
"matrix": "matrix", "matrix": "matrix",
"email": "mailto", "email": "mailto",
"ssb": "ssb", "ssb": "ssb",
"briar": "briar",
"cwtch": "cwtch",
"jami": "jami",
"tox": "toxId" "tox": "toxId"
} }
aliasesNotFound = []
for name, alias in webfingerPropertyName.items():
aliasesNotFound.append(alias)
for propertyValue in actorJson['attachment']: for propertyValue in actorJson['attachment']:
if not propertyValue.get('name'): if not propertyValue.get('name'):
continue continue
propertyName = propertyValue['name'].lower() propertyName = propertyValue['name'].lower()
if not (propertyName.startswith('ssb') or found = False
propertyName.startswith('xmpp') or for name, alias in webfingerPropertyName.items():
propertyName.startswith('matrix') or if name == propertyName:
propertyName.startswith('email') or if alias in aliasesNotFound:
propertyName.startswith('tox')): aliasesNotFound.remove(alias)
found = True
break
if not found:
continue continue
if not propertyValue.get('type'): if not propertyValue.get('type'):
continue continue
@ -285,6 +296,9 @@ def _webfingerUpdateFromProfile(wfJson: {}, actorJson: {}) -> bool:
continue continue
newValue = propertyValue['value'].strip() newValue = propertyValue['value'].strip()
if '://' in newValue:
newValue = newValue.split('://')[1]
aliasIndex = 0 aliasIndex = 0
found = False found = False
for alias in wfJson['aliases']: for alias in wfJson['aliases']:
@ -300,6 +314,17 @@ def _webfingerUpdateFromProfile(wfJson: {}, actorJson: {}) -> bool:
else: else:
wfJson['aliases'].append(newAlias) wfJson['aliases'].append(newAlias)
changed = True changed = True
# remove any aliases which are no longer in the actor profile
removeAlias = []
for alias in aliasesNotFound:
for fullAlias in wfJson['aliases']:
if fullAlias.startswith(alias + ':'):
removeAlias.append(fullAlias)
for fullAlias in removeAlias:
wfJson['aliases'].remove(fullAlias)
changed = True
return changed return changed