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

main
Bob Mottram 2020-12-20 18:41:14 +00:00
commit 578919b965
67 changed files with 857 additions and 419 deletions

View File

@ -369,7 +369,7 @@ def sendAnnounceViaServer(baseDir: str, session,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
fromNickname, fromDomain, fromNickname, fromDomain,
postToBox) postToBox, 73528)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -123,7 +123,7 @@ def sendAvailabilityViaServer(baseDir: str, session,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, projectVersion, personCache, projectVersion,
httpPrefix, nickname, httpPrefix, nickname,
domain, postToBox) domain, postToBox, 57262)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -441,7 +441,7 @@ def sendBookmarkViaServer(baseDir: str, session,
fromPersonId, sharedInbox, avatarUrl, fromPersonId, sharedInbox, avatarUrl,
displayName) = getPersonBox(baseDir, session, wfRequest, personCache, displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname, projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox, 72483)
if not inboxUrl: if not inboxUrl:
if debug: if debug:
@ -518,7 +518,7 @@ def sendUndoBookmarkViaServer(baseDir: str, session,
fromPersonId, sharedInbox, avatarUrl, fromPersonId, sharedInbox, avatarUrl,
displayName) = getPersonBox(baseDir, session, wfRequest, personCache, displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname, projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox, 72528)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

251
daemon.py
View File

