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

merge-requests/30/head
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,
projectVersion, httpPrefix,
fromNickname, fromDomain,
postToBox)
postToBox, 73528)
if not inboxUrl:
if debug:

View File

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

View File

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

251
daemon.py
View File

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

View File

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

View File

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

View File

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

View File

@ -122,6 +122,11 @@ parser.add_argument('--dormantMonths',
default=3,
help='How many months does a followed account need to ' +
'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',
dest='maxNewswirePosts', type=int,
default=20,
@ -2035,6 +2040,11 @@ dormantMonths = \
if dormantMonths is not None:
args.dormantMonths = int(dormantMonths)
sendThreadsTimeoutMins = \
getConfigParam(baseDir, 'sendThreadsTimeoutMins')
if sendThreadsTimeoutMins is not None:
args.sendThreadsTimeoutMins = int(sendThreadsTimeoutMins)
allowNewsFollowers = \
getConfigParam(baseDir, 'allowNewsFollowers')
if allowNewsFollowers is not None:
@ -2083,7 +2093,8 @@ if setTheme(baseDir, themeName, domain, args.allowLocalNetworkAccess):
print('Theme set to ' + themeName)
if __name__ == "__main__":
runDaemon(args.dormantMonths,
runDaemon(args.sendThreadsTimeoutMins,
args.dormantMonths,
args.maxNewswirePosts,
args.allowLocalNetworkAccess,
args.maxFeedItemSizeKb,

View File

@ -23,6 +23,24 @@ def addFilter(baseDir: str, nickname: str, domain: str, words: str) -> bool:
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,
words: str) -> bool:
"""Removes a word filter
@ -43,6 +61,24 @@ def removeFilter(baseDir: str, nickname: str, domain: str,
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:
"""Returns true if the given post content is a retweet or twitter crosspost
"""
@ -53,12 +89,45 @@ def isTwitterPost(content: str) -> bool:
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:
"""Should the given content be filtered out?
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
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
removeTwitter = baseDir + '/accounts/' + \
nickname + '@' + domain + '/.removeTwitter'
@ -66,19 +135,6 @@ def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool:
if isTwitterPost(content):
return True
filtersFilename = baseDir + '/accounts/' + \
accountFiltersFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/filters.txt'
if os.path.isfile(filtersFilename):
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
return isFilteredBase(accountFiltersFilename, content)

View File

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

View File

@ -173,9 +173,19 @@ def inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
avatarUrl = None
if boxname != 'tlevents' and boxname != 'outbox':
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,
translate, pageNumber,
baseDir, session, cachedWebfingers, personCache,
baseDir, session, wfRequest, personCache,
nickname, domain, port, postJsonObject,
avatarUrl, True, allowDeletion,
httpPrefix, __version__, boxname, None,
@ -2456,7 +2466,7 @@ def runInboxQueueWatchdog(projectVersion: str, httpd) -> None:
httpd.thrInboxQueue.start()
while True:
time.sleep(20)
if not httpd.thrInboxQueue.isAlive() or httpd.restartInboxQueue:
if not httpd.thrInboxQueue.is_alive() or httpd.restartInboxQueue:
httpd.restartInboxQueueInProgress = True
httpd.thrInboxQueue.kill()
httpd.thrInboxQueue = inboxQueueOriginal.clone(runInboxQueue)

View File

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

View File

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

View File

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

View File

@ -191,7 +191,13 @@ def getDefaultPersonContext() -> str:
'fingerprintKey': {'@id': 'toot:fingerprintKey', '@type': '@id'},
'messageFranking': 'toot:messageFranking',
'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)
def getUserUrl(wfRequest: {}) -> str:
if wfRequest.get('links'):
for link in wfRequest['links']:
if link.get('type') and link.get('href'):
if link['type'] == 'application/activity+json':
if not ('/users/' in link['href'] or
'/accounts/' in link['href'] or
'/profile/' in link['href'] or
'/channel/' in link['href']):
print('Webfinger activity+json contains ' +
'single user instance actor')
return link['href']
def getUserUrl(wfRequest: {}, sourceId=0) -> str:
"""Gets the actor url from a webfinger request
"""
print('getUserUrl: ' + str(sourceId) + ' ' + str(wfRequest))
if not wfRequest.get('links'):
print('getUserUrl webfinger activity+json contains no links ' +
str(sourceId) + ' ' + str(wfRequest))
return None
for link in wfRequest['links']:
if not (link.get('type') and link.get('href')):
continue
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
@ -198,13 +207,14 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
personCache: {},
projectVersion: str, httpPrefix: 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'
asHeader = {
'Accept': 'application/activity+json; profile="' + profileStr + '"'
}
if not wfRequest.get('errors'):
personUrl = getUserUrl(wfRequest)
personUrl = getUserUrl(wfRequest, sourceId)
else:
if nickname == 'dev':
# try single user instance
@ -1174,7 +1184,7 @@ def postIsAddressedToFollowers(baseDir: str,
postJsonObject: {}) -> bool:
"""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'):
return False
@ -1192,7 +1202,7 @@ def postIsAddressedToFollowers(baseDir: str,
if postJsonObject.get('cc'):
ccList = postJsonObject['cc']
followersUrl = httpPrefix + '://' + domain + '/users/' + \
followersUrl = httpPrefix + '://' + domainFull + '/users/' + \
nickname + '/followers'
# does the followers url exist in 'to' or 'cc' lists?
@ -1765,7 +1775,8 @@ def sendPost(projectVersion: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, postToBox)
nickname, domain, postToBox,
72533)
if not inboxUrl:
return 3
@ -1880,7 +1891,8 @@ def sendPostViaServer(projectVersion: str,
personCache,
projectVersion, httpPrefix,
fromNickname,
fromDomain, postToBox)
fromDomain, postToBox,
82796)
if not inboxUrl:
if debug:
print('DEBUG: No ' + postToBox + ' was found for ' + handle)
@ -2079,7 +2091,8 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, postToBox)
nickname, domain, postToBox,
30873)
print("inboxUrl: " + str(inboxUrl))
print("toPersonId: " + str(toPersonId))
@ -2342,14 +2355,40 @@ def sendToNamedAddresses(session, baseDir: str,
def hasSharedInbox(session, httpPrefix: str, domain: str) -> bool:
"""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,
httpPrefix, {},
None, __version__)
if wfRequest:
if isinstance(wfRequest, dict):
if not wfRequest.get('errors'):
return True
tryHandles = [
domain + '@' + domain,
'inbox@' + domain
]
for handle in tryHandles:
wfRequest = webfingerHandle(session, handle,
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
@ -2388,24 +2427,35 @@ def sendToFollowers(session, baseDir: str,
clientToServer = False
# 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():
print('Sending post to followers progress ' +
str(int(sendingCtr * 100 / len(grouped.items()))) + '% ' +
followerDomain)
sendingCtr += 1
if debug:
print('DEBUG: follower handles for ' + followerDomain)
pprint(followerHandles)
# check that the follower's domain is active
followerDomainUrl = httpPrefix + '://' + followerDomain
if not siteIsActive(followerDomainUrl):
print('Domain is inactive: ' + followerDomainUrl)
print('Sending post to followers domain is inactive: ' +
followerDomainUrl)
continue
print('Domain is active: ' + followerDomainUrl)
print('Sending post to followers domain is active: ' +
followerDomainUrl)
withSharedInbox = hasSharedInbox(session, httpPrefix, followerDomain)
if debug:
if withSharedInbox:
print(followerDomain + ' has shared inbox')
else:
print(followerDomain + ' does not have a shared inbox')
if not withSharedInbox:
print('Sending post to followers, ' + followerDomain +
' does not have a shared inbox')
toPort = port
index = 0
@ -2438,22 +2488,14 @@ def sendToFollowers(session, baseDir: str,
toNickname = 'inbox'
if toNickname != 'inbox' and postJsonObject.get('type'):
if postJsonObject['type'] == 'Update':
if postJsonObject.get('object'):
if isinstance(postJsonObject['object'], dict):
if postJsonObject['object'].get('type'):
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 sendingProfileUpdate(postJsonObject):
print('Sending post to followers ' +
'shared inbox of ' + toDomain)
toNickname = 'inbox'
if debug:
print('DEBUG: Sending from ' + nickname + '@' + domain +
' to ' + toNickname + '@' + toDomain)
print('Sending post to followers from ' +
nickname + '@' + domain +
' to ' + toNickname + '@' + toDomain)
sendSignedJson(postJsonObject, session, baseDir,
nickname, fromDomain, port,
@ -2465,19 +2507,17 @@ def sendToFollowers(session, baseDir: str,
else:
# send to individual followers without using a shared inbox
for handle in followerHandles:
if debug:
print('DEBUG: Sending to ' + handle)
print('Sending post to followers ' + handle)
toNickname = handle.split('@')[0]
if debug:
if postJsonObject['type'] != 'Update':
print('DEBUG: Sending from ' +
nickname + '@' + domain + ' to ' +
toNickname + '@' + toDomain)
else:
print('DEBUG: Sending profile update from ' +
nickname + '@' + domain + ' to ' +
toNickname + '@' + toDomain)
if postJsonObject['type'] != 'Update':
print('Sending post to followers from ' +
nickname + '@' + domain + ' to ' +
toNickname + '@' + toDomain)
else:
print('Sending post to followers profile update from ' +
nickname + '@' + domain + ' to ' +
toNickname + '@' + toDomain)
sendSignedJson(postJsonObject, session, baseDir,
nickname, fromDomain, port,
@ -2492,6 +2532,10 @@ def sendToFollowers(session, baseDir: str,
if debug:
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,
nickname: str,
@ -3357,7 +3401,8 @@ def getPublicPostsOfPerson(baseDir: str, nickname: str, domain: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox')
nickname, domain, 'outbox',
62524)
maxMentions = 10
maxEmoji = 10
maxAttachments = 5
@ -3398,7 +3443,8 @@ def getPublicPostDomains(session, baseDir: str, nickname: str, domain: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox')
nickname, domain, 'outbox',
92522)
maxMentions = 99
maxEmoji = 99
maxAttachments = 5
@ -3441,7 +3487,8 @@ def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox')
nickname, domain, 'outbox',
13863)
maxMentions = 99
maxEmoji = 99
maxAttachments = 5
@ -3956,7 +4003,7 @@ def sendBlockViaServer(baseDir: str, session,
displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox)
fromDomain, postToBox, 72652)
if not inboxUrl:
if debug:
@ -4038,7 +4085,7 @@ def sendUndoBlockViaServer(baseDir: str, session,
fromPersonId, sharedInbox, avatarUrl,
displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox)
fromDomain, postToBox, 53892)
if not inboxUrl:
if debug:

View File

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

View File

@ -158,7 +158,7 @@ def runPostScheduleWatchdog(projectVersion: str, httpd) -> None:
httpd.thrPostSchedule.start()
while True:
time.sleep(20)
if not httpd.thrPostSchedule.isAlive():
if not httpd.thrPostSchedule.is_alive():
httpd.thrPostSchedule.kill()
httpd.thrPostSchedule = \
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') -> {}:
if not isinstance(url, str):
print('url: ' + str(url))
print('ERROR: getJson url should be a string')
print('ERROR: getJson failed, url should be a string')
return None
sessionParams = {}
sessionHeaders = {}
@ -71,23 +71,23 @@ def getJson(session, url: str, headers: {}, params: {},
sessionHeaders['User-Agent'] += \
'; +' + httpPrefix + '://' + domain + '/'
if not session:
print('WARN: no session specified for getJson')
print('WARN: getJson failed, no session specified for getJson')
try:
result = session.get(url, headers=sessionHeaders, params=sessionParams)
return result.json()
except requests.exceptions.RequestException as e:
print('ERROR: getJson failed\nurl: ' + str(url) + '\n' +
'headers: ' + str(sessionHeaders) + '\n' +
'params: ' + str(sessionParams) + '\n')
print('ERROR: getJson failed\nurl: ' + str(url) + ' ' +
'headers: ' + str(sessionHeaders) + ' ' +
'params: ' + str(sessionParams))
print(e)
except ValueError as e:
print('ERROR: getJson failed\nurl: ' + str(url) + '\n' +
'headers: ' + str(sessionHeaders) + '\n' +
'params: ' + str(sessionParams) + '\n')
print('ERROR: getJson failed\nurl: ' + str(url) + ' ' +
'headers: ' + str(sessionHeaders) + ' ' +
'params: ' + str(sessionParams) + ' ')
print(e)
except SocketError as e:
if e.errno == errno.ECONNRESET:
print('WARN: connection was reset during getJson')
print('WARN: getJson failed, connection was reset during getJson')
print(e)
return None

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -342,5 +342,10 @@
"Ask about a shared item.": "اسأل عن عنصر مشترك.",
"Account Information": "معلومات الحساب",
"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.",
"Account Information": "Informació del compte",
"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.",
"Account Information": "Gwybodaeth Gyfrif",
"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.",
"Account Information": "Kontoinformationen",
"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.",
"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"
"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.",
"Account Information": "Información de la cuenta",
"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é.",
"Account Information": "Information sur le compte",
"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.",
"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",
"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.": "एक साझा आइटम के बारे में पूछें।",
"Account Information": "खाते की जानकारी",
"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.",
"Account Information": "Informazioni account",
"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.": "共有アイテムについて質問します。",
"Account Information": "口座情報",
"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.",
"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"
"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.",
"Account Information": "Informação da conta",
"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.": "Спросите об общем элементе.",
"Account Information": "Информация об аккаунте",
"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.": "询问共享项目。",
"Account Information": "帐户信息",
"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"
import os
from shutil import copyfile
from utils import getConfigParam
from utils import getNicknameFromActor
from utils import isEditor
from webapp_utils import sharesTimelineJson
from webapp_utils import htmlPostSeparator
from webapp_utils import getLeftImageFile
from webapp_utils import getImageFile
from webapp_utils import headerButtonsFrontScreen
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
@ -70,7 +68,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
editor: bool,
showBackButton: bool, timelinePath: str,
rssIconAtTop: bool, showHeaderImage: bool,
frontPage: bool) -> str:
frontPage: bool, theme: str) -> str:
"""Returns html content for the left column
"""
htmlStr = ''
@ -83,23 +81,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
editImageClass = ''
if showHeaderImage:
leftImageFile, leftColumnImageFilename = \
getLeftImageFile(baseDir, nickname, domain)
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
getLeftImageFile(baseDir, nickname, domain, theme)
# show the image at the top of the column
editImageClass = 'leftColEdit'
@ -255,7 +237,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
timelinePath: str, authorized: bool,
rssIconAtTop: bool,
iconsAsButtons: bool,
defaultTimeline: str) -> str:
defaultTimeline: str,
theme: str) -> str:
"""Show the left column links within mobile view
"""
htmlStr = ''
@ -276,7 +259,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
domain = domain.split(':')[0]
htmlStr = htmlHeaderWithExternalStyle(cssFilename)
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \
@ -293,7 +277,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
httpPrefix, translate,
editor,
False, timelinePath,
rssIconAtTop, False, False)
rssIconAtTop, False, False,
theme)
else:
if editor:
htmlStr += '<br><br><br>\n'
@ -310,7 +295,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str,
defaultTimeline: str) -> str:
defaultTimeline: str, theme: str) -> str:
"""Shows the edit links screen
"""
if '/users/' not in path:
@ -331,7 +316,8 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
cssFilename = baseDir + '/links.css'
# filename of the banner shown at the top
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
editLinksForm = htmlHeaderWithExternalStyle(cssFilename)