@ -239,6 +239,8 @@ from newswire import loadHashtagCategories
from newsdaemon import runNewswireWatchdog from newsdaemon import runNewswireWatchdog
from newsdaemon import runNewswireDaemon from newsdaemon import runNewswireDaemon
from filters import isFiltered from filters import isFiltered
from filters import addGlobalFilter
from filters import removeGlobalFilter
import os import os
@ -1012,7 +1014,7 @@ class PubServer(BaseHTTPRequestHandler):
print('Waiting for previous outbox thread to end') print('Waiting for previous outbox thread to end')
waitCtr = 0 waitCtr = 0
thName = accountOutboxThreadName thName = accountOutboxThreadName
while self.server.outboxThread[thName].isAlive() and waitCtr < 8: while self.server.outboxThread[thName].is_alive() and waitCtr < 8:
time.sleep(1) time.sleep(1)
waitCtr += 1 waitCtr += 1
if waitCtr >= 8: if waitCtr >= 8:
@ -1506,6 +1508,10 @@ class PubServer(BaseHTTPRequestHandler):
moderationButton = 'block' moderationButton = 'block'
elif moderationStr.startswith('submitUnblock'): elif moderationStr.startswith('submitUnblock'):
moderationButton = 'unblock' moderationButton = 'unblock'
elif moderationStr.startswith('submitFilter'):
moderationButton = 'filter'
elif moderationStr.startswith('submitUnfilter'):
moderationButton = 'unfilter'
elif moderationStr.startswith('submitSuspend'): elif moderationStr.startswith('submitSuspend'):
moderationButton = 'suspend' moderationButton = 'suspend'
elif moderationStr.startswith('submitUnsuspend'): elif moderationStr.startswith('submitUnsuspend'):
@ -1526,6 +1532,10 @@ class PubServer(BaseHTTPRequestHandler):
suspendAccount(baseDir, nickname, domain) suspendAccount(baseDir, nickname, domain)
if moderationButton == 'unsuspend': if moderationButton == 'unsuspend':
unsuspendAccount(baseDir, nickname) unsuspendAccount(baseDir, nickname)
if moderationButton == 'filter':
addGlobalFilter(baseDir, moderationText)
if moderationButton == 'unfilter':
removeGlobalFilter(baseDir, moderationText)
if moderationButton == 'block': if moderationButton == 'block':
fullBlockDomain = None fullBlockDomain = None
if moderationText.startswith('http') or \ if moderationText.startswith('http') or \
@ -1939,7 +1949,8 @@ class PubServer(BaseHTTPRequestHandler):
domain, domain,
domainFull, domainFull,
self.server.defaultTimeline, self.server.defaultTimeline,
self.server.newswire).encode('utf-8') self.server.newswire,
self.server.themeName).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
self._write(msg) self._write(msg)
@ -2032,7 +2043,8 @@ class PubServer(BaseHTTPRequestHandler):
domain, domain,
domainFull, domainFull,
self.server.defaultTimeline, self.server.defaultTimeline,
self.server.newswire).encode('utf-8') self.server.newswire,
self.server.themeName).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
self._write(msg) self._write(msg)
@ -3772,20 +3784,42 @@ class PubServer(BaseHTTPRequestHandler):
# which isn't implemented in Epicyon # which isn't implemented in Epicyon
actorJson['discoverable'] = False actorJson['discoverable'] = False
actorChanged = True actorChanged = True
if not actorJson['@context'][2].get('orgSchema'):
actorJson['@context'][2]['orgSchema'] = \
'toot:orgSchema'
actorChanged = True
if not actorJson['@context'][2].get('skills'):
actorJson['@context'][2]['skills'] = 'toot:skills'
actorChanged = True
if not actorJson['@context'][2].get('shares'):
actorJson['@context'][2]['shares'] = 'toot:shares'
actorChanged = True
if not actorJson['@context'][2].get('roles'):
actorJson['@context'][2]['roles'] = 'toot:roles'
actorChanged = True
if not actorJson['@context'][2].get('availability'):
actorJson['@context'][2]['availaibility'] = \
'toot:availability'
if not actorJson['@context'][2].get('nomadicLocations'):
actorJson['@context'][2]['nomadicLocations'] = \
'toot:nomadicLocations'
actorChanged = True
if actorJson.get('capabilityAcquisitionEndpoint'):
del actorJson['capabilityAcquisitionEndpoint']
actorChanged = True
# update the avatar/image url file extension # update the avatar/image url file extension
uploads = profileMediaTypesUploaded.items() uploads = profileMediaTypesUploaded.items()
for mType, lastPart in uploads: for mType, lastPart in uploads:
repStr = '/' + lastPart repStr = '/' + lastPart
if mType == 'avatar': if mType == 'avatar':
lastPartOfUrl = \ actorUrl = actorJson['icon']['url']
actorJson['icon']['url'].split('/')[-1] lastPartOfUrl = actorUrl.split('/')[-1]
srchStr = '/' + lastPartOfUrl srchStr = '/' + lastPartOfUrl
actorJson['icon']['url'] = \ actorUrl = actorUrl.replace(srchStr, repStr)
actorJson['icon']['url'].replace(srchStr, actorJson['icon']['url'] = actorUrl
repStr) print('actorUrl: ' + actorUrl)
if '.' in actorJson['icon']['url']: if '.' in actorUrl:
imgExt = \ imgExt = actorUrl.split('.')[-1]
actorJson['icon']['url'].split('.')[-1]
if imgExt == 'jpg': if imgExt == 'jpg':
imgExt = 'jpeg' imgExt = 'jpeg'
actorJson['icon']['mediaType'] = \ actorJson['icon']['mediaType'] = \
@ -3814,6 +3848,9 @@ class PubServer(BaseHTTPRequestHandler):
if not skillName: if not skillName:
skillCtr += 1 skillCtr += 1
continue continue
if isFiltered(baseDir, nickname, domain, skillName):
skillCtr += 1
continue
skillValue = \ skillValue = \
fields.get('skillValue' + str(skillCtr)) fields.get('skillValue' + str(skillCtr))
if not skillValue: if not skillValue:
@ -3826,6 +3863,9 @@ class PubServer(BaseHTTPRequestHandler):
int(skillValue): int(skillValue):
actorChanged = True actorChanged = True
newSkills[skillName] = int(skillValue) newSkills[skillName] = int(skillValue)
skillsStr = self.server.translate['Skills']
setHashtagCategory(baseDir, skillName,
skillsStr.lower())
skillCtr += 1 skillCtr += 1
if len(actorJson['skills'].items()) != \ if len(actorJson['skills'].items()) != \
len(newSkills.items()): len(newSkills.items()):
@ -3847,8 +3887,14 @@ class PubServer(BaseHTTPRequestHandler):
# change displayed name # change displayed name
if fields.get('displayNickname'): if fields.get('displayNickname'):
if fields['displayNickname'] != actorJson['name']: if fields['displayNickname'] != actorJson['name']:
actorJson['name'] = \ displayName = \
removeHtml(fields['displayNickname']) removeHtml(fields['displayNickname'])
if not isFiltered(baseDir,
nickname, domain,
displayName):
actorJson['name'] = displayName
else:
actorJson['name'] = nickname
actorChanged = True actorChanged = True
# change media instance status # change media instance status
@ -4136,18 +4182,20 @@ class PubServer(BaseHTTPRequestHandler):
if fields.get('bio'): if fields.get('bio'):
if fields['bio'] != actorJson['summary']: if fields['bio'] != actorJson['summary']:
bioStr = removeHtml(fields['bio']) bioStr = removeHtml(fields['bio'])
actorTags = {} if not isFiltered(baseDir,
actorJson['summary'] = \ nickname, domain, bioStr):
addHtmlTags(baseDir, actorTags = {}
httpPrefix, actorJson['summary'] = \
nickname, addHtmlTags(baseDir,
domainFull, httpPrefix,
bioStr, [], actorTags) nickname,
if actorTags: domainFull,
actorJson['tag'] = [] bioStr, [], actorTags)
for tagName, tag in actorTags.items(): if actorTags:
actorJson['tag'].append(tag) actorJson['tag'] = []
actorChanged = True for tagName, tag in actorTags.items():
actorJson['tag'].append(tag)
actorChanged = True
else: else:
if actorJson['summary']: if actorJson['summary']:
actorJson['summary'] = '' actorJson['summary'] = ''
@ -5030,6 +5078,7 @@ class PubServer(BaseHTTPRequestHandler):
cookie: str, debug: bool) -> None: cookie: str, debug: bool) -> None:
"""Show person options screen """Show person options screen
""" """
backToPath = ''
optionsStr = path.split('?options=')[1] optionsStr = path.split('?options=')[1]
originPathStr = path.split('?options=')[0] originPathStr = path.split('?options=')[0]
if ';' in optionsStr and '/users/news/' not in path: if ';' in optionsStr and '/users/news/' not in path:
@ -5038,6 +5087,14 @@ class PubServer(BaseHTTPRequestHandler):
optionsActor = optionsList[0] optionsActor = optionsList[0]
optionsPageNumber = optionsList[1] optionsPageNumber = optionsList[1]
optionsProfileUrl = optionsList[2] optionsProfileUrl = optionsList[2]
if '.' in optionsProfileUrl and \
optionsProfileUrl.startswith('/members/'):
ext = optionsProfileUrl.split('.')[-1]
optionsProfileUrl = optionsProfileUrl.split('/members/')[1]
optionsProfileUrl = optionsProfileUrl.replace('.' + ext, '')
optionsProfileUrl = \
'/users/' + optionsProfileUrl + '/avatar.' + ext
backToPath = 'moderation'
if optionsPageNumber.isdigit(): if optionsPageNumber.isdigit():
pageNumber = int(optionsPageNumber) pageNumber = int(optionsPageNumber)
optionsLink = None optionsLink = None
@ -5083,7 +5140,8 @@ class PubServer(BaseHTTPRequestHandler):
toxAddress, jamiAddress, toxAddress, jamiAddress,
PGPpubKey, PGPfingerprint, PGPpubKey, PGPfingerprint,
emailAddress, emailAddress,
self.server.dormantMonths).encode('utf-8') self.server.dormantMonths,
backToPath).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
self._write(msg) self._write(msg)
@ -6605,6 +6663,7 @@ class PubServer(BaseHTTPRequestHandler):
YTReplacementDomain, YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire, self.server.newswire,
self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
actorJson['roles'], actorJson['roles'],
None, None) None, None)
@ -6685,6 +6744,7 @@ class PubServer(BaseHTTPRequestHandler):
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
self.server.newswire, self.server.newswire,
self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
actorJson['skills'], actorJson['skills'],
None, None) None, None)
@ -7046,7 +7106,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
if GETstartTime: if GETstartTime:
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
'show status done', 'show status done',
@ -7169,7 +7230,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized, self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7285,7 +7346,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized, self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7401,7 +7462,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7517,7 +7579,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7642,7 +7705,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7763,7 +7827,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7847,7 +7912,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized, self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7947,7 +8012,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -8066,7 +8132,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -8177,7 +8244,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons, self.server.iconsAsButtons,
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized) authorized,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -8374,6 +8442,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire, self.server.newswire,
self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
shares, shares,
pageNumber, sharesPerPage) pageNumber, sharesPerPage)
@ -8466,6 +8535,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire, self.server.newswire,
self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
following, following,
pageNumber, pageNumber,
@ -8558,6 +8628,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire, self.server.newswire,
self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
followers, followers,
pageNumber, pageNumber,
@ -8625,6 +8696,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire, self.server.newswire,
self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
None, None).encode('utf-8') None, None).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
@ -8870,9 +8942,9 @@ class PubServer(BaseHTTPRequestHandler):
self._404() self._404()
return True return True
def _columImage(self, side: str, callingDomain: str, path: str, def _columnImage(self, side: str, callingDomain: str, path: str,
baseDir: str, domain: str, port: int, baseDir: str, domain: str, port: int,
GETstartTime, GETtimings: {}) -> bool: GETstartTime, GETtimings: {}) -> bool:
"""Shows an image at the top of the left/right column """Shows an image at the top of the left/right column
""" """
nickname = getNicknameFromActor(path) nickname = getNicknameFromActor(path)
@ -8999,10 +9071,10 @@ class PubServer(BaseHTTPRequestHandler):
self._404() self._404()
return True return True
def _showAvatarOrBackground(self, callingDomain: str, path: str, def _showAvatarOrBanner(self, callingDomain: str, path: str,
baseDir: str, domain: str, baseDir: str, domain: str,
GETstartTime, GETtimings: {}) -> bool: GETstartTime, GETtimings: {}) -> bool:
"""Shows an avatar or profile background image """Shows an avatar or banner or profile background image
""" """
if '/users/' in path: if '/users/' in path:
if self._pathIsImage(path): if self._pathIsImage(path):
@ -9010,11 +9082,20 @@ class PubServer(BaseHTTPRequestHandler):
if '/' in avatarStr and '.temp.' not in path: if '/' in avatarStr and '.temp.' not in path:
avatarNickname = avatarStr.split('/')[0] avatarNickname = avatarStr.split('/')[0]
avatarFile = avatarStr.split('/')[1] avatarFile = avatarStr.split('/')[1]
avatarFileExt = avatarFile.split('.')[-1]
# remove any numbers, eg. avatar123.png becomes avatar.png # remove any numbers, eg. avatar123.png becomes avatar.png
if avatarFile.startswith('avatar'): if avatarFile.startswith('avatar'):
avatarFile = 'avatar.' + avatarFile.split('.')[1] avatarFile = 'avatar.' + avatarFileExt
elif avatarFile.startswith('banner'):
avatarFile = 'banner.' + avatarFileExt
elif avatarFile.startswith('search_banner'):
avatarFile = 'search_banner.' + avatarFileExt
elif avatarFile.startswith('image'): elif avatarFile.startswith('image'):
avatarFile = 'image.' + avatarFile.split('.')[1] avatarFile = 'image.' + avatarFileExt
elif avatarFile.startswith('left_col_image'):
avatarFile = 'left_col_image.' + avatarFileExt
elif avatarFile.startswith('right_col_image'):
avatarFile = 'right_col_image.' + avatarFileExt
avatarFilename = \ avatarFilename = \
baseDir + '/accounts/' + \ baseDir + '/accounts/' + \
avatarNickname + '@' + domain + '/' + avatarFile avatarNickname + '@' + domain + '/' + avatarFile
@ -9138,7 +9219,8 @@ class PubServer(BaseHTTPRequestHandler):
nickname, domain, nickname, domain,
domainFull, domainFull,
self.server.defaultTimeline, self.server.defaultTimeline,
self.server.newswire).encode('utf-8') self.server.newswire,
self.server.themeName).encode('utf-8')
if not msg: if not msg:
print('Error replying to ' + inReplyToUrl) print('Error replying to ' + inReplyToUrl)
self._404() self._404()
@ -9167,7 +9249,8 @@ class PubServer(BaseHTTPRequestHandler):
path, domain, path, domain,
port, port,
httpPrefix, httpPrefix,
self.server.defaultTimeline).encode('utf-8') self.server.defaultTimeline,
self.server.themeName).encode('utf-8')
if msg: if msg:
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -9181,7 +9264,7 @@ class PubServer(BaseHTTPRequestHandler):
def _editLinks(self, callingDomain: str, path: str, def _editLinks(self, callingDomain: str, path: str,
translate: {}, baseDir: str, translate: {}, baseDir: str,
httpPrefix: str, domain: str, port: int, httpPrefix: str, domain: str, port: int,
cookie: str) -> bool: cookie: str, theme: str) -> bool:
"""Show the links from the left column """Show the links from the left column
""" """
if '/users/' in path and path.endswith('/editlinks'): if '/users/' in path and path.endswith('/editlinks'):
@ -9191,7 +9274,8 @@ class PubServer(BaseHTTPRequestHandler):
path, domain, path, domain,
port, port,
httpPrefix, httpPrefix,
self.server.defaultTimeline).encode('utf-8') self.server.defaultTimeline,
theme).encode('utf-8')
if msg: if msg:
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -9215,7 +9299,8 @@ class PubServer(BaseHTTPRequestHandler):
path, domain, path, domain,
port, port,
httpPrefix, httpPrefix,
self.server.defaultTimeline).encode('utf-8') self.server.defaultTimeline,
self.server.themeName).encode('utf-8')
if msg: if msg:
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -9992,19 +10077,19 @@ class PubServer(BaseHTTPRequestHandler):
return return
if self.path.endswith('/left_col_image.png'): if self.path.endswith('/left_col_image.png'):
if self._columImage('left', callingDomain, self.path, if self._columnImage('left', callingDomain, self.path,
self.server.baseDir, self.server.baseDir,
self.server.domain, self.server.domain,
self.server.port, self.server.port,
GETstartTime, GETtimings): GETstartTime, GETtimings):
return return
if self.path.endswith('/right_col_image.png'): if self.path.endswith('/right_col_image.png'):
if self._columImage('right', callingDomain, self.path, if self._columnImage('right', callingDomain, self.path,
self.server.baseDir, self.server.baseDir,
self.server.domain, self.server.domain,
self.server.port, self.server.port,
GETstartTime, GETtimings): GETstartTime, GETtimings):
return return
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
@ -10082,10 +10167,10 @@ class PubServer(BaseHTTPRequestHandler):
# show avatar or background image # show avatar or background image
# Note that this comes before the busy flag to avoid conflicts # Note that this comes before the busy flag to avoid conflicts
if self._showAvatarOrBackground(callingDomain, self.path, if self._showAvatarOrBanner(callingDomain, self.path,
self.server.baseDir, self.server.baseDir,
self.server.domain, self.server.domain,
GETstartTime, GETtimings): GETstartTime, GETtimings):
return return
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
@ -10208,7 +10293,8 @@ class PubServer(BaseHTTPRequestHandler):
authorized, authorized,
rssIconAtTop, rssIconAtTop,
iconsAsButtons, iconsAsButtons,
defaultTimeline).encode('utf-8') defaultTimeline,
self.server.themeName).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
self._write(msg) self._write(msg)
@ -10239,7 +10325,8 @@ class PubServer(BaseHTTPRequestHandler):
authorized, authorized,
self.server.rssIconAtTop, self.server.rssIconAtTop,
iconsAsButtons, iconsAsButtons,
defaultTimeline).encode('utf-8') defaultTimeline,
self.server.themeName).encode('utf-8')
self._set_headers('text/html', len(msg), cookie, callingDomain) self._set_headers('text/html', len(msg), cookie, callingDomain)
self._write(msg) self._write(msg)
self.server.GETbusy = False self.server.GETbusy = False
@ -10307,7 +10394,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.translate, self.server.translate,
self.server.baseDir, self.path, self.server.baseDir, self.path,
self.server.domain, self.server.domain,
self.server.defaultTimeline).encode('utf-8') self.server.defaultTimeline,
self.server.themeName).encode('utf-8')
self._set_headers('text/html', len(msg), cookie, callingDomain) self._set_headers('text/html', len(msg), cookie, callingDomain)
self._write(msg) self._write(msg)
self.server.GETbusy = False self.server.GETbusy = False
@ -10321,7 +10409,8 @@ class PubServer(BaseHTTPRequestHandler):
msg = htmlSearchHashtagCategory(self.server.cssCache, msg = htmlSearchHashtagCategory(self.server.cssCache,
self.server.translate, self.server.translate,
self.server.baseDir, self.path, self.server.baseDir, self.path,
self.server.domain) self.server.domain,
self.server.themeName)
if msg: if msg:
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/html', len(msg), cookie, callingDomain) self._set_headers('text/html', len(msg), cookie, callingDomain)
@ -10813,7 +10902,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.httpPrefix, self.server.httpPrefix,
self.server.domain, self.server.domain,
self.server.port, self.server.port,
cookie): cookie,
self.server.themeName):
return return
# edit newswire from the right column of the timeline # edit newswire from the right column of the timeline
@ -11643,7 +11733,8 @@ class PubServer(BaseHTTPRequestHandler):
fields['subject'], fields['subject'],
fields['message'], fields['message'],
filename, attachmentMediaType, filename, attachmentMediaType,
fields['imageDescription']) fields['imageDescription'],
self.server.themeName)
if messageJson: if messageJson:
messageJson = messageJson.encode('utf-8') messageJson = messageJson.encode('utf-8')
self._set_headers('text/html', self._set_headers('text/html',
@ -12087,7 +12178,7 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.newPostThread.get(newPostThreadName): if self.server.newPostThread.get(newPostThreadName):
print('Waiting for previous new post thread to end') print('Waiting for previous new post thread to end')
waitCtr = 0 waitCtr = 0
while (self.server.newPostThread[newPostThreadName].isAlive() and while (self.server.newPostThread[newPostThreadName].is_alive() and
waitCtr < 8): waitCtr < 8):
time.sleep(1) time.sleep(1)
waitCtr += 1 waitCtr += 1
@ -12980,12 +13071,13 @@ class EpicyonServer(ThreadingHTTPServer):
return HTTPServer.handle_error(self, request, client_address) return HTTPServer.handle_error(self, request, client_address)
def runPostsQueue(baseDir: str, sendThreads: [], debug: bool) -> None: def runPostsQueue(baseDir: str, sendThreads: [], debug: bool,
timeoutMins: int) -> None:
"""Manages the threads used to send posts """Manages the threads used to send posts
""" """
while True: while True:
time.sleep(1) time.sleep(1)
removeDormantThreads(baseDir, sendThreads, debug) removeDormantThreads(baseDir, sendThreads, debug, timeoutMins)
def runSharesExpire(versionNumber: str, baseDir: str) -> None: def runSharesExpire(versionNumber: str, baseDir: str) -> None:
@ -13004,7 +13096,7 @@ def runPostsWatchdog(projectVersion: str, httpd) -> None:
httpd.thrPostsQueue.start() httpd.thrPostsQueue.start()
while True: while True:
time.sleep(20) time.sleep(20)
if not httpd.thrPostsQueue.isAlive(): if not httpd.thrPostsQueue.is_alive():
httpd.thrPostsQueue.kill() httpd.thrPostsQueue.kill()
httpd.thrPostsQueue = postsQueueOriginal.clone(runPostsQueue) httpd.thrPostsQueue = postsQueueOriginal.clone(runPostsQueue)
httpd.thrPostsQueue.start() httpd.thrPostsQueue.start()
@ -13019,7 +13111,7 @@ def runSharesExpireWatchdog(projectVersion: str, httpd) -> None:
httpd.thrSharesExpire.start() httpd.thrSharesExpire.start()
while True: while True:
time.sleep(20) time.sleep(20)
if not httpd.thrSharesExpire.isAlive(): if not httpd.thrSharesExpire.is_alive():
httpd.thrSharesExpire.kill() httpd.thrSharesExpire.kill()
httpd.thrSharesExpire = sharesExpireOriginal.clone(runSharesExpire) httpd.thrSharesExpire = sharesExpireOriginal.clone(runSharesExpire)
httpd.thrSharesExpire.start() httpd.thrSharesExpire.start()
@ -13048,7 +13140,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
break break
def runDaemon(dormantMonths: int, def runDaemon(sendThreadsTimeoutMins: int,
dormantMonths: int,
maxNewswirePosts: int, maxNewswirePosts: int,
allowLocalNetworkAccess: bool, allowLocalNetworkAccess: bool,
maxFeedItemSizeKb: int, maxFeedItemSizeKb: int,
@ -13343,10 +13436,14 @@ def runDaemon(dormantMonths: int,
httpd.maxPostsInBox), daemon=True) httpd.maxPostsInBox), daemon=True)
httpd.thrCache.start() httpd.thrCache.start()
# number of mins after which sending posts or updates will expire
httpd.sendThreadsTimeoutMins = sendThreadsTimeoutMins
print('Creating posts queue') print('Creating posts queue')
httpd.thrPostsQueue = \ httpd.thrPostsQueue = \
threadWithTrace(target=runPostsQueue, threadWithTrace(target=runPostsQueue,
args=(baseDir, httpd.sendThreads, debug), daemon=True) args=(baseDir, httpd.sendThreads, debug,
httpd.sendThreadsTimeoutMins), daemon=True)
if not unitTest: if not unitTest:
httpd.thrPostsWatchdog = \ httpd.thrPostsWatchdog = \
threadWithTrace(target=runPostsWatchdog, threadWithTrace(target=runPostsWatchdog,

View File

@ -136,7 +136,7 @@ def sendDeleteViaServer(baseDir: str, session,
fromPersonId, sharedInbox, avatarUrl, fromPersonId, sharedInbox, avatarUrl,
displayName) = getPersonBox(baseDir, session, wfRequest, personCache, displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname, projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox, 53036)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -4,8 +4,8 @@
--options-bg-color: #282c37; --options-bg-color: #282c37;
--options-link-bg-color: transparent; --options-link-bg-color: transparent;
--options-fg-color: #dddddd; --options-fg-color: #dddddd;
--main-link-color: #999; --options-main-link-color: #999;
--main-visited-color: #888; --options-main-visited-color: #888;
--border-color: #505050; --border-color: #505050;
--font-size-header: 18px; --font-size-header: 18px;
--font-color-header: #ccc; --font-color-header: #ccc;
@ -34,7 +34,7 @@
--follow-text-entry-width: 90%; --follow-text-entry-width: 90%;
--focus-color: white; --focus-color: white;
--petname-width-chars: 16ch; --petname-width-chars: 16ch;
--main-link-color-hover: #bbb; --options-main-link-color-hover: #bbb;
} }
@font-face { @font-face {
@ -73,25 +73,25 @@ a, u {
} }
a:visited{ a:visited{
color: var(--main-visited-color); color: var(--options-main-visited-color);
background: var(--options-link-bg-color); background: var(--options-link-bg-color);
font-weight: normal; font-weight: normal;
text-decoration: none; text-decoration: none;
} }
a:link { a:link {
color: var(--main-link-color); color: var(--options-main-link-color);
background: var(--options-link-bg-color); background: var(--options-link-bg-color);
font-weight: normal; font-weight: normal;
text-decoration: none; text-decoration: none;
} }
a:link:hover { a:link:hover {
color: var(--main-link-color-hover); color: var(--options-main-link-color-hover);
} }
a:visited:hover { a:visited:hover {
color: var(--main-link-color-hover); color: var(--options-main-link-color-hover);
} }
a:focus { a:focus {
@ -116,12 +116,12 @@ a:focus {
.imText { .imText {
font-size: var(--font-size4); font-size: var(--font-size4);
color: var(--main-link-color); color: var(--options-main-link-color);
} }
.pgp { .pgp {
font-size: var(--font-size5); font-size: var(--font-size5);
color: var(--main-link-color); color: var(--options-main-link-color);
background: var(--options-link-bg-color); background: var(--options-link-bg-color);
} }

View File

@ -950,6 +950,14 @@ div.container {
font-size: var(--font-size); font-size: var(--font-size);
color: var(--title-color); color: var(--title-color);
} }
.accountsTable {
width: 100%;
border: 0;
}
.accountsTableCol {
width: 20%;
text-align: center;
}
.containerHeader { .containerHeader {
border: var(--border-width-header) solid var(--border-color); border: var(--border-width-header) solid var(--border-color);
background-color: var(--header-bg-color); background-color: var(--header-bg-color);
@ -1601,6 +1609,14 @@ div.container {
font-size: var(--font-size-mobile); font-size: var(--font-size-mobile);
color: var(--title-color); color: var(--title-color);
} }
.accountsTable {
width: 100%;
border: 0;
}
.accountsTableCol {
width: 20%;
text-align: center;
}
.containerHeader { .containerHeader {
border: var(--border-width-header) solid var(--border-color); border: var(--border-width-header) solid var(--border-color);
background-color: var(--header-bg-color); background-color: var(--header-bg-color);

View File

@ -122,6 +122,11 @@ parser.add_argument('--dormantMonths',
default=3, default=3,
help='How many months does a followed account need to ' + help='How many months does a followed account need to ' +
'be unseen for before being considered dormant') 'be unseen for before being considered dormant')
parser.add_argument('--sendThreadsTimeoutMins',
dest='sendThreadsTimeoutMins', type=int,
default=30,
help='How many minutes before a thread to send out ' +
'posts expires')
parser.add_argument('--maxNewswirePosts', parser.add_argument('--maxNewswirePosts',
dest='maxNewswirePosts', type=int, dest='maxNewswirePosts', type=int,
default=20, default=20,
@ -2035,6 +2040,11 @@ dormantMonths = \
if dormantMonths is not None: if dormantMonths is not None:
args.dormantMonths = int(dormantMonths) args.dormantMonths = int(dormantMonths)
sendThreadsTimeoutMins = \
getConfigParam(baseDir, 'sendThreadsTimeoutMins')
if sendThreadsTimeoutMins is not None:
args.sendThreadsTimeoutMins = int(sendThreadsTimeoutMins)
allowNewsFollowers = \ allowNewsFollowers = \
getConfigParam(baseDir, 'allowNewsFollowers') getConfigParam(baseDir, 'allowNewsFollowers')
if allowNewsFollowers is not None: if allowNewsFollowers is not None:
@ -2083,7 +2093,8 @@ if setTheme(baseDir, themeName, domain, args.allowLocalNetworkAccess):
print('Theme set to ' + themeName) print('Theme set to ' + themeName)
if __name__ == "__main__": if __name__ == "__main__":
runDaemon(args.dormantMonths, runDaemon(args.sendThreadsTimeoutMins,
args.dormantMonths,
args.maxNewswirePosts, args.maxNewswirePosts,
args.allowLocalNetworkAccess, args.allowLocalNetworkAccess,
args.maxFeedItemSizeKb, args.maxFeedItemSizeKb,

View File

@ -23,6 +23,24 @@ def addFilter(baseDir: str, nickname: str, domain: str, words: str) -> bool:
return True return True
def addGlobalFilter(baseDir: str, words: str) -> bool:
"""Adds a global filter for particular words within
the content of a incoming posts
"""
if not words:
return False
if len(words) < 2:
return False
filtersFilename = baseDir + '/accounts/filters.txt'
if os.path.isfile(filtersFilename):
if words in open(filtersFilename).read():
return False
filtersFile = open(filtersFilename, "a+")
filtersFile.write(words + '\n')
filtersFile.close()
return True
def removeFilter(baseDir: str, nickname: str, domain: str, def removeFilter(baseDir: str, nickname: str, domain: str,
words: str) -> bool: words: str) -> bool:
"""Removes a word filter """Removes a word filter
@ -43,6 +61,24 @@ def removeFilter(baseDir: str, nickname: str, domain: str,
return False return False
def removeGlobalFilter(baseDir: str, words: str) -> bool:
"""Removes a global word filter
"""
filtersFilename = baseDir + '/accounts/filters.txt'
if os.path.isfile(filtersFilename):
if words in open(filtersFilename).read():
with open(filtersFilename, 'r') as fp:
with open(filtersFilename + '.new', 'w+') as fpnew:
for line in fp:
line = line.replace('\n', '')
if line != words:
fpnew.write(line + '\n')
if os.path.isfile(filtersFilename + '.new'):
os.rename(filtersFilename + '.new', filtersFilename)
return True
return False
def isTwitterPost(content: str) -> bool: def isTwitterPost(content: str) -> bool:
"""Returns true if the given post content is a retweet or twitter crosspost """Returns true if the given post content is a retweet or twitter crosspost
""" """
@ -53,12 +89,45 @@ def isTwitterPost(content: str) -> bool:
return False return False
def isFilteredBase(filename: str, content: str) -> bool:
"""Uses the given file containing filtered words to check
the given content
"""
if not os.path.isfile(filename):
return False
with open(filename, 'r') as fp:
for line in fp:
filterStr = line.replace('\n', '').replace('\r', '')
if not filterStr:
continue
if len(filterStr) < 2:
continue
if '+' not in filterStr:
if filterStr in content:
return True
else:
filterWords = filterStr.replace('"', '').split('+')
for word in filterWords:
if word not in content:
return False
return True
return False
def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool: def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool:
"""Should the given content be filtered out? """Should the given content be filtered out?
This is a simple type of filter which just matches words, not a regex This is a simple type of filter which just matches words, not a regex
You can add individual words or use word1+word2 to indicate that two You can add individual words or use word1+word2 to indicate that two
words must be present although not necessarily adjacent words must be present although not necessarily adjacent
""" """
globalFiltersFilename = baseDir + '/accounts/filters.txt'
if isFilteredBase(globalFiltersFilename, content):
return True
if not nickname or not domain:
return False
# optionally remove retweets # optionally remove retweets
removeTwitter = baseDir + '/accounts/' + \ removeTwitter = baseDir + '/accounts/' + \
nickname + '@' + domain + '/.removeTwitter' nickname + '@' + domain + '/.removeTwitter'
@ -66,19 +135,6 @@ def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool:
if isTwitterPost(content): if isTwitterPost(content):
return True return True
filtersFilename = baseDir + '/accounts/' + \ accountFiltersFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/filters.txt' nickname + '@' + domain + '/filters.txt'
if os.path.isfile(filtersFilename): return isFilteredBase(accountFiltersFilename, content)
with open(filtersFilename, 'r') as fp:
for line in fp:
filterStr = line.replace('\n', '').replace('\r', '')
if '+' not in filterStr:
if filterStr in content:
return True
else:
filterWords = filterStr.replace('"', '').split('+')
for word in filterWords:
if word not in content:
return False
return True
return False

View File

@ -995,7 +995,7 @@ def sendFollowRequestViaServer(baseDir: str, session,
fromPersonId, sharedInbox, avatarUrl, fromPersonId, sharedInbox, avatarUrl,
displayName) = getPersonBox(baseDir, session, wfRequest, personCache, displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname, projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox, 52025)
if not inboxUrl: if not inboxUrl:
if debug: if debug:
@ -1086,7 +1086,8 @@ def sendUnfollowRequestViaServer(baseDir: str, session,
wfRequest, personCache, wfRequest, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
fromNickname, fromNickname,
fromDomain, postToBox) fromDomain, postToBox,
76536)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -173,9 +173,19 @@ def inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
avatarUrl = None avatarUrl = None
if boxname != 'tlevents' and boxname != 'outbox': if boxname != 'tlevents' and boxname != 'outbox':
boxname = 'inbox' boxname = 'inbox'
# wfRequest = {}
# requestHandle = nickname + '@' + domain
# if cachedWebfingers.get(requestHandle):
# wfRequest = cachedWebfingers[requestHandle]
# elif cachedWebfingers.get(requestHandle + ':' + str(port)):
# wfRequest = cachedWebfingers[requestHandle + ':' + str(port)]
# TODO: this may need to be changed
wfRequest = cachedWebfingers
individualPostAsHtml(True, recentPostsCache, maxRecentPosts, individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
translate, pageNumber, translate, pageNumber,
baseDir, session, cachedWebfingers, personCache, baseDir, session, wfRequest, personCache,
nickname, domain, port, postJsonObject, nickname, domain, port, postJsonObject,
avatarUrl, True, allowDeletion, avatarUrl, True, allowDeletion,
httpPrefix, __version__, boxname, None, httpPrefix, __version__, boxname, None,
@ -2456,7 +2466,7 @@ def runInboxQueueWatchdog(projectVersion: str, httpd) -> None:
httpd.thrInboxQueue.start() httpd.thrInboxQueue.start()
while True: while True:
time.sleep(20) time.sleep(20)
if not httpd.thrInboxQueue.isAlive() or httpd.restartInboxQueue: if not httpd.thrInboxQueue.is_alive() or httpd.restartInboxQueue:
httpd.restartInboxQueueInProgress = True httpd.restartInboxQueueInProgress = True
httpd.thrInboxQueue.kill() httpd.thrInboxQueue.kill()
httpd.thrInboxQueue = inboxQueueOriginal.clone(runInboxQueue) httpd.thrInboxQueue = inboxQueueOriginal.clone(runInboxQueue)

View File

@ -257,7 +257,7 @@ def sendLikeViaServer(baseDir: str, session,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
fromNickname, fromDomain, fromNickname, fromDomain,
postToBox) postToBox, 72873)
if not inboxUrl: if not inboxUrl:
if debug: if debug:
@ -335,7 +335,8 @@ def sendUndoLikeViaServer(baseDir: str, session,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, projectVersion, personCache, projectVersion,
httpPrefix, fromNickname, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox,
72625)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -752,7 +752,7 @@ def runNewswireWatchdog(projectVersion: str, httpd) -> None:
httpd.thrNewswireDaemon.start() httpd.thrNewswireDaemon.start()
while True: while True:
time.sleep(50) time.sleep(50)
if not httpd.thrNewswireDaemon.isAlive(): if not httpd.thrNewswireDaemon.is_alive():
httpd.thrNewswireDaemon.kill() httpd.thrNewswireDaemon.kill()
httpd.thrNewswireDaemon = \ httpd.thrNewswireDaemon = \
newswireOriginal.clone(runNewswireDaemon) newswireOriginal.clone(runNewswireDaemon)

View File

@ -110,7 +110,7 @@ def addNewswireDictEntry(baseDir: str, domain: str,
allText = title + ' ' + description allText = title + ' ' + description
# check that none of the text is filtered against # check that none of the text is filtered against
if isFiltered(baseDir, 'news', domain, allText): if isFiltered(baseDir, None, None, allText):
return return
if tags is None: if tags is None:

View File

@ -191,7 +191,13 @@ def getDefaultPersonContext() -> str:
'fingerprintKey': {'@id': 'toot:fingerprintKey', '@type': '@id'}, 'fingerprintKey': {'@id': 'toot:fingerprintKey', '@type': '@id'},
'messageFranking': 'toot:messageFranking', 'messageFranking': 'toot:messageFranking',
'publicKeyBase64': 'toot:publicKeyBase64', 'publicKeyBase64': 'toot:publicKeyBase64',
'discoverable': 'toot:discoverable' 'discoverable': 'toot:discoverable',
'orgSchema': 'toot:orgSchema',
'shares': 'toot:shares',
'skills': 'toot:skills',
'roles': 'toot:roles',
'availability': 'toot:availability',
'nomadicLocations': 'toot:nomadicLocations'
} }

171
posts.py
View File

@ -141,18 +141,27 @@ def cleanHtml(rawHtml: str) -> str:
return html.unescape(text) return html.unescape(text)
def getUserUrl(wfRequest: {}) -> str: def getUserUrl(wfRequest: {}, sourceId=0) -> str:
if wfRequest.get('links'): """Gets the actor url from a webfinger request
for link in wfRequest['links']: """
if link.get('type') and link.get('href'): print('getUserUrl: ' + str(sourceId) + ' ' + str(wfRequest))
if link['type'] == 'application/activity+json': if not wfRequest.get('links'):
if not ('/users/' in link['href'] or print('getUserUrl webfinger activity+json contains no links ' +
'/accounts/' in link['href'] or str(sourceId) + ' ' + str(wfRequest))
'/profile/' in link['href'] or return None
'/channel/' in link['href']): for link in wfRequest['links']:
print('Webfinger activity+json contains ' + if not (link.get('type') and link.get('href')):
'single user instance actor') continue
return link['href'] if link['type'] != 'application/activity+json':
continue
if not ('/users/' in link['href'] or
'/accounts/' in link['href'] or
'/profile/' in link['href'] or
'/channel/' in link['href']):
print('getUserUrl webfinger activity+json ' +
'contains single user instance actor ' +
str(sourceId) + ' ' + str(link))
return link['href']
return None return None
@ -198,13 +207,14 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
personCache: {}, personCache: {},
projectVersion: str, httpPrefix: str, projectVersion: str, httpPrefix: str,
nickname: str, domain: str, nickname: str, domain: str,
boxName='inbox') -> (str, str, str, str, str, str, str, str): boxName='inbox',
sourceId=0) -> (str, str, str, str, str, str, str, str):
profileStr = 'https://www.w3.org/ns/activitystreams' profileStr = 'https://www.w3.org/ns/activitystreams'
asHeader = { asHeader = {
'Accept': 'application/activity+json; profile="' + profileStr + '"' 'Accept': 'application/activity+json; profile="' + profileStr + '"'
} }
if not wfRequest.get('errors'): if not wfRequest.get('errors'):
personUrl = getUserUrl(wfRequest) personUrl = getUserUrl(wfRequest, sourceId)
else: else:
if nickname == 'dev': if nickname == 'dev':
# try single user instance # try single user instance
@ -1174,7 +1184,7 @@ def postIsAddressedToFollowers(baseDir: str,
postJsonObject: {}) -> bool: postJsonObject: {}) -> bool:
"""Returns true if the given post is addressed to followers of the nickname """Returns true if the given post is addressed to followers of the nickname
""" """
domain = getFullDomain(domain, port) domainFull = getFullDomain(domain, port)
if not postJsonObject.get('object'): if not postJsonObject.get('object'):
return False return False
@ -1192,7 +1202,7 @@ def postIsAddressedToFollowers(baseDir: str,
if postJsonObject.get('cc'): if postJsonObject.get('cc'):
ccList = postJsonObject['cc'] ccList = postJsonObject['cc']
followersUrl = httpPrefix + '://' + domain + '/users/' + \ followersUrl = httpPrefix + '://' + domainFull + '/users/' + \
nickname + '/followers' nickname + '/followers'
# does the followers url exist in 'to' or 'cc' lists? # does the followers url exist in 'to' or 'cc' lists?
@ -1765,7 +1775,8 @@ def sendPost(projectVersion: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
nickname, domain, postToBox) nickname, domain, postToBox,
72533)
if not inboxUrl: if not inboxUrl:
return 3 return 3
@ -1880,7 +1891,8 @@ def sendPostViaServer(projectVersion: str,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
fromNickname, fromNickname,
fromDomain, postToBox) fromDomain, postToBox,
82796)
if not inboxUrl: if not inboxUrl:
if debug: if debug:
print('DEBUG: No ' + postToBox + ' was found for ' + handle) print('DEBUG: No ' + postToBox + ' was found for ' + handle)
@ -2079,7 +2091,8 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
displayName) = getPersonBox(baseDir, session, wfRequest, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
nickname, domain, postToBox) nickname, domain, postToBox,
30873)
print("inboxUrl: " + str(inboxUrl)) print("inboxUrl: " + str(inboxUrl))
print("toPersonId: " + str(toPersonId)) print("toPersonId: " + str(toPersonId))
@ -2342,14 +2355,40 @@ def sendToNamedAddresses(session, baseDir: str,
def hasSharedInbox(session, httpPrefix: str, domain: str) -> bool: def hasSharedInbox(session, httpPrefix: str, domain: str) -> bool:
"""Returns true if the given domain has a shared inbox """Returns true if the given domain has a shared inbox
This tries the new and the old way of webfingering the shared inbox
""" """
wfRequest = webfingerHandle(session, domain + '@' + domain, tryHandles = [
httpPrefix, {}, domain + '@' + domain,
None, __version__) 'inbox@' + domain
if wfRequest: ]
if isinstance(wfRequest, dict): for handle in tryHandles:
if not wfRequest.get('errors'): wfRequest = webfingerHandle(session, handle,
return True httpPrefix, {},
None, __version__)
if wfRequest:
if isinstance(wfRequest, dict):
if not wfRequest.get('errors'):
return True
return False
def sendingProfileUpdate(postJsonObject: {}) -> bool:
"""Returns true if the given json is a profile update
"""
if postJsonObject['type'] != 'Update':
return False
if not postJsonObject.get('object'):
return False
if not isinstance(postJsonObject['object'], dict):
return False
if not postJsonObject['object'].get('type'):
return False
activityType = postJsonObject['object']['type']
if activityType == 'Person' or \
activityType == 'Application' or \
activityType == 'Group' or \
activityType == 'Service':
return True
return False return False
@ -2388,24 +2427,35 @@ def sendToFollowers(session, baseDir: str,
clientToServer = False clientToServer = False
# for each instance # for each instance
sendingStartTime = datetime.datetime.utcnow()
print('Sending post to followers begins ' +
sendingStartTime.strftime("%Y-%m-%dT%H:%M:%SZ"))
sendingCtr = 0
for followerDomain, followerHandles in grouped.items(): for followerDomain, followerHandles in grouped.items():
print('Sending post to followers progress ' +
str(int(sendingCtr * 100 / len(grouped.items()))) + '% ' +
followerDomain)
sendingCtr += 1
if debug: if debug:
print('DEBUG: follower handles for ' + followerDomain)
pprint(followerHandles) pprint(followerHandles)
# check that the follower's domain is active # check that the follower's domain is active
followerDomainUrl = httpPrefix + '://' + followerDomain followerDomainUrl = httpPrefix + '://' + followerDomain
if not siteIsActive(followerDomainUrl): if not siteIsActive(followerDomainUrl):
print('Domain is inactive: ' + followerDomainUrl) print('Sending post to followers domain is inactive: ' +
followerDomainUrl)
continue continue
print('Domain is active: ' + followerDomainUrl) print('Sending post to followers domain is active: ' +
followerDomainUrl)
withSharedInbox = hasSharedInbox(session, httpPrefix, followerDomain) withSharedInbox = hasSharedInbox(session, httpPrefix, followerDomain)
if debug: if debug:
if withSharedInbox: if withSharedInbox:
print(followerDomain + ' has shared inbox') print(followerDomain + ' has shared inbox')
else: if not withSharedInbox:
print(followerDomain + ' does not have a shared inbox') print('Sending post to followers, ' + followerDomain +
' does not have a shared inbox')
toPort = port toPort = port
index = 0 index = 0
@ -2438,22 +2488,14 @@ def sendToFollowers(session, baseDir: str,
toNickname = 'inbox' toNickname = 'inbox'
if toNickname != 'inbox' and postJsonObject.get('type'): if toNickname != 'inbox' and postJsonObject.get('type'):
if postJsonObject['type'] == 'Update': if sendingProfileUpdate(postJsonObject):
if postJsonObject.get('object'): print('Sending post to followers ' +
if isinstance(postJsonObject['object'], dict): 'shared inbox of ' + toDomain)
if postJsonObject['object'].get('type'): toNickname = 'inbox'
typ = postJsonObject['object']['type']
if typ == 'Person' or \
typ == 'Application' or \
typ == 'Group' or \
typ == 'Service':
print('Sending profile update to ' +
'shared inbox of ' + toDomain)
toNickname = 'inbox'
if debug: print('Sending post to followers from ' +
print('DEBUG: Sending from ' + nickname + '@' + domain + nickname + '@' + domain +
' to ' + toNickname + '@' + toDomain) ' to ' + toNickname + '@' + toDomain)
sendSignedJson(postJsonObject, session, baseDir, sendSignedJson(postJsonObject, session, baseDir,
nickname, fromDomain, port, nickname, fromDomain, port,
@ -2465,19 +2507,17 @@ def sendToFollowers(session, baseDir: str,
else: else:
# send to individual followers without using a shared inbox # send to individual followers without using a shared inbox
for handle in followerHandles: for handle in followerHandles:
if debug: print('Sending post to followers ' + handle)
print('DEBUG: Sending to ' + handle)
toNickname = handle.split('@')[0] toNickname = handle.split('@')[0]
if debug: if postJsonObject['type'] != 'Update':
if postJsonObject['type'] != 'Update': print('Sending post to followers from ' +
print('DEBUG: Sending from ' + nickname + '@' + domain + ' to ' +
nickname + '@' + domain + ' to ' + toNickname + '@' + toDomain)
toNickname + '@' + toDomain) else:
else: print('Sending post to followers profile update from ' +
print('DEBUG: Sending profile update from ' + nickname + '@' + domain + ' to ' +
nickname + '@' + domain + ' to ' + toNickname + '@' + toDomain)
toNickname + '@' + toDomain)
sendSignedJson(postJsonObject, session, baseDir, sendSignedJson(postJsonObject, session, baseDir,
nickname, fromDomain, port, nickname, fromDomain, port,
@ -2492,6 +2532,10 @@ def sendToFollowers(session, baseDir: str,
if debug: if debug:
print('DEBUG: End of sendToFollowers') print('DEBUG: End of sendToFollowers')
sendingEndTime = datetime.datetime.utcnow()
sendingMins = int((sendingEndTime - sendingStartTime).total_seconds() / 60)
print('Sending post to followers ends ' + str(sendingMins) + ' mins')
def sendToFollowersThread(session, baseDir: str, def sendToFollowersThread(session, baseDir: str,
nickname: str, nickname: str,
@ -3357,7 +3401,8 @@ def getPublicPostsOfPerson(baseDir: str, nickname: str, domain: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
nickname, domain, 'outbox') nickname, domain, 'outbox',
62524)
maxMentions = 10 maxMentions = 10
maxEmoji = 10 maxEmoji = 10
maxAttachments = 5 maxAttachments = 5
@ -3398,7 +3443,8 @@ def getPublicPostDomains(session, baseDir: str, nickname: str, domain: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
nickname, domain, 'outbox') nickname, domain, 'outbox',
92522)
maxMentions = 99 maxMentions = 99
maxEmoji = 99 maxEmoji = 99
maxAttachments = 5 maxAttachments = 5
@ -3441,7 +3487,8 @@ def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
nickname, domain, 'outbox') nickname, domain, 'outbox',
13863)
maxMentions = 99 maxMentions = 99
maxEmoji = 99 maxEmoji = 99
maxAttachments = 5 maxAttachments = 5
@ -3956,7 +4003,7 @@ def sendBlockViaServer(baseDir: str, session,
displayName) = getPersonBox(baseDir, session, wfRequest, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, fromNickname, projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox, 72652)
if not inboxUrl: if not inboxUrl:
if debug: if debug:
@ -4038,7 +4085,7 @@ def sendUndoBlockViaServer(baseDir: str, session,
fromPersonId, sharedInbox, avatarUrl, fromPersonId, sharedInbox, avatarUrl,
displayName) = getPersonBox(baseDir, session, wfRequest, personCache, displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname, projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox, 53892)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -316,7 +316,8 @@ def sendRoleViaServer(baseDir: str, session,
wfRequest, personCache, wfRequest, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
delegatorNickname, delegatorNickname,
delegatorDomain, postToBox) delegatorDomain, postToBox,
765672)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -158,7 +158,7 @@ def runPostScheduleWatchdog(projectVersion: str, httpd) -> None:
httpd.thrPostSchedule.start() httpd.thrPostSchedule.start()
while True: while True:
time.sleep(20) time.sleep(20)
if not httpd.thrPostSchedule.isAlive(): if not httpd.thrPostSchedule.is_alive():
httpd.thrPostSchedule.kill() httpd.thrPostSchedule.kill()
httpd.thrPostSchedule = \ httpd.thrPostSchedule = \
postScheduleOriginal.clone(runPostSchedule) postScheduleOriginal.clone(runPostSchedule)

View File

@ -0,0 +1,2 @@
#!/bin/bash
journalctl -u epicyon -r | grep "Sending profile update to\|a shared inbox"

2
scripts/sending 100755
View File

@ -0,0 +1,2 @@
#!/bin/bash
journalctl -u epicyon -r | grep "Sending post to followers"

View File

@ -58,7 +58,7 @@ def getJson(session, url: str, headers: {}, params: {},
domain='testdomain') -> {}: domain='testdomain') -> {}:
if not isinstance(url, str): if not isinstance(url, str):
print('url: ' + str(url)) print('url: ' + str(url))
print('ERROR: getJson url should be a string') print('ERROR: getJson failed, url should be a string')
return None return None
sessionParams = {} sessionParams = {}
sessionHeaders = {} sessionHeaders = {}
@ -71,23 +71,23 @@ def getJson(session, url: str, headers: {}, params: {},
sessionHeaders['User-Agent'] += \ sessionHeaders['User-Agent'] += \
'; +' + httpPrefix + '://' + domain + '/' '; +' + httpPrefix + '://' + domain + '/'
if not session: if not session:
print('WARN: no session specified for getJson') print('WARN: getJson failed, no session specified for getJson')
try: try:
result = session.get(url, headers=sessionHeaders, params=sessionParams) result = session.get(url, headers=sessionHeaders, params=sessionParams)
return result.json() return result.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print('ERROR: getJson failed\nurl: ' + str(url) + '\n' + print('ERROR: getJson failed\nurl: ' + str(url) + ' ' +
'headers: ' + str(sessionHeaders) + '\n' + 'headers: ' + str(sessionHeaders) + ' ' +
'params: ' + str(sessionParams) + '\n') 'params: ' + str(sessionParams))
print(e) print(e)
except ValueError as e: except ValueError as e:
print('ERROR: getJson failed\nurl: ' + str(url) + '\n' + print('ERROR: getJson failed\nurl: ' + str(url) + ' ' +
'headers: ' + str(sessionHeaders) + '\n' + 'headers: ' + str(sessionHeaders) + ' ' +
'params: ' + str(sessionParams) + '\n') 'params: ' + str(sessionParams) + ' ')
print(e) print(e)
except SocketError as e: except SocketError as e:
if e.errno == errno.ECONNRESET: if e.errno == errno.ECONNRESET:
print('WARN: connection was reset during getJson') print('WARN: getJson failed, connection was reset during getJson')
print(e) print(e)
return None return None

View File

@ -376,7 +376,8 @@ def sendShareViaServer(baseDir, session,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, projectVersion, personCache, projectVersion,
httpPrefix, fromNickname, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox,
83653)
if not inboxUrl: if not inboxUrl:
if debug: if debug:
@ -474,7 +475,8 @@ def sendUndoShareViaServer(baseDir: str, session,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, projectVersion, personCache, projectVersion,
httpPrefix, fromNickname, httpPrefix, fromNickname,
fromDomain, postToBox) fromDomain, postToBox,
12663)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -152,7 +152,7 @@ def sendSkillViaServer(baseDir: str, session, nickname: str, password: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, projectVersion, personCache, projectVersion,
httpPrefix, nickname, domain, httpPrefix, nickname, domain,
postToBox) postToBox, 86725)
if not inboxUrl: if not inboxUrl:
if debug: if debug:

View File

@ -65,7 +65,8 @@ def instancesGraph(baseDir: str, handles: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
nickname, domain, 'outbox') nickname, domain, 'outbox',
27261)
postDomains = \ postDomains = \
getPostDomains(session, personUrl, 64, maxMentions, maxEmoji, getPostDomains(session, personUrl, 64, maxMentions, maxEmoji,
maxAttachments, federationList, maxAttachments, federationList,

View File

@ -236,11 +236,11 @@ def testThreads():
args=('test',), args=('test',),
daemon=True) daemon=True)
thr.start() thr.start()
assert thr.isAlive() is True assert thr.is_alive() is True
time.sleep(1) time.sleep(1)
thr.kill() thr.kill()
thr.join() thr.join()
assert thr.isAlive() is False assert thr.is_alive() is False
def createServerAlice(path: str, domain: str, port: int, def createServerAlice(path: str, domain: str, port: int,
@ -296,8 +296,10 @@ def createServerAlice(path: str, domain: str, port: int,
allowLocalNetworkAccess = True allowLocalNetworkAccess = True
maxNewswirePosts = 20 maxNewswirePosts = 20
dormantMonths = 3 dormantMonths = 3
sendThreadsTimeoutMins = 30
print('Server running: Alice') print('Server running: Alice')
runDaemon(dormantMonths, maxNewswirePosts, runDaemon(sendThreadsTimeoutMins,
dormantMonths, maxNewswirePosts,
allowLocalNetworkAccess, allowLocalNetworkAccess,
2048, False, True, False, False, True, 10, False, 2048, False, True, False, False, True, 10, False,
0, 100, 1024, 5, False, 0, 100, 1024, 5, False,
@ -366,8 +368,10 @@ def createServerBob(path: str, domain: str, port: int,
allowLocalNetworkAccess = True allowLocalNetworkAccess = True
maxNewswirePosts = 20 maxNewswirePosts = 20
dormantMonths = 3 dormantMonths = 3
sendThreadsTimeoutMins = 30
print('Server running: Bob') print('Server running: Bob')
runDaemon(dormantMonths, maxNewswirePosts, runDaemon(sendThreadsTimeoutMins,
dormantMonths, maxNewswirePosts,
allowLocalNetworkAccess, allowLocalNetworkAccess,
2048, False, True, False, False, True, 10, False, 2048, False, True, False, False, True, 10, False,
0, 100, 1024, 5, False, 0, 0, 100, 1024, 5, False, 0,
@ -410,8 +414,10 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
allowLocalNetworkAccess = True allowLocalNetworkAccess = True
maxNewswirePosts = 20 maxNewswirePosts = 20
dormantMonths = 3 dormantMonths = 3
sendThreadsTimeoutMins = 30
print('Server running: Eve') print('Server running: Eve')
runDaemon(dormantMonths, maxNewswirePosts, runDaemon(sendThreadsTimeoutMins,
dormantMonths, maxNewswirePosts,
allowLocalNetworkAccess, allowLocalNetworkAccess,
2048, False, True, False, False, True, 10, False, 2048, False, True, False, False, True, 10, False,
0, 100, 1024, 5, False, 0, 0, 100, 1024, 5, False, 0,
@ -456,7 +462,7 @@ def testPostMessageBetweenServers():
global thrAlice global thrAlice
if thrAlice: if thrAlice:
while thrAlice.isAlive(): while thrAlice.is_alive():
thrAlice.stop() thrAlice.stop()
time.sleep(1) time.sleep(1)
thrAlice.kill() thrAlice.kill()
@ -470,7 +476,7 @@ def testPostMessageBetweenServers():
global thrBob global thrBob
if thrBob: if thrBob:
while thrBob.isAlive(): while thrBob.is_alive():
thrBob.stop() thrBob.stop()
time.sleep(1) time.sleep(1)
thrBob.kill() thrBob.kill()
@ -484,8 +490,8 @@ def testPostMessageBetweenServers():
thrAlice.start() thrAlice.start()
thrBob.start() thrBob.start()
assert thrAlice.isAlive() is True assert thrAlice.is_alive() is True
assert thrBob.isAlive() is True assert thrBob.is_alive() is True
# wait for both servers to be running # wait for both servers to be running
while not (testServerAliceRunning and testServerBobRunning): while not (testServerAliceRunning and testServerBobRunning):
@ -684,11 +690,11 @@ def testPostMessageBetweenServers():
# stop the servers # stop the servers
thrAlice.kill() thrAlice.kill()
thrAlice.join() thrAlice.join()
assert thrAlice.isAlive() is False assert thrAlice.is_alive() is False
thrBob.kill() thrBob.kill()
thrBob.join() thrBob.join()
assert thrBob.isAlive() is False assert thrBob.is_alive() is False
os.chdir(baseDir) os.chdir(baseDir)
shutil.rmtree(aliceDir) shutil.rmtree(aliceDir)
@ -727,7 +733,7 @@ def testFollowBetweenServers():
global thrAlice global thrAlice
if thrAlice: if thrAlice:
while thrAlice.isAlive(): while thrAlice.is_alive():
thrAlice.stop() thrAlice.stop()
time.sleep(1) time.sleep(1)
thrAlice.kill() thrAlice.kill()
@ -741,7 +747,7 @@ def testFollowBetweenServers():
global thrBob global thrBob
if thrBob: if thrBob:
while thrBob.isAlive(): while thrBob.is_alive():
thrBob.stop() thrBob.stop()
time.sleep(1) time.sleep(1)
thrBob.kill() thrBob.kill()
@ -755,8 +761,8 @@ def testFollowBetweenServers():
thrAlice.start() thrAlice.start()
thrBob.start() thrBob.start()
assert thrAlice.isAlive() is True assert thrAlice.is_alive() is True
assert thrBob.isAlive() is True assert thrBob.is_alive() is True
# wait for all servers to be running # wait for all servers to be running
ctr = 0 ctr = 0
@ -856,11 +862,11 @@ def testFollowBetweenServers():
# stop the servers # stop the servers
thrAlice.kill() thrAlice.kill()
thrAlice.join() thrAlice.join()
assert thrAlice.isAlive() is False assert thrAlice.is_alive() is False
thrBob.kill() thrBob.kill()
thrBob.join() thrBob.join()
assert thrBob.isAlive() is False assert thrBob.is_alive() is False
# queue item removed # queue item removed
time.sleep(4) time.sleep(4)
@ -1284,7 +1290,7 @@ def testClientToServer():
global thrAlice global thrAlice
if thrAlice: if thrAlice:
while thrAlice.isAlive(): while thrAlice.is_alive():
thrAlice.stop() thrAlice.stop()
time.sleep(1) time.sleep(1)
thrAlice.kill() thrAlice.kill()
@ -1298,7 +1304,7 @@ def testClientToServer():
global thrBob global thrBob
if thrBob: if thrBob:
while thrBob.isAlive(): while thrBob.is_alive():
thrBob.stop() thrBob.stop()
time.sleep(1) time.sleep(1)
thrBob.kill() thrBob.kill()
@ -1312,8 +1318,8 @@ def testClientToServer():
thrAlice.start() thrAlice.start()
thrBob.start() thrBob.start()
assert thrAlice.isAlive() is True assert thrAlice.is_alive() is True
assert thrBob.isAlive() is True assert thrBob.is_alive() is True
# wait for both servers to be running # wait for both servers to be running
ctr = 0 ctr = 0
@ -1608,11 +1614,11 @@ def testClientToServer():
# stop the servers # stop the servers
thrAlice.kill() thrAlice.kill()
thrAlice.join() thrAlice.join()
assert thrAlice.isAlive() is False assert thrAlice.is_alive() is False
thrBob.kill() thrBob.kill()
thrBob.join() thrBob.join()
assert thrBob.isAlive() is False assert thrBob.is_alive() is False
os.chdir(baseDir) os.chdir(baseDir)
# shutil.rmtree(aliceDir) # shutil.rmtree(aliceDir)

View File

@ -1,5 +1,6 @@
{ {
"today-circle": "#03a494", "today-circle": "#03a494",
"options-main-link-color-hover": "white",
"main-link-color-hover": "blue", "main-link-color-hover": "blue",
"font-size-newswire-mobile": "32px", "font-size-newswire-mobile": "32px",
"newswire-date-color": "#00a594", "newswire-date-color": "#00a594",
@ -58,8 +59,10 @@
"border-width": "1px", "border-width": "1px",
"border-width-header": "1px", "border-width-header": "1px",
"main-link-color": "darkblue", "main-link-color": "darkblue",
"options-main-link-color": "lightgrey",
"title-color": "#2a2c37", "title-color": "#2a2c37",
"main-visited-color": "#232c37", "main-visited-color": "#232c37",
"options-main-visited-color": "#ccc",
"text-entry-foreground": "#111", "text-entry-foreground": "#111",
"text-entry-background": "white", "text-entry-background": "white",
"font-color-header": "black", "font-color-header": "black",

View File

@ -24,8 +24,11 @@
"border-color": "#035103", "border-color": "#035103",
"main-link-color": "#2fff2f", "main-link-color": "#2fff2f",
"main-link-color-hover": "#afff2f", "main-link-color-hover": "#afff2f",
"options-main-link-color": "#2fff2f",
"options-main-link-color-hover": "#afff2f",
"title-color": "#2fff2f", "title-color": "#2fff2f",
"main-visited-color": "#3c8234", "main-visited-color": "#3c8234",
"options-main-visited-color": "#3c8234",
"button-selected": "#063200", "button-selected": "#063200",
"button-background-hover": "#a62200", "button-background-hover": "#a62200",
"button-text-hover": "#00ff00", "button-text-hover": "#00ff00",

View File

@ -29,8 +29,11 @@
"link-bg-color": "#383335", "link-bg-color": "#383335",
"main-link-color": "white", "main-link-color": "white",
"main-link-color-hover": "#ddd", "main-link-color-hover": "#ddd",
"options-main-link-color": "white",
"options-main-link-color-hover": "#ddd",
"title-color": "white", "title-color": "white",
"main-visited-color": "#e1c4bc", "main-visited-color": "#e1c4bc",
"options-main-visited-color": "#e1c4bc",
"main-fg-color": "white", "main-fg-color": "white",
"options-fg-color": "white", "options-fg-color": "white",
"column-left-fg-color": "white", "column-left-fg-color": "white",

View File

@ -40,7 +40,10 @@
"link-bg-color": "black", "link-bg-color": "black",
"main-link-color": "#ff9900", "main-link-color": "#ff9900",
"main-link-color-hover": "#d09338", "main-link-color-hover": "#d09338",
"options-main-link-color": "#ff9900",
"options-main-link-color-hover": "#d09338",
"main-visited-color": "#ffb900", "main-visited-color": "#ffb900",
"options-main-visited-color": "#ffb900",
"main-fg-color": "white", "main-fg-color": "white",
"login-fg-color": "white", "login-fg-color": "white",
"options-fg-color": "white", "options-fg-color": "white",

View File

@ -105,8 +105,11 @@
"border-color": "#c0cdd9", "border-color": "#c0cdd9",
"main-link-color": "#25408f", "main-link-color": "#25408f",
"main-link-color-hover": "#10408f", "main-link-color-hover": "#10408f",
"options-main-link-color": "#25408f",
"options-main-link-color-hover": "#10408f",
"title-color": "#2a2c37", "title-color": "#2a2c37",
"main-visited-color": "#25408f", "main-visited-color": "#25408f",
"options-main-visited-color": "#25408f",
"text-entry-foreground": "#111", "text-entry-foreground": "#111",
"text-entry-background": "white", "text-entry-background": "white",
"font-color-header": "black", "font-color-header": "black",

View File

@ -30,8 +30,11 @@
"border-width-header": "5px", "border-width-header": "5px",
"main-link-color": "#9fb42b", "main-link-color": "#9fb42b",
"main-link-color-hover": "#cfb42b", "main-link-color-hover": "#cfb42b",
"options-main-link-color": "#9fb42b",
"options-main-link-color-hover": "#cfb42b",
"title-color": "#9fb42b", "title-color": "#9fb42b",
"main-visited-color": "#9fb42b", "main-visited-color": "#9fb42b",
"options-main-visited-color": "#9fb42b",
"button-selected": "black", "button-selected": "black",
"button-highlighted": "green", "button-highlighted": "green",
"button-background-hover": "#a3390d", "button-background-hover": "#a3390d",

View File

@ -39,8 +39,11 @@
"border-color": "#c0cdd9", "border-color": "#c0cdd9",
"main-link-color": "#2a2c37", "main-link-color": "#2a2c37",
"main-link-color-hover": "#aa2c37", "main-link-color-hover": "#aa2c37",
"options-main-link-color": "#2a2c37",
"options-main-link-color-hover": "#aa2c37",
"title-color": "#2a2c37", "title-color": "#2a2c37",
"main-visited-color": "#232c37", "main-visited-color": "#232c37",
"options-main-visited-color": "#232c37",
"text-entry-foreground": "#111", "text-entry-foreground": "#111",
"text-entry-background": "white", "text-entry-background": "white",
"font-color-header": "black", "font-color-header": "black",

View File

@ -5,6 +5,7 @@
"rss-icon-at-top": "True", "rss-icon-at-top": "True",
"publish-button-at-top": "False", "publish-button-at-top": "False",
"main-visited-color": "#0481f5", "main-visited-color": "#0481f5",
"options-main-visited-color": "#0481f5",
"post-separator-margin-top": "9%", "post-separator-margin-top": "9%",
"post-separator-margin-bottom": "9%", "post-separator-margin-bottom": "9%",
"post-separator-width": "80%", "post-separator-width": "80%",
@ -30,6 +31,8 @@
"link-bg-color": "#0f0d10", "link-bg-color": "#0f0d10",
"main-link-color": "#6481f5", "main-link-color": "#6481f5",
"main-link-color-hover": "#d09338", "main-link-color-hover": "#d09338",
"options-main-link-color": "#6481f5",
"options-main-link-color-hover": "#d09338",
"main-fg-color": "#0481f5", "main-fg-color": "#0481f5",
"login-fg-color": "#0481f5", "login-fg-color": "#0481f5",
"options-fg-color": "#0481f5", "options-fg-color": "#0481f5",

View File

@ -30,8 +30,11 @@
"border-color": "#3f2145", "border-color": "#3f2145",
"main-link-color": "#ff42a0", "main-link-color": "#ff42a0",
"main-link-color-hover": "white", "main-link-color-hover": "white",
"options-main-link-color": "#ff42a0",
"options-main-link-color-hover": "white",
"title-color": "white", "title-color": "white",
"main-visited-color": "#f93bb0", "main-visited-color": "#f93bb0",
"options-main-visited-color": "#f93bb0",
"button-selected": "#c042a0", "button-selected": "#c042a0",
"button-background-hover": "#af42a0", "button-background-hover": "#af42a0",
"button-text-hover": "#f98bb0", "button-text-hover": "#f98bb0",

View File

@ -21,6 +21,7 @@
"rss-icon-at-top": "True", "rss-icon-at-top": "True",
"publish-button-at-top": "False", "publish-button-at-top": "False",
"main-visited-color": "#46eed5", "main-visited-color": "#46eed5",
"options-main-visited-color": "#46eed5",
"post-separator-margin-top": "9%", "post-separator-margin-top": "9%",
"post-separator-margin-bottom": "9%", "post-separator-margin-bottom": "9%",
"post-separator-width": "80%", "post-separator-width": "80%",
@ -51,6 +52,8 @@
"link-bg-color": "#0f0d10", "link-bg-color": "#0f0d10",
"main-link-color": "#05b9ec", "main-link-color": "#05b9ec",
"main-link-color-hover": "#46eed5", "main-link-color-hover": "#46eed5",
"options-main-link-color": "#05b9ec",
"options-main-link-color-hover": "#46eed5",
"main-fg-color": "white", "main-fg-color": "white",
"login-fg-color": "white", "login-fg-color": "white",
"options-fg-color": "white", "options-fg-color": "white",

View File

@ -47,8 +47,11 @@
"border-color": "#c0cdd9", "border-color": "#c0cdd9",
"main-link-color": "#2a2c37", "main-link-color": "#2a2c37",
"main-link-color-hover": "#aa2c37", "main-link-color-hover": "#aa2c37",
"options-main-link-color": "#2a2c37",
"options-main-link-color-hover": "#aa2c37",
"title-color": "#2a2c37", "title-color": "#2a2c37",
"main-visited-color": "#232c37", "main-visited-color": "#232c37",
"options-main-visited-color": "#232c37",
"text-entry-foreground": "#111", "text-entry-foreground": "#111",
"text-entry-background": "white", "text-entry-background": "white",
"font-color-header": "black", "font-color-header": "black",

View File

@ -28,8 +28,11 @@
"link-bg-color": "#0f0d10", "link-bg-color": "#0f0d10",
"main-link-color": "#ffc4bc", "main-link-color": "#ffc4bc",
"main-link-color-hover": "white", "main-link-color-hover": "white",
"options-main-link-color": "#ffc4bc",
"options-main-link-color-hover": "white",
"title-color": "#ffc4bc", "title-color": "#ffc4bc",
"main-visited-color": "#e1c4bc", "main-visited-color": "#e1c4bc",
"options-main-visited-color": "#e1c4bc",
"main-fg-color": "#ffc4bc", "main-fg-color": "#ffc4bc",
"login-fg-color": "#ffc4bc", "login-fg-color": "#ffc4bc",
"options-fg-color": "#ffc4bc", "options-fg-color": "#ffc4bc",

View File

@ -24,8 +24,11 @@
"border-width-header": "7px", "border-width-header": "7px",
"main-link-color": "#dddddd", "main-link-color": "#dddddd",
"main-link-color-hover": "white", "main-link-color-hover": "white",
"options-main-link-color": "#dddddd",
"options-main-link-color-hover": "white",
"title-color": "#dddddd", "title-color": "#dddddd",
"main-visited-color": "#dddddd", "main-visited-color": "#dddddd",
"options-main-visited-color": "#dddddd",
"button-background-hover": "#a63b35", "button-background-hover": "#a63b35",
"publish-button-background": "#463b35", "publish-button-background": "#463b35",
"button-background": "#463b35", "button-background": "#463b35",

View File

@ -69,12 +69,14 @@ class threadWithTrace(threading.Thread):
daemon=True) daemon=True)
def removeDormantThreads(baseDir: str, threadsList: [], debug: bool) -> None: def removeDormantThreads(baseDir: str, threadsList: [], debug: bool,
timeoutMins=30) -> None:
"""Removes threads whose execution has completed """Removes threads whose execution has completed
""" """
if len(threadsList) == 0: if len(threadsList) == 0:
return return
timeoutSecs = int(timeoutMins * 60)
dormantThreads = [] dormantThreads = []
currTime = datetime.datetime.utcnow() currTime = datetime.datetime.utcnow()
changed = False changed = False
@ -92,13 +94,13 @@ def removeDormantThreads(baseDir: str, threadsList: [], debug: bool) -> None:
'thread is not alive ten seconds after start') 'thread is not alive ten seconds after start')
removeThread = True removeThread = True
# timeout for started threads # timeout for started threads
if (currTime - th.startTime).total_seconds() > 600: if (currTime - th.startTime).total_seconds() > timeoutSecs:
if debug: if debug:
print('DEBUG: started thread timed out') print('DEBUG: started thread timed out')
removeThread = True removeThread = True
else: else:
# timeout for threads which havn't been started # timeout for threads which havn't been started
if (currTime - th.startTime).total_seconds() > 600: if (currTime - th.startTime).total_seconds() > timeoutSecs:
if debug: if debug:
print('DEBUG: unstarted thread timed out') print('DEBUG: unstarted thread timed out')
removeThread = True removeThread = True

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "اسأل عن عنصر مشترك.", "Ask about a shared item.": "اسأل عن عنصر مشترك.",
"Account Information": "معلومات الحساب", "Account Information": "معلومات الحساب",
"This account interacts with the following instances": "يتفاعل هذا الحساب مع الحالات التالية", "This account interacts with the following instances": "يتفاعل هذا الحساب مع الحالات التالية",
"News posts are moderated": "المشاركات الإخبارية خاضعة للإشراف" "News posts are moderated": "المشاركات الإخبارية خاضعة للإشراف",
"Filter": "منقي",
"Filter out words": "تصفية الكلمات",
"Unfilter": "غير مرشح",
"Unfilter words": "الكلمات غير المصفاة",
"Show Accounts": "إظهار الحسابات"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Pregunteu sobre un element compartit.", "Ask about a shared item.": "Pregunteu sobre un element compartit.",
"Account Information": "Informació del compte", "Account Information": "Informació del compte",
"This account interacts with the following instances": "Aquest compte interactua amb les instàncies següents", "This account interacts with the following instances": "Aquest compte interactua amb les instàncies següents",
"News posts are moderated": "Les publicacions de notícies es moderen" "News posts are moderated": "Les publicacions de notícies es moderen",
"Filter": "Filtre",
"Filter out words": "Filtra les paraules",
"Unfilter": "Sense filtre",
"Unfilter words": "Paraules sense filtre",
"Show Accounts": "Mostra comptes"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Gofynnwch am eitem a rennir.", "Ask about a shared item.": "Gofynnwch am eitem a rennir.",
"Account Information": "Gwybodaeth Gyfrif", "Account Information": "Gwybodaeth Gyfrif",
"This account interacts with the following instances": "Mae'r cyfrif hwn yn rhyngweithio â'r achosion canlynol", "This account interacts with the following instances": "Mae'r cyfrif hwn yn rhyngweithio â'r achosion canlynol",
"News posts are moderated": "Mae swyddi newyddion yn cael eu cymedroli" "News posts are moderated": "Mae swyddi newyddion yn cael eu cymedroli",
"Filter": "Hidlo",
"Filter out words": "Hidlo geiriau",
"Unfilter": "Di-hid",
"Unfilter words": "Geiriau di-hid",
"Show Accounts": "Dangos Cyfrifon"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Fragen Sie nach einem gemeinsamen Artikel.", "Ask about a shared item.": "Fragen Sie nach einem gemeinsamen Artikel.",
"Account Information": "Kontoinformationen", "Account Information": "Kontoinformationen",
"This account interacts with the following instances": "Dieses Konto interagiert mit den folgenden Instanzen", "This account interacts with the following instances": "Dieses Konto interagiert mit den folgenden Instanzen",
"News posts are moderated": "Nachrichtenbeiträge werden moderiert" "News posts are moderated": "Nachrichtenbeiträge werden moderiert",
"Filter": "Filter",
"Filter out words": "Wörter herausfiltern",
"Unfilter": "Filter entfernen",
"Unfilter words": "Wörter herausfiltern",
"Show Accounts": "Konten anzeigen"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Ask about a shared item.", "Ask about a shared item.": "Ask about a shared item.",
"Account Information": "Account Information", "Account Information": "Account Information",
"This account interacts with the following instances": "This account interacts with the following instances", "This account interacts with the following instances": "This account interacts with the following instances",
"News posts are moderated": "News posts are moderated" "News posts are moderated": "News posts are moderated",
"Filter": "Filter",
"Filter out words": "Filter out words",
"Unfilter": "Unfilter",
"Unfilter words": "Unfilter words",
"Show Accounts": "Show Accounts"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Pregunte por un elemento compartido.", "Ask about a shared item.": "Pregunte por un elemento compartido.",
"Account Information": "Información de la cuenta", "Account Information": "Información de la cuenta",
"This account interacts with the following instances": "Esta cuenta interactúa con las siguientes instancias", "This account interacts with the following instances": "Esta cuenta interactúa con las siguientes instancias",
"News posts are moderated": "Las publicaciones de noticias están moderadas" "News posts are moderated": "Las publicaciones de noticias están moderadas",
"Filter": "Filtrar",
"Filter out words": "Filtrar palabras",
"Unfilter": "Unfilter",
"Unfilter words": "Palabras sin filtrar",
"Show Accounts": "Mostrar cuentas"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Renseignez-vous sur un élément partagé.", "Ask about a shared item.": "Renseignez-vous sur un élément partagé.",
"Account Information": "Information sur le compte", "Account Information": "Information sur le compte",
"This account interacts with the following instances": "Ce compte interagit avec les instances suivantes", "This account interacts with the following instances": "Ce compte interagit avec les instances suivantes",
"News posts are moderated": "Les articles d'actualité sont modérés" "News posts are moderated": "Les articles d'actualité sont modérés",
"Filter": "Filtre",
"Filter out words": "Filtrer les mots",
"Unfilter": "Non filtrer",
"Unfilter words": "Mots non filtrés",
"Show Accounts": "Afficher les comptes"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Fiafraigh faoi earra roinnte.", "Ask about a shared item.": "Fiafraigh faoi earra roinnte.",
"Account Information": "Faisnéis Chuntais", "Account Information": "Faisnéis Chuntais",
"This account interacts with the following instances": "Idirghníomhaíonn an cuntas seo leis na cásanna seo a leanas", "This account interacts with the following instances": "Idirghníomhaíonn an cuntas seo leis na cásanna seo a leanas",
"News posts are moderated": "Déantar poist nuachta a mhodhnú" "News posts are moderated": "Déantar poist nuachta a mhodhnú",
"Filter": "Scagaire",
"Filter out words": "Scag focail amach",
"Unfilter": "Neamhleithleach",
"Unfilter words": "Focail neamhleithleacha",
"Show Accounts": "Taispeáin Cuntais"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "एक साझा आइटम के बारे में पूछें।", "Ask about a shared item.": "एक साझा आइटम के बारे में पूछें।",
"Account Information": "खाते की जानकारी", "Account Information": "खाते की जानकारी",
"This account interacts with the following instances": "यह खाता निम्नलिखित उदाहरणों के साथ सहभागिता करता है", "This account interacts with the following instances": "यह खाता निम्नलिखित उदाहरणों के साथ सहभागिता करता है",
"News posts are moderated": "समाचार पोस्ट संचालित होते हैं" "News posts are moderated": "समाचार पोस्ट संचालित होते हैं",
"Filter": "फ़िल्टर",
"Filter out words": "शब्दों को फ़िल्टर करें",
"Unfilter": "Unfilter",
"Unfilter words": "अनफ़िल्टर शब्द",
"Show Accounts": "खाते दिखाएं"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Chiedi informazioni su un elemento condiviso.", "Ask about a shared item.": "Chiedi informazioni su un elemento condiviso.",
"Account Information": "Informazioni account", "Account Information": "Informazioni account",
"This account interacts with the following instances": "Questo account interagisce con le seguenti istanze", "This account interacts with the following instances": "Questo account interagisce con le seguenti istanze",
"News posts are moderated": "I post di notizie sono moderati" "News posts are moderated": "I post di notizie sono moderati",
"Filter": "Filtro",
"Filter out words": "Filtra le parole",
"Unfilter": "Unfilter",
"Unfilter words": "Parole non filtrate",
"Show Accounts": "Mostra account"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "共有アイテムについて質問します。", "Ask about a shared item.": "共有アイテムについて質問します。",
"Account Information": "口座情報", "Account Information": "口座情報",
"This account interacts with the following instances": "このアカウントは、次のインスタンスと相互作用します", "This account interacts with the following instances": "このアカウントは、次のインスタンスと相互作用します",
"News posts are moderated": "ニュース投稿はモデレートされます" "News posts are moderated": "ニュース投稿はモデレートされます",
"Filter": "フィルタ",
"Filter out words": "単語を除外する",
"Unfilter": "フィルタリング解除",
"Unfilter words": "単語のフィルタリングを解除する",
"Show Accounts": "アカウントを表示する"
} }

View File

@ -338,5 +338,10 @@
"Ask about a shared item.": "Ask about a shared item.", "Ask about a shared item.": "Ask about a shared item.",
"Account Information": "Account Information", "Account Information": "Account Information",
"This account interacts with the following instances": "This account interacts with the following instances", "This account interacts with the following instances": "This account interacts with the following instances",
"News posts are moderated": "News posts are moderated" "News posts are moderated": "News posts are moderated",
"Filter": "Filter",
"Filter out words": "Filter out words",
"Unfilter": "Unfilter",
"Unfilter words": "Unfilter words",
"Show Accounts": "Show Accounts"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Pergunte sobre um item compartilhado.", "Ask about a shared item.": "Pergunte sobre um item compartilhado.",
"Account Information": "Informação da conta", "Account Information": "Informação da conta",
"This account interacts with the following instances": "Esta conta interage com as seguintes instâncias", "This account interacts with the following instances": "Esta conta interage com as seguintes instâncias",
"News posts are moderated": "Postagens de notícias são moderadas" "News posts are moderated": "Postagens de notícias são moderadas",
"Filter": "Filtro",
"Filter out words": "Filtrar palavras",
"Unfilter": "Unfilter",
"Unfilter words": "Palavras sem filtro",
"Show Accounts": "Mostrar contas"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "Спросите об общем элементе.", "Ask about a shared item.": "Спросите об общем элементе.",
"Account Information": "Информация об аккаунте", "Account Information": "Информация об аккаунте",
"This account interacts with the following instances": "Этот аккаунт взаимодействует со следующими экземплярами", "This account interacts with the following instances": "Этот аккаунт взаимодействует со следующими экземплярами",
"News posts are moderated": "Сообщения новостей модерируются" "News posts are moderated": "Сообщения новостей модерируются",
"Filter": "Фильтр",
"Filter out words": "Отфильтровать слова",
"Unfilter": "Нефильтровать",
"Unfilter words": "Не фильтровать слова",
"Show Accounts": "Показать счета"
} }

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "询问共享项目。", "Ask about a shared item.": "询问共享项目。",
"Account Information": "帐户信息", "Account Information": "帐户信息",
"This account interacts with the following instances": "此帐户与以下实例进行交互", "This account interacts with the following instances": "此帐户与以下实例进行交互",
"News posts are moderated": "新闻发布被审核" "News posts are moderated": "新闻发布被审核",
"Filter": "过滤",
"Filter out words": "过滤掉单词",
"Unfilter": "取消过滤",
"Unfilter words": "未过滤字词",
"Show Accounts": "显示帐户"
} }

View File

@ -7,14 +7,12 @@ __email__ = "bob@freedombone.net"
__status__ = "Production" __status__ = "Production"
import os import os
from shutil import copyfile
from utils import getConfigParam from utils import getConfigParam
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import isEditor from utils import isEditor
from webapp_utils import sharesTimelineJson from webapp_utils import sharesTimelineJson
from webapp_utils import htmlPostSeparator from webapp_utils import htmlPostSeparator
from webapp_utils import getLeftImageFile from webapp_utils import getLeftImageFile
from webapp_utils import getImageFile
from webapp_utils import headerButtonsFrontScreen from webapp_utils import headerButtonsFrontScreen
from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter from webapp_utils import htmlFooter
@ -70,7 +68,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
editor: bool, editor: bool,
showBackButton: bool, timelinePath: str, showBackButton: bool, timelinePath: str,
rssIconAtTop: bool, showHeaderImage: bool, rssIconAtTop: bool, showHeaderImage: bool,
frontPage: bool) -> str: frontPage: bool, theme: str) -> str:
"""Returns html content for the left column """Returns html content for the left column
""" """
htmlStr = '' htmlStr = ''
@ -83,23 +81,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
editImageClass = '' editImageClass = ''
if showHeaderImage: if showHeaderImage:
leftImageFile, leftColumnImageFilename = \ leftImageFile, leftColumnImageFilename = \
getLeftImageFile(baseDir, nickname, domain) getLeftImageFile(baseDir, nickname, domain, theme)
if not os.path.isfile(leftColumnImageFilename):
theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default':
theme = ''
else:
theme = '_' + theme
themeLeftImageFile, themeLeftColumnImageFilename = \
getImageFile(baseDir, 'left_col_image', baseDir + '/img',
nickname, domain)
if os.path.isfile(themeLeftColumnImageFilename):
leftColumnImageFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + themeLeftImageFile
copyfile(themeLeftColumnImageFilename,
leftColumnImageFilename)
leftImageFile = themeLeftImageFile
# show the image at the top of the column # show the image at the top of the column
editImageClass = 'leftColEdit' editImageClass = 'leftColEdit'
@ -255,7 +237,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
timelinePath: str, authorized: bool, timelinePath: str, authorized: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
iconsAsButtons: bool, iconsAsButtons: bool,
defaultTimeline: str) -> str: defaultTimeline: str,
theme: str) -> str:
"""Show the left column links within mobile view """Show the left column links within mobile view
""" """
htmlStr = '' htmlStr = ''
@ -276,7 +259,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
domain = domain.split(':')[0] domain = domain.split(':')[0]
htmlStr = htmlHeaderWithExternalStyle(cssFilename) htmlStr = htmlHeaderWithExternalStyle(cssFilename)
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
htmlStr += \ htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
@ -293,7 +277,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
httpPrefix, translate, httpPrefix, translate,
editor, editor,
False, timelinePath, False, timelinePath,
rssIconAtTop, False, False) rssIconAtTop, False, False,
theme)
else: else:
if editor: if editor:
htmlStr += '<br><br><br>\n' htmlStr += '<br><br><br>\n'
@ -310,7 +295,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str, def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str, domain: str, port: int, httpPrefix: str,
defaultTimeline: str) -> str: defaultTimeline: str, theme: str) -> str:
"""Shows the edit links screen """Shows the edit links screen
""" """
if '/users/' not in path: if '/users/' not in path:
@ -331,7 +316,8 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
cssFilename = baseDir + '/links.css' cssFilename = baseDir + '/links.css'
# filename of the banner shown at the top # filename of the banner shown at the top
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
editLinksForm = htmlHeaderWithExternalStyle(cssFilename) editLinksForm = htmlHeaderWithExternalStyle(cssFilename)

View File

@ -8,18 +8,15 @@ __status__ = "Production"
import os import os
from datetime import datetime from datetime import datetime
from shutil import copyfile
from content import removeLongWords from content import removeLongWords
from utils import removeHtml from utils import removeHtml
from utils import locatePost from utils import locatePost
from utils import loadJson from utils import loadJson
from utils import getConfigParam
from utils import votesOnNewswireItem from utils import votesOnNewswireItem
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import isEditor from utils import isEditor
from posts import isModerator from posts import isModerator
from webapp_utils import getRightImageFile from webapp_utils import getRightImageFile
from webapp_utils import getImageFile
from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter from webapp_utils import htmlFooter
from webapp_utils import getBannerFile from webapp_utils import getBannerFile
@ -51,7 +48,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, authorized: bool,
showHeaderImage: bool) -> str: showHeaderImage: bool,
theme: str) -> str:
"""Returns html content for the right column """Returns html content for the right column
""" """
htmlStr = '' htmlStr = ''
@ -84,23 +82,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
editImageClass = '' editImageClass = ''
if showHeaderImage: if showHeaderImage:
rightImageFile, rightColumnImageFilename = \ rightImageFile, rightColumnImageFilename = \
getRightImageFile(baseDir, nickname, domain) getRightImageFile(baseDir, nickname, domain, theme)
if not os.path.isfile(rightColumnImageFilename):
theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default':
theme = ''
else:
theme = '_' + theme
themeRightImageFile, themeRightColumnImageFilename = \
getImageFile(baseDir, 'right_col_image', baseDir + '/img',
nickname, domain)
if os.path.isfile(themeRightColumnImageFilename):
rightColumnImageFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + themeRightImageFile
copyfile(themeRightColumnImageFilename,
rightColumnImageFilename)
rightImageFile = themeRightImageFile
# show the image at the top of the column # show the image at the top of the column
editImageClass = 'rightColEdit' editImageClass = 'rightColEdit'
@ -297,7 +279,8 @@ def htmlCitations(baseDir: str, nickname: str, domain: str,
blogTitle: str, blogContent: str, blogTitle: str, blogContent: str,
blogImageFilename: str, blogImageFilename: str,
blogImageAttachmentMediaType: str, blogImageAttachmentMediaType: str,
blogImageDescription: str) -> str: blogImageDescription: str,
theme: str) -> str:
"""Show the citations screen when creating a blog """Show the citations screen when creating a blog
""" """
htmlStr = '' htmlStr = ''
@ -329,7 +312,8 @@ def htmlCitations(baseDir: str, nickname: str, domain: str,
htmlStr = htmlHeaderWithExternalStyle(cssFilename) htmlStr = htmlHeaderWithExternalStyle(cssFilename)
# top banner # top banner
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
htmlStr += \ htmlStr += \
'<a href="/users/' + nickname + '/newblog" title="' + \ '<a href="/users/' + nickname + '/newblog" title="' + \
translate['Go Back'] + '" alt="' + \ translate['Go Back'] + '" alt="' + \
@ -412,7 +396,8 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
authorized: bool, authorized: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
iconsAsButtons: bool, iconsAsButtons: bool,
defaultTimeline: str) -> str: defaultTimeline: str,
theme: str) -> str:
"""Shows the mobile version of the newswire right column """Shows the mobile version of the newswire right column
""" """
htmlStr = '' htmlStr = ''
@ -436,7 +421,8 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
htmlStr = htmlHeaderWithExternalStyle(cssFilename) htmlStr = htmlHeaderWithExternalStyle(cssFilename)
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
htmlStr += \ htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
@ -456,7 +442,7 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
newswire, positiveVoting, newswire, positiveVoting,
False, timelinePath, showPublishButton, False, timelinePath, showPublishButton,
showPublishAsIcon, rssIconAtTop, False, showPublishAsIcon, rssIconAtTop, False,
authorized, False) authorized, False, theme)
else: else:
if editor: if editor:
htmlStr += '<br><br><br>\n' htmlStr += '<br><br><br>\n'
@ -472,7 +458,7 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str, def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str, domain: str, port: int, httpPrefix: str,
defaultTimeline: str) -> str: defaultTimeline: str, theme: str) -> str:
"""Shows the edit newswire screen """Shows the edit newswire screen
""" """
if '/users/' not in path: if '/users/' not in path:
@ -493,7 +479,8 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
cssFilename = baseDir + '/links.css' cssFilename = baseDir + '/links.css'
# filename of the banner shown at the top # filename of the banner shown at the top
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
editNewswireForm = htmlHeaderWithExternalStyle(cssFilename) editNewswireForm = htmlHeaderWithExternalStyle(cssFilename)