View File

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

View File

@ -169,7 +169,8 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
reportUrl: str, pageNumber: int,
nickname: str, domain: str,
domainFull: str,
defaultTimeline: str, newswire: {}) -> str:
defaultTimeline: str, newswire: {},
theme: str) -> str:
"""New post screen
"""
replyStr = ''
@ -178,7 +179,8 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
messageBoxHeight = 400
# 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('/newreport'):

View File

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

View File

@ -9,12 +9,10 @@ __status__ = "Production"
import os
from shutil import copyfile
from datetime import datetime
from utils import getConfigParam
from utils import getNicknameFromActor
from utils import getHashtagCategories
from utils import getHashtagCategory
from webapp_utils import getSearchBannerFile
from webapp_utils import getImageFile
from webapp_utils import getContentWarningButton
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
@ -231,7 +229,8 @@ def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
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
"""
actor = path.split('/category/')[0]
@ -251,24 +250,7 @@ def htmlSearchHashtagCategory(cssCache: {}, translate: {},
# show a banner above the search box
searchBannerFile, searchBannerFilename = \
getSearchBannerFile(baseDir, searchNickname, domain)
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
getSearchBannerFile(baseDir, searchNickname, domain, theme)
if os.path.isfile(searchBannerFilename):
htmlStr += '<a href="' + actor + '/search">\n'

View File

@ -7,10 +7,14 @@ __email__ = "bob@freedombone.net"
__status__ = "Production"
import os
from utils import isEditor
from utils import loadJson
from utils import getNicknameFromActor
from utils import getDomainFromActor
from posts import getPublicPostInfo
from posts import isModerator
from webapp_timeline import htmlTimeline
# from webapp_utils import getPersonAvatarUrl
from webapp_utils import getContentWarningButton
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
@ -152,11 +156,73 @@ def htmlModerationInfo(cssCache: {}, translate: {},
'</a></h1></center><br>'
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'
if os.path.isfile(suspendedFilename):
with open(suspendedFilename, "r") as f:
suspendedStr = f.read()
infoForm += '<div class="container">'
infoForm += '<div class="container">\n'
infoForm += ' <br><b>' + \
translate['Suspended accounts'] + '</b>'
infoForm += ' <br>' + \
@ -164,15 +230,15 @@ def htmlModerationInfo(cssCache: {}, translate: {},
infoForm += \
' <textarea id="message" ' + \
'name="suspended" style="height:200px">' + \
suspendedStr + '</textarea>'
infoForm += '</div>'
suspendedStr + '</textarea>\n'
infoForm += '</div>\n'
infoShown = True
blockingFilename = baseDir + '/accounts/blocking.txt'
if os.path.isfile(blockingFilename):
with open(blockingFilename, "r") as f:
blockedStr = f.read()
infoForm += '<div class="container">'
infoForm += '<div class="container">\n'
infoForm += \
' <br><b>' + \
translate['Blocked accounts and hashtags'] + '</b>'
@ -182,13 +248,29 @@ def htmlModerationInfo(cssCache: {}, translate: {},
infoForm += \
' <textarea id="message" ' + \
'name="blocked" style="height:700px">' + \
blockedStr + '</textarea>'
infoForm += '</div>'
blockedStr + '</textarea>\n'
infoForm += '</div>\n'
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:
infoForm += \
'<center><p>' + \
translate[msgStr2] + \
'</p></center>'
'</p></center>\n'
infoForm += htmlFooter()
return infoForm

View File

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

View File

@ -47,6 +47,7 @@ from content import getMentionsFromHtml
from content import switchWords
from person import isPersonSnoozed
from announce import announcedByPerson
from webapp_utils import getAvatarImageUrl
from webapp_utils import getPersonAvatarUrl
from webapp_utils import updateAvatarImageCache
from webapp_utils import loadIndividualPostAsHtmlFromCache
@ -177,32 +178,6 @@ def getPostFromRecentCache(session,
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,
nickname: str, domainFull: str,
avatarUrl: str, postActor: str,
@ -1179,7 +1154,8 @@ def individualPostAsHtml(allowDownloads: bool,
avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox')
nickname, domain, 'outbox',
72367)
logPostTiming(enableTimingLog, postStartTime, '6')
if avatarUrl2:

View File

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

View File

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

View File

@ -60,7 +60,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool,
moderationActionStr: str) -> str:
moderationActionStr: str,
theme: str) -> str:
"""Show the timeline as html
"""
enableTimingLog = False
@ -123,7 +124,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
cssFilename = baseDir + '/epicyon.css'
# 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')
@ -404,7 +406,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
getLeftColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate,
editor, False, None, rssIconAtTop,
True, False)
True, False, theme)
tlStr += ' <td valign="top" class="col-left">' + \
leftColumnStr + ' </td>\n'
# center column containing posts
@ -444,6 +446,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
else:
tlStr += ' <input type="text" ' + \
'name="moderationAction" value="" autofocus><br>\n'
tlStr += \
' <input type="submit" title="' + \
translate['Information about current blocks/suspensions'] + \
@ -453,6 +456,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Remove the above item'] + \
'" name="submitRemove" value="' + \
translate['Remove'] + '">\n'
tlStr += \
' <input type="submit" title="' + \
translate['Suspend the above account nickname'] + \
@ -462,6 +466,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Remove a suspension for an account nickname'] + \
'" name="submitUnsuspend" value="' + \
translate['Unsuspend'] + '">\n'
tlStr += \
' <input type="submit" title="' + \
translate['Block an account on another instance'] + \
@ -470,6 +475,16 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
' <input type="submit" title="' + \
translate['Unblock an account on another instance'] + \
'" 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'
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '6')
@ -592,7 +607,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
False, None, True,
showPublishAsIcon,
rssIconAtTop, publishButtonAtTop,
authorized, True)
authorized, True, theme)
tlStr += ' <td valign="top" class="col-right">' + \
rightColumnStr + ' </td>\n'
tlStr += ' </tr>\n'
@ -705,7 +720,7 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the shares timeline as html
"""
manuallyApproveFollowers = \
@ -724,7 +739,7 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlInbox(cssCache: {}, defaultTimeline: str,
@ -742,7 +757,7 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the inbox as html
"""
manuallyApproveFollowers = \
@ -761,7 +776,7 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlBookmarks(cssCache: {}, defaultTimeline: str,
@ -779,7 +794,7 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the bookmarks as html
"""
manuallyApproveFollowers = \
@ -798,7 +813,7 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlEvents(cssCache: {}, defaultTimeline: str,
@ -816,7 +831,7 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the events as html
"""
manuallyApproveFollowers = \
@ -835,7 +850,7 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
@ -853,7 +868,7 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the DM timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -867,7 +882,7 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
@ -885,7 +900,7 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the replies timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -900,7 +915,7 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
@ -918,7 +933,7 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the media timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -933,7 +948,7 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
@ -951,7 +966,7 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the blogs timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -966,7 +981,7 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
@ -984,7 +999,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool,
theme: str) -> str:
"""Show the features timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -999,7 +1015,7 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlInboxNews(cssCache: {}, defaultTimeline: str,
@ -1017,7 +1033,7 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the news timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1032,7 +1048,7 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None)
authorized, None, theme)
def htmlOutbox(cssCache: {}, defaultTimeline: str,
@ -1050,7 +1066,7 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
authorized: bool, theme: str) -> str:
"""Show the Outbox as html
"""
manuallyApproveFollowers = \
@ -1066,4 +1082,4 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
newswire, False, False, positiveVoting,
showPublishAsIcon, fullWidthTimelineButtonHeader,
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,
nickname: str, domain: str) -> (str, str):
nickname: str, domain: str, theme: str) -> (str, str):
"""
returns the filenames for an image with the given name
"""
@ -484,39 +484,41 @@ def getImageFile(baseDir: str, name: str, directory: str,
bannerFile = ''
bannerFilename = ''
for ext in bannerExtensions:
bannerFile = name + '.' + ext
bannerFilename = directory + '/' + bannerFile
if os.path.isfile(bannerFilename):
bannerFileTest = name + '.' + ext
bannerFilenameTest = directory + '/' + bannerFileTest
if os.path.isfile(bannerFilenameTest):
bannerFile = name + '_' + theme + '.' + ext
bannerFilename = bannerFilenameTest
break
return bannerFile, bannerFilename
def getBannerFile(baseDir: str,
nickname: str, domain: str) -> (str, str):
nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'banner',
baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain)
nickname, domain, theme)
def getSearchBannerFile(baseDir: str,
nickname: str, domain: str) -> (str, str):
nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'search_banner',
baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain)
nickname, domain, theme)
def getLeftImageFile(baseDir: str,
nickname: str, domain: str) -> (str, str):
nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'left_col_image',
baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain)
nickname, domain, theme)
def getRightImageFile(baseDir: str,
nickname: str, domain: str) -> (str, str):
nickname: str, domain: str, theme: str) -> (str, str):
return getImageFile(baseDir, 'right_col_image',
baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain)
nickname, domain, theme)
def htmlHeaderWithExternalStyle(cssFilename: str, lang='en') -> str:
@ -850,3 +852,29 @@ def htmlHighlightLabel(label: str, highlight: bool) -> str:
if not highlight:
return 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,
cachedWebfingers)
if wf:
print('Webfinger from cache: ' + str(wf))
return wf
url = '{}://{}/.well-known/webfinger'.format(httpPrefix, domain)
par = {