View File

@ -169,7 +169,8 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
reportUrl: str, pageNumber: int, reportUrl: str, pageNumber: int,
nickname: str, domain: str, nickname: str, domain: str,
domainFull: str, domainFull: str,
defaultTimeline: str, newswire: {}) -> str: defaultTimeline: str, newswire: {},
theme: str) -> str:
"""New post screen """New post screen
""" """
replyStr = '' replyStr = ''
@ -178,7 +179,8 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
messageBoxHeight = 400 messageBoxHeight = 400
# filename of the banner shown at the top # filename of the banner shown at the top
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
if not path.endswith('/newshare'): if not path.endswith('/newshare'):
if not path.endswith('/newreport'): if not path.endswith('/newreport'):

View File

@ -83,7 +83,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
session, wfRequest: {}, personCache: {}, session, wfRequest: {}, personCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
newswire: {}, extraJson=None, newswire: {}, theme: str, extraJson=None,
pageNumber=None, maxItemsPerPage=None) -> str: pageNumber=None, maxItemsPerPage=None) -> str:
"""Show the news instance front screen """Show the news instance front screen
""" """
@ -104,7 +104,8 @@ def htmlFrontScreen(rssIconAtTop: bool,
iconsAsButtons) iconsAsButtons)
# If this is the news account then show a different banner # If this is the news account then show a different banner
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
profileHeaderStr = \ profileHeaderStr = \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
'src="/users/' + nickname + '/' + bannerFile + '" />\n' 'src="/users/' + nickname + '/' + bannerFile + '" />\n'
@ -124,7 +125,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
getLeftColumnContent(baseDir, 'news', domainFull, getLeftColumnContent(baseDir, 'news', domainFull,
httpPrefix, translate, httpPrefix, translate,
False, False, None, rssIconAtTop, True, False, False, None, rssIconAtTop, True,
True) True, theme)
profileHeaderStr += ' </td>\n' profileHeaderStr += ' </td>\n'
profileHeaderStr += ' <td valign="top" class="col-center">\n' profileHeaderStr += ' <td valign="top" class="col-center">\n'
@ -136,7 +137,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
licenseStr = '' licenseStr = ''
bannerFile, bannerFilename = \ bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain) getBannerFile(baseDir, nickname, domain, theme)
profileStr += \ profileStr += \
htmlFrontScreenPosts(recentPostsCache, maxRecentPosts, htmlFrontScreenPosts(recentPostsCache, maxRecentPosts,
translate, translate,
@ -155,7 +156,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
httpPrefix, translate, httpPrefix, translate,
False, False, newswire, False, False, False, newswire, False,
False, None, False, False, False, None, False, False,
False, True, authorized, True) False, True, authorized, True, theme)
profileFooterStr += ' </td>\n' profileFooterStr += ' </td>\n'
profileFooterStr += ' </tr>\n' profileFooterStr += ' </tr>\n'
profileFooterStr += ' </tbody>\n' profileFooterStr += ' </tbody>\n'

View File

@ -9,12 +9,10 @@ __status__ = "Production"
import os import os
from shutil import copyfile from shutil import copyfile
from datetime import datetime from datetime import datetime
from utils import getConfigParam
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import getHashtagCategories from utils import getHashtagCategories
from utils import getHashtagCategory from utils import getHashtagCategory
from webapp_utils import getSearchBannerFile from webapp_utils import getSearchBannerFile
from webapp_utils import getImageFile
from webapp_utils import getContentWarningButton from webapp_utils import getContentWarningButton
from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter from webapp_utils import htmlFooter
@ -231,7 +229,8 @@ def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
def htmlSearchHashtagCategory(cssCache: {}, translate: {}, def htmlSearchHashtagCategory(cssCache: {}, translate: {},
baseDir: str, path: str, domain: str) -> str: baseDir: str, path: str, domain: str,
theme: str) -> str:
"""Show hashtags after selecting a category on the main search screen """Show hashtags after selecting a category on the main search screen
""" """
actor = path.split('/category/')[0] actor = path.split('/category/')[0]
@ -251,24 +250,7 @@ def htmlSearchHashtagCategory(cssCache: {}, translate: {},
# show a banner above the search box # show a banner above the search box
searchBannerFile, searchBannerFilename = \ searchBannerFile, searchBannerFilename = \
getSearchBannerFile(baseDir, searchNickname, domain) getSearchBannerFile(baseDir, searchNickname, domain, theme)
if not os.path.isfile(searchBannerFilename):
# get the default search banner for the theme
theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default':
theme = ''
else:
theme = '_' + theme
themeSearchImageFile, themeSearchBannerFilename = \
getImageFile(baseDir, 'search_banner', baseDir + '/img',
searchNickname, domain)
if os.path.isfile(themeSearchBannerFilename):
searchBannerFilename = \
baseDir + '/accounts/' + \
searchNickname + '@' + domain + '/' + themeSearchImageFile
copyfile(themeSearchBannerFilename,
searchBannerFilename)
searchBannerFile = themeSearchImageFile
if os.path.isfile(searchBannerFilename): if os.path.isfile(searchBannerFilename):
htmlStr += '<a href="' + actor + '/search">\n' htmlStr += '<a href="' + actor + '/search">\n'

View File

@ -7,10 +7,14 @@ __email__ = "bob@freedombone.net"
__status__ = "Production" __status__ = "Production"
import os import os
from utils import isEditor
from utils import loadJson
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import getDomainFromActor from utils import getDomainFromActor
from posts import getPublicPostInfo from posts import getPublicPostInfo
from posts import isModerator
from webapp_timeline import htmlTimeline from webapp_timeline import htmlTimeline
# from webapp_utils import getPersonAvatarUrl
from webapp_utils import getContentWarningButton from webapp_utils import getContentWarningButton
from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter from webapp_utils import htmlFooter
@ -152,11 +156,73 @@ def htmlModerationInfo(cssCache: {}, translate: {},
'</a></h1></center><br>' '</a></h1></center><br>'
infoShown = False infoShown = False
accounts = []
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct or 'news@' in acct:
continue
accounts.append(acct)
break
accounts.sort()
cols = 5
if len(accounts) > 10:
infoForm += '<details><summary><b>' + translate['Show Accounts']
infoForm += '</b></summary>\n'
infoForm += '<div class="container">\n'
infoForm += '<table class="accountsTable">\n'
infoForm += ' <colgroup>\n'
for col in range(cols):
infoForm += ' <col span="1" class="accountsTableCol">\n'
infoForm += ' </colgroup>\n'
infoForm += '<tr>\n'
col = 0
for acct in accounts:
acctNickname = acct.split('@')[0]
accountDir = os.path.join(baseDir + '/accounts', acct)
actorJson = loadJson(accountDir + '.json')
if not actorJson:
continue
actor = actorJson['id']
avatarUrl = ''
ext = ''
if actorJson.get('icon'):
if actorJson['icon'].get('url'):
avatarUrl = actorJson['icon']['url']
if '.' in avatarUrl:
ext = '.' + avatarUrl.split('.')[-1]
acctUrl = \
'/users/' + nickname + '?options=' + actor + ';1;' + \
'/members/' + acctNickname + ext
infoForm += '<td>\n<a href="' + acctUrl + '">'
infoForm += '<img loading="lazy" style="width:90%" '
infoForm += 'src="' + avatarUrl + '" />'
infoForm += '<br><center>'
if isModerator(baseDir, acctNickname):
infoForm += '<b><u>' + acctNickname + '</u></b>'
else:
infoForm += acctNickname
if isEditor(baseDir, acctNickname):
infoForm += ''
infoForm += '</center></a>\n</td>\n'
col += 1
if col == cols:
# new row of accounts
infoForm += '</tr>\n<tr>\n'
infoForm += '</tr>\n</table>\n'
infoForm += '</div>\n'
if len(accounts) > 10:
infoForm += '</details>\n'
suspendedFilename = baseDir + '/accounts/suspended.txt' suspendedFilename = baseDir + '/accounts/suspended.txt'
if os.path.isfile(suspendedFilename): if os.path.isfile(suspendedFilename):
with open(suspendedFilename, "r") as f: with open(suspendedFilename, "r") as f:
suspendedStr = f.read() suspendedStr = f.read()
infoForm += '<div class="container">' infoForm += '<div class="container">\n'
infoForm += ' <br><b>' + \ infoForm += ' <br><b>' + \
translate['Suspended accounts'] + '</b>' translate['Suspended accounts'] + '</b>'
infoForm += ' <br>' + \ infoForm += ' <br>' + \
@ -164,15 +230,15 @@ def htmlModerationInfo(cssCache: {}, translate: {},
infoForm += \ infoForm += \
' <textarea id="message" ' + \ ' <textarea id="message" ' + \
'name="suspended" style="height:200px">' + \ 'name="suspended" style="height:200px">' + \
suspendedStr + '</textarea>' suspendedStr + '</textarea>\n'
infoForm += '</div>' infoForm += '</div>\n'
infoShown = True infoShown = True
blockingFilename = baseDir + '/accounts/blocking.txt' blockingFilename = baseDir + '/accounts/blocking.txt'
if os.path.isfile(blockingFilename): if os.path.isfile(blockingFilename):
with open(blockingFilename, "r") as f: with open(blockingFilename, "r") as f:
blockedStr = f.read() blockedStr = f.read()
infoForm += '<div class="container">' infoForm += '<div class="container">\n'
infoForm += \ infoForm += \
' <br><b>' + \ ' <br><b>' + \
translate['Blocked accounts and hashtags'] + '</b>' translate['Blocked accounts and hashtags'] + '</b>'
@ -182,13 +248,29 @@ def htmlModerationInfo(cssCache: {}, translate: {},
infoForm += \ infoForm += \
' <textarea id="message" ' + \ ' <textarea id="message" ' + \
'name="blocked" style="height:700px">' + \ 'name="blocked" style="height:700px">' + \
blockedStr + '</textarea>' blockedStr + '</textarea>\n'
infoForm += '</div>' infoForm += '</div>\n'
infoShown = True infoShown = True
filtersFilename = baseDir + '/accounts/filters.txt'
if os.path.isfile(filtersFilename):
with open(filtersFilename, "r") as f:
filteredStr = f.read()
infoForm += '<div class="container">\n'
infoForm += \
' <br><b>' + \
translate['Filtered words'] + '</b>'
infoForm += \
' <textarea id="message" ' + \
'name="filtered" style="height:700px">' + \
filteredStr + '</textarea>\n'
infoForm += '</div>\n'
infoShown = True
if not infoShown: if not infoShown:
infoForm += \ infoForm += \
'<center><p>' + \ '<center><p>' + \
translate[msgStr2] + \ translate[msgStr2] + \
'</p></center>' '</p></center>\n'
infoForm += htmlFooter() infoForm += htmlFooter()
return infoForm return infoForm

View File

@ -43,7 +43,8 @@ def htmlPersonOptions(defaultTimeline: str,
PGPpubKey: str, PGPpubKey: str,
PGPfingerprint: str, PGPfingerprint: str,
emailAddress: str, emailAddress: str,
dormantMonths: int) -> str: dormantMonths: int,
backToPath: str) -> str:
"""Show options for a person: view/follow/block/report """Show options for a person: view/follow/block/report
""" """
optionsDomain, optionsPort = getDomainFromActor(optionsActor) optionsDomain, optionsPort = getDomainFromActor(optionsActor)
@ -225,6 +226,8 @@ def htmlPersonOptions(defaultTimeline: str,
backPath = '/' backPath = '/'
if nickname: if nickname:
backPath = '/users/' + nickname + '/' + defaultTimeline backPath = '/users/' + nickname + '/' + defaultTimeline
if 'moderation' in backToPath:
backPath = '/users/' + nickname + '/moderation'
optionsStr += \ optionsStr += \
' <a href="' + backPath + '"><button type="button" ' + \ ' <a href="' + backPath + '"><button type="button" ' + \
'class="buttonIcon" name="submitBack">' + translate['Go Back'] + \ 'class="buttonIcon" name="submitBack">' + translate['Go Back'] + \

View File

@ -47,6 +47,7 @@ from content import getMentionsFromHtml
from content import switchWords from content import switchWords
from person import isPersonSnoozed from person import isPersonSnoozed
from announce import announcedByPerson from announce import announcedByPerson
from webapp_utils import getAvatarImageUrl
from webapp_utils import getPersonAvatarUrl from webapp_utils import getPersonAvatarUrl
from webapp_utils import updateAvatarImageCache from webapp_utils import updateAvatarImageCache
from webapp_utils import loadIndividualPostAsHtmlFromCache from webapp_utils import loadIndividualPostAsHtmlFromCache
@ -177,32 +178,6 @@ def getPostFromRecentCache(session,
return postHtml return postHtml
def getAvatarImageUrl(session,
baseDir: str, httpPrefix: str,
postActor: str, personCache: {},
avatarUrl: str, allowDownloads: bool) -> str:
"""Returns the avatar image url
"""
# get the avatar image url for the post actor
if not avatarUrl:
avatarUrl = \
getPersonAvatarUrl(baseDir, postActor, personCache,
allowDownloads)
avatarUrl = \
updateAvatarImageCache(session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
else:
updateAvatarImageCache(session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
if not avatarUrl:
avatarUrl = postActor + '/avatar.png'
return avatarUrl
def getAvatarImageHtml(showAvatarOptions: bool, def getAvatarImageHtml(showAvatarOptions: bool,
nickname: str, domainFull: str, nickname: str, domainFull: str,
avatarUrl: str, postActor: str, avatarUrl: str, postActor: str,
@ -1179,7 +1154,8 @@ def individualPostAsHtml(allowDownloads: bool,
avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, personCache,
projectVersion, httpPrefix, projectVersion, httpPrefix,
nickname, domain, 'outbox') nickname, domain, 'outbox',
72367)
logPostTiming(enableTimingLog, postStartTime, '6') logPostTiming(enableTimingLog, postStartTime, '6')
if avatarUrl2: if avatarUrl2:

View File

@ -34,6 +34,7 @@ from pgp import getPGPfingerprint
from pgp import getPGPpubKey from pgp import getPGPpubKey
from tox import getToxAddress from tox import getToxAddress
from jami import getJamiAddress from jami import getJamiAddress
from filters import isFiltered
from webapp_frontscreen import htmlFrontScreen from webapp_frontscreen import htmlFrontScreen
from webapp_utils import scheduledPostsExist from webapp_utils import scheduledPostsExist
from webapp_utils import getPersonAvatarUrl from webapp_utils import getPersonAvatarUrl
@ -259,10 +260,20 @@ def htmlProfileAfterSearch(cssCache: {},
continue continue
if not item.get('object'): if not item.get('object'):
continue continue
# wfRequest = {}
# requestHandle = nickname + '@' + domain
# if cachedWebfingers.get(requestHandle):
# wfRequest = cachedWebfingers[requestHandle]
# elif cachedWebfingers.get(requestHandle + ':' + str(port)):
# wfRequest = cachedWebfingers[requestHandle + ':' + str(port)]
# TODO: this may need to be changed
wfRequest = cachedWebfingers
profileStr += \ profileStr += \
individualPostAsHtml(True, recentPostsCache, maxRecentPosts, individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
translate, None, baseDir, translate, None, baseDir,
session, cachedWebfingers, personCache, session, wfRequest, personCache,
nickname, domain, port, nickname, domain, port,
item, avatarUrl, False, False, item, avatarUrl, False, False,
httpPrefix, projectVersion, 'inbox', httpPrefix, projectVersion, 'inbox',
@ -282,7 +293,8 @@ def getProfileHeader(baseDir: str, nickname: str, domain: str,
displayName: str, displayName: str,
avatarDescription: str, avatarDescription: str,
profileDescriptionShort: str, profileDescriptionShort: str,
loginButton: str, avatarUrl: str) -> str: loginButton: str, avatarUrl: str,
theme: str) -> str:
"""The header of the profile screen, containing background """The header of the profile screen, containing background
image and avatar image and avatar
""" """
@ -291,7 +303,7 @@ def getProfileHeader(baseDir: str, nickname: str, domain: str,
nickname + '/' + defaultTimeline + '" title="' + \ nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
htmlStr += ' <img class="profileBackground" ' + \ htmlStr += ' <img class="profileBackground" ' + \
'src="/users/' + nickname + '/image.png" /></a>\n' 'src="/users/' + nickname + '/image_' + theme + '.png" /></a>\n'
htmlStr += ' <figcaption>\n' htmlStr += ' <figcaption>\n'
htmlStr += \ htmlStr += \
' <a href="/users/' + \ ' <a href="/users/' + \
@ -359,7 +371,7 @@ def htmlProfile(rssIconAtTop: bool,
session, wfRequest: {}, personCache: {}, session, wfRequest: {}, personCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
newswire: {}, dormantMonths: int, newswire: {}, theme: str, dormantMonths: int,
extraJson=None, pageNumber=None, extraJson=None, pageNumber=None,
maxItemsPerPage=None) -> str: maxItemsPerPage=None) -> str:
"""Show the profile page as html """Show the profile page as html
@ -378,7 +390,7 @@ def htmlProfile(rssIconAtTop: bool,
session, wfRequest, personCache, session, wfRequest, personCache,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
newswire, extraJson, newswire, theme, extraJson,
pageNumber, maxItemsPerPage) pageNumber, maxItemsPerPage)
domain, port = getDomainFromActor(profileJson['id']) domain, port = getDomainFromActor(profileJson['id'])
@ -561,7 +573,7 @@ def htmlProfile(rssIconAtTop: bool,
defaultTimeline, displayName, defaultTimeline, displayName,
avatarDescription, avatarDescription,
profileDescriptionShort, profileDescriptionShort,
loginButton, avatarUrl) loginButton, avatarUrl, theme)
profileStr = profileHeaderStr + donateSection profileStr = profileHeaderStr + donateSection
profileStr += '<div class="container" id="buttonheader">\n' profileStr += '<div class="container" id="buttonheader">\n'
@ -819,7 +831,7 @@ def htmlProfileShares(actor: str, translate: {},
def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str, domain: str, port: int, httpPrefix: str,
defaultTimeline: str) -> str: defaultTimeline: str, theme: str) -> str:
"""Shows the edit profile screen """Shows the edit profile screen
""" """
imageFormats = getImageFormats() imageFormats = getImageFormats()
@ -836,7 +848,8 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
return '' return ''
# filename of the banner shown at the top # filename of the banner shown at the top
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
isBot = '' isBot = ''
isGroup = '' isGroup = ''
@ -872,10 +885,13 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
PGPpubKey = getPGPpubKey(actorJson) PGPpubKey = getPGPpubKey(actorJson)
PGPfingerprint = getPGPfingerprint(actorJson) PGPfingerprint = getPGPfingerprint(actorJson)
if actorJson.get('name'): if actorJson.get('name'):
displayNickname = actorJson['name'] if not isFiltered(baseDir, nickname, domain, actorJson['name']):
displayNickname = actorJson['name']
if actorJson.get('summary'): if actorJson.get('summary'):
bioStr = \ bioStr = \
actorJson['summary'].replace('<p>', '').replace('</p>', '') actorJson['summary'].replace('<p>', '').replace('</p>', '')
if isFiltered(baseDir, nickname, domain, bioStr):
bioStr = ''
if actorJson.get('manuallyApprovesFollowers'): if actorJson.get('manuallyApprovesFollowers'):
if actorJson['manuallyApprovesFollowers']: if actorJson['manuallyApprovesFollowers']:
manuallyApprovesFollowers = 'checked' manuallyApprovesFollowers = 'checked'
@ -982,6 +998,8 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
skillCtr = 1 skillCtr = 1
if skills: if skills:
for skillDesc, skillValue in skills.items(): for skillDesc, skillValue in skills.items():
if isFiltered(baseDir, nickname, domain, skillDesc):
continue
skillsStr += \ skillsStr += \
'<p><input type="text" placeholder="' + translate['Skill'] + \ '<p><input type="text" placeholder="' + translate['Skill'] + \
' ' + str(skillCtr) + '" name="skillName' + str(skillCtr) + \ ' ' + str(skillCtr) + '" name="skillName' + str(skillCtr) + \
@ -1457,7 +1475,7 @@ def individualFollowAsHtml(translate: {},
avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest, avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, projectVersion, personCache, projectVersion,
httpPrefix, nickname, httpPrefix, nickname,
domain, 'outbox') domain, 'outbox', 43036)
if avatarUrl2: if avatarUrl2:
avatarUrl = avatarUrl2 avatarUrl = avatarUrl2
if displayName: if displayName:

View File

@ -15,7 +15,6 @@ from utils import isEditor
from utils import loadJson from utils import loadJson
from utils import getDomainFromActor from utils import getDomainFromActor
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import getConfigParam
from utils import locatePost from utils import locatePost
from utils import isPublicPost from utils import isPublicPost
from utils import firstParagraphFromString from utils import firstParagraphFromString
@ -24,7 +23,6 @@ from utils import getHashtagCategory
from feeds import rss2TagHeader from feeds import rss2TagHeader
from feeds import rss2TagFooter from feeds import rss2TagFooter
from webapp_utils import getAltPath from webapp_utils import getAltPath
from webapp_utils import getImageFile
from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter from webapp_utils import htmlFooter
from webapp_utils import getSearchBannerFile from webapp_utils import getSearchBannerFile
@ -312,7 +310,7 @@ def htmlSearchEmojiTextEntry(cssCache: {}, translate: {},
def htmlSearch(cssCache: {}, translate: {}, def htmlSearch(cssCache: {}, translate: {},
baseDir: str, path: str, domain: str, baseDir: str, path: str, domain: str,
defaultTimeline: str) -> str: defaultTimeline: str, theme: str) -> str:
"""Search called from the timeline icon """Search called from the timeline icon
""" """
actor = path.replace('/search', '') actor = path.replace('/search', '')
@ -331,24 +329,7 @@ def htmlSearch(cssCache: {}, translate: {},
# show a banner above the search box # show a banner above the search box
searchBannerFile, searchBannerFilename = \ searchBannerFile, searchBannerFilename = \
getSearchBannerFile(baseDir, searchNickname, domain) getSearchBannerFile(baseDir, searchNickname, domain, theme)
if not os.path.isfile(searchBannerFilename):
# get the default search banner for the theme
theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default':
theme = ''
else:
theme = '_' + theme
themeSearchImageFile, themeSearchBannerFilename = \
getImageFile(baseDir, 'search_banner', baseDir + '/img',
searchNickname, domain)
if os.path.isfile(themeSearchBannerFilename):
searchBannerFilename = \
baseDir + '/accounts/' + \
searchNickname + '@' + domain + '/' + themeSearchImageFile
copyfile(themeSearchBannerFilename,
searchBannerFilename)
searchBannerFile = themeSearchImageFile
if os.path.isfile(searchBannerFilename): if os.path.isfile(searchBannerFilename):
usersPath = '/users/' + searchNickname usersPath = '/users/' + searchNickname

View File

@ -60,7 +60,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, authorized: bool,
moderationActionStr: str) -> str: moderationActionStr: str,
theme: str) -> str:
"""Show the timeline as html """Show the timeline as html
""" """
enableTimingLog = False enableTimingLog = False
@ -123,7 +124,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
cssFilename = baseDir + '/epicyon.css' cssFilename = baseDir + '/epicyon.css'
# filename of the banner shown at the top # filename of the banner shown at the top
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '1') logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '1')
@ -404,7 +406,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
getLeftColumnContent(baseDir, nickname, domainFull, getLeftColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate, httpPrefix, translate,
editor, False, None, rssIconAtTop, editor, False, None, rssIconAtTop,
True, False) True, False, theme)
tlStr += ' <td valign="top" class="col-left">' + \ tlStr += ' <td valign="top" class="col-left">' + \
leftColumnStr + ' </td>\n' leftColumnStr + ' </td>\n'
# center column containing posts # center column containing posts
@ -444,6 +446,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
else: else:
tlStr += ' <input type="text" ' + \ tlStr += ' <input type="text" ' + \
'name="moderationAction" value="" autofocus><br>\n' 'name="moderationAction" value="" autofocus><br>\n'
tlStr += \ tlStr += \
' <input type="submit" title="' + \ ' <input type="submit" title="' + \
translate['Information about current blocks/suspensions'] + \ translate['Information about current blocks/suspensions'] + \
@ -453,6 +456,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Remove the above item'] + \ translate['Remove the above item'] + \
'" name="submitRemove" value="' + \ '" name="submitRemove" value="' + \
translate['Remove'] + '">\n' translate['Remove'] + '">\n'
tlStr += \ tlStr += \
' <input type="submit" title="' + \ ' <input type="submit" title="' + \
translate['Suspend the above account nickname'] + \ translate['Suspend the above account nickname'] + \
@ -462,6 +466,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Remove a suspension for an account nickname'] + \ translate['Remove a suspension for an account nickname'] + \
'" name="submitUnsuspend" value="' + \ '" name="submitUnsuspend" value="' + \
translate['Unsuspend'] + '">\n' translate['Unsuspend'] + '">\n'
tlStr += \ tlStr += \
' <input type="submit" title="' + \ ' <input type="submit" title="' + \
translate['Block an account on another instance'] + \ translate['Block an account on another instance'] + \
@ -470,6 +475,16 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
' <input type="submit" title="' + \ ' <input type="submit" title="' + \
translate['Unblock an account on another instance'] + \ translate['Unblock an account on another instance'] + \
'" name="submitUnblock" value="' + translate['Unblock'] + '">\n' '" name="submitUnblock" value="' + translate['Unblock'] + '">\n'
tlStr += \
' <input type="submit" title="' + \
translate['Filter out words'] + \
'" name="submitFilter" value="' + translate['Filter'] + '">\n'
tlStr += \
' <input type="submit" title="' + \
translate['Unfilter words'] + \
'" name="submitUnfilter" value="' + translate['Unfilter'] + '">\n'
tlStr += '</div>\n</form>\n' tlStr += '</div>\n</form>\n'
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '6') logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '6')
@ -592,7 +607,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
False, None, True, False, None, True,
showPublishAsIcon, showPublishAsIcon,
rssIconAtTop, publishButtonAtTop, rssIconAtTop, publishButtonAtTop,
authorized, True) authorized, True, theme)
tlStr += ' <td valign="top" class="col-right">' + \ tlStr += ' <td valign="top" class="col-right">' + \
rightColumnStr + ' </td>\n' rightColumnStr + ' </td>\n'
tlStr += ' </tr>\n' tlStr += ' </tr>\n'
@ -705,7 +720,7 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the shares timeline as html """Show the shares timeline as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -724,7 +739,7 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlInbox(cssCache: {}, defaultTimeline: str, def htmlInbox(cssCache: {}, defaultTimeline: str,
@ -742,7 +757,7 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the inbox as html """Show the inbox as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -761,7 +776,7 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlBookmarks(cssCache: {}, defaultTimeline: str, def htmlBookmarks(cssCache: {}, defaultTimeline: str,
@ -779,7 +794,7 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the bookmarks as html """Show the bookmarks as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -798,7 +813,7 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlEvents(cssCache: {}, defaultTimeline: str, def htmlEvents(cssCache: {}, defaultTimeline: str,
@ -816,7 +831,7 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the events as html """Show the events as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -835,7 +850,7 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlInboxDMs(cssCache: {}, defaultTimeline: str, def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
@ -853,7 +868,7 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the DM timeline as html """Show the DM timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -867,7 +882,7 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
showPublishAsIcon, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlInboxReplies(cssCache: {}, defaultTimeline: str, def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
@ -885,7 +900,7 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the replies timeline as html """Show the replies timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -900,7 +915,7 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlInboxMedia(cssCache: {}, defaultTimeline: str, def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
@ -918,7 +933,7 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the media timeline as html """Show the media timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -933,7 +948,7 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlInboxBlogs(cssCache: {}, defaultTimeline: str, def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
@ -951,7 +966,7 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the blogs timeline as html """Show the blogs timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -966,7 +981,7 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlInboxFeatures(cssCache: {}, defaultTimeline: str, def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
@ -984,7 +999,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool,
theme: str) -> str:
"""Show the features timeline as html """Show the features timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -999,7 +1015,7 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlInboxNews(cssCache: {}, defaultTimeline: str, def htmlInboxNews(cssCache: {}, defaultTimeline: str,
@ -1017,7 +1033,7 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the news timeline as html """Show the news timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -1032,7 +1048,7 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)
def htmlOutbox(cssCache: {}, defaultTimeline: str, def htmlOutbox(cssCache: {}, defaultTimeline: str,
@ -1050,7 +1066,7 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool, iconsAsButtons: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool) -> str: authorized: bool, theme: str) -> str:
"""Show the Outbox as html """Show the Outbox as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -1066,4 +1082,4 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
newswire, False, False, positiveVoting, newswire, False, False, positiveVoting,
showPublishAsIcon, fullWidthTimelineButtonHeader, showPublishAsIcon, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None) authorized, None, theme)

View File

@ -476,7 +476,7 @@ def postContainsPublic(postJsonObject: {}) -> bool:
def getImageFile(baseDir: str, name: str, directory: str, def getImageFile(baseDir: str, name: str, directory: str,
nickname: str, domain: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
""" """
returns the filenames for an image with the given name returns the filenames for an image with the given name
""" """
@ -484,39 +484,41 @@ def getImageFile(baseDir: str, name: str, directory: str,
bannerFile = '' bannerFile = ''
bannerFilename = '' bannerFilename = ''
for ext in bannerExtensions: for ext in bannerExtensions:
bannerFile = name + '.' + ext bannerFileTest = name + '.' + ext
bannerFilename = directory + '/' + bannerFile bannerFilenameTest = directory + '/' + bannerFileTest
if os.path.isfile(bannerFilename): if os.path.isfile(bannerFilenameTest):
bannerFile = name + '_' + theme + '.' + ext
bannerFilename = bannerFilenameTest
break break
return bannerFile, bannerFilename return bannerFile, bannerFilename
def getBannerFile(baseDir: str, def getBannerFile(baseDir: str,
nickname: str, domain: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'banner', return getImageFile(baseDir, 'banner',
baseDir + '/accounts/' + nickname + '@' + domain, baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain) nickname, domain, theme)
def getSearchBannerFile(baseDir: str, def getSearchBannerFile(baseDir: str,
nickname: str, domain: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'search_banner', return getImageFile(baseDir, 'search_banner',
baseDir + '/accounts/' + nickname + '@' + domain, baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain) nickname, domain, theme)
def getLeftImageFile(baseDir: str, def getLeftImageFile(baseDir: str,
nickname: str, domain: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'left_col_image', return getImageFile(baseDir, 'left_col_image',
baseDir + '/accounts/' + nickname + '@' + domain, baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain) nickname, domain, theme)
def getRightImageFile(baseDir: str, def getRightImageFile(baseDir: str,
nickname: str, domain: str) -> (str, str): nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'right_col_image', return getImageFile(baseDir, 'right_col_image',
baseDir + '/accounts/' + nickname + '@' + domain, baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain) nickname, domain, theme)
def htmlHeaderWithExternalStyle(cssFilename: str, lang='en') -> str: def htmlHeaderWithExternalStyle(cssFilename: str, lang='en') -> str:
@ -850,3 +852,29 @@ def htmlHighlightLabel(label: str, highlight: bool) -> str:
if not highlight: if not highlight:
return label return label
return '*' + str(label) + '*' return '*' + str(label) + '*'
def getAvatarImageUrl(session,
baseDir: str, httpPrefix: str,
postActor: str, personCache: {},
avatarUrl: str, allowDownloads: bool) -> str:
"""Returns the avatar image url
"""
# get the avatar image url for the post actor
if not avatarUrl:
avatarUrl = \
getPersonAvatarUrl(baseDir, postActor, personCache,
allowDownloads)
avatarUrl = \
updateAvatarImageCache(session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
else:
updateAvatarImageCache(session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
if not avatarUrl:
avatarUrl = postActor + '/avatar.png'
return avatarUrl

View File

@ -67,6 +67,7 @@ def webfingerHandle(session, handle: str, httpPrefix: str,
wf = getWebfingerFromCache(nickname + '@' + wfDomain, wf = getWebfingerFromCache(nickname + '@' + wfDomain,
cachedWebfingers) cachedWebfingers)
if wf: if wf:
print('Webfinger from cache: ' + str(wf))
return wf return wf
url = '{}://{}/.well-known/webfinger'.format(httpPrefix, domain) url = '{}://{}/.well-known/webfinger'.format(httpPrefix, domain)
par = { par = {