diff --git a/announce.py b/announce.py
index 386a63866..45be9351b 100644
--- a/announce.py
+++ b/announce.py
@@ -369,7 +369,7 @@ def sendAnnounceViaServer(baseDir: str, session,
personCache,
projectVersion, httpPrefix,
fromNickname, fromDomain,
- postToBox)
+ postToBox, 73528)
if not inboxUrl:
if debug:
diff --git a/availability.py b/availability.py
index b7739e86e..1dc7b03cf 100644
--- a/availability.py
+++ b/availability.py
@@ -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:
diff --git a/bookmarks.py b/bookmarks.py
index c0ef808a1..75eeac47c 100644
--- a/bookmarks.py
+++ b/bookmarks.py
@@ -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:
diff --git a/daemon.py b/daemon.py
index 6c76b5a2e..d20f5ae73 100644
--- a/daemon.py
+++ b/daemon.py
@@ -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,
diff --git a/delete.py b/delete.py
index 8302da1ed..524004d8b 100644
--- a/delete.py
+++ b/delete.py
@@ -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:
diff --git a/epicyon-options.css b/epicyon-options.css
index 1675d65da..aaff9989b 100644
--- a/epicyon-options.css
+++ b/epicyon-options.css
@@ -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);
}
diff --git a/epicyon-profile.css b/epicyon-profile.css
index 2e9436d3d..dbe620ade 100644
--- a/epicyon-profile.css
+++ b/epicyon-profile.css
@@ -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);
diff --git a/epicyon.py b/epicyon.py
index ba8eb64b3..8b243c66e 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -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,
diff --git a/filters.py b/filters.py
index 744e28775..dc726ff55 100644
--- a/filters.py
+++ b/filters.py
@@ -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)
diff --git a/follow.py b/follow.py
index 58c1dfb6d..441d02fdf 100644
--- a/follow.py
+++ b/follow.py
@@ -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:
diff --git a/inbox.py b/inbox.py
index 9a2536837..7c169f55e 100644
--- a/inbox.py
+++ b/inbox.py
@@ -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)
diff --git a/like.py b/like.py
index 6076c7299..7f59e6167 100644
--- a/like.py
+++ b/like.py
@@ -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:
diff --git a/newsdaemon.py b/newsdaemon.py
index 42a69994b..c89ae24ef 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -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)
diff --git a/newswire.py b/newswire.py
index 9b60041b3..88c71fb28 100644
--- a/newswire.py
+++ b/newswire.py
@@ -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:
diff --git a/person.py b/person.py
index 9fcf099d2..406700b29 100644
--- a/person.py
+++ b/person.py
@@ -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'
}
diff --git a/posts.py b/posts.py
index aefd0564c..6f22d6292 100644
--- a/posts.py
+++ b/posts.py
@@ -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:
diff --git a/roles.py b/roles.py
index ecbf237b8..a03a34ba7 100644
--- a/roles.py
+++ b/roles.py
@@ -316,7 +316,8 @@ def sendRoleViaServer(baseDir: str, session,
wfRequest, personCache,
projectVersion, httpPrefix,
delegatorNickname,
- delegatorDomain, postToBox)
+ delegatorDomain, postToBox,
+ 765672)
if not inboxUrl:
if debug:
diff --git a/schedule.py b/schedule.py
index c213c4ec5..29808ca23 100644
--- a/schedule.py
+++ b/schedule.py
@@ -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)
diff --git a/scripts/checkupdate b/scripts/checkupdate
new file mode 100755
index 000000000..278a76fb1
--- /dev/null
+++ b/scripts/checkupdate
@@ -0,0 +1,2 @@
+#!/bin/bash
+journalctl -u epicyon -r | grep "Sending profile update to\|a shared inbox"
\ No newline at end of file
diff --git a/scripts/sending b/scripts/sending
new file mode 100755
index 000000000..795033429
--- /dev/null
+++ b/scripts/sending
@@ -0,0 +1,2 @@
+#!/bin/bash
+journalctl -u epicyon -r | grep "Sending post to followers"
\ No newline at end of file
diff --git a/session.py b/session.py
index 96b6de026..f740c4759 100644
--- a/session.py
+++ b/session.py
@@ -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
diff --git a/shares.py b/shares.py
index 1926395a4..cb0867cba 100644
--- a/shares.py
+++ b/shares.py
@@ -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:
diff --git a/skills.py b/skills.py
index 2b4930ff2..2637acd78 100644
--- a/skills.py
+++ b/skills.py
@@ -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:
diff --git a/socnet.py b/socnet.py
index 31c59eb88..36b5c7cb4 100644
--- a/socnet.py
+++ b/socnet.py
@@ -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,
diff --git a/tests.py b/tests.py
index 654f977be..ca0d1bbc2 100644
--- a/tests.py
+++ b/tests.py
@@ -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)
diff --git a/theme/debian/theme.json b/theme/debian/theme.json
index a373ed144..126109ff0 100644
--- a/theme/debian/theme.json
+++ b/theme/debian/theme.json
@@ -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",
diff --git a/theme/hacker/theme.json b/theme/hacker/theme.json
index 68551095e..bda1565e9 100644
--- a/theme/hacker/theme.json
+++ b/theme/hacker/theme.json
@@ -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",
diff --git a/theme/henge/theme.json b/theme/henge/theme.json
index 450e9a2e0..b8685b1ef 100644
--- a/theme/henge/theme.json
+++ b/theme/henge/theme.json
@@ -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",
diff --git a/theme/indymediaclassic/theme.json b/theme/indymediaclassic/theme.json
index b56890f30..32621e0a3 100644
--- a/theme/indymediaclassic/theme.json
+++ b/theme/indymediaclassic/theme.json
@@ -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",
diff --git a/theme/indymediamodern/theme.json b/theme/indymediamodern/theme.json
index 228197afc..48a92eeed 100644
--- a/theme/indymediamodern/theme.json
+++ b/theme/indymediamodern/theme.json
@@ -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",
diff --git a/theme/lcd/theme.json b/theme/lcd/theme.json
index 68395282b..f0062ce12 100644
--- a/theme/lcd/theme.json
+++ b/theme/lcd/theme.json
@@ -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",
diff --git a/theme/light/theme.json b/theme/light/theme.json
index cea6c4389..c6a212772 100644
--- a/theme/light/theme.json
+++ b/theme/light/theme.json
@@ -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",
diff --git a/theme/night/theme.json b/theme/night/theme.json
index b7cddde8c..8e93fa9d7 100644
--- a/theme/night/theme.json
+++ b/theme/night/theme.json
@@ -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",
diff --git a/theme/purple/theme.json b/theme/purple/theme.json
index 2f500c7fc..710fd3b04 100644
--- a/theme/purple/theme.json
+++ b/theme/purple/theme.json
@@ -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",
diff --git a/theme/rc3/theme.json b/theme/rc3/theme.json
index 1ab809f18..46df93ea7 100644
--- a/theme/rc3/theme.json
+++ b/theme/rc3/theme.json
@@ -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",
diff --git a/theme/solidaric/theme.json b/theme/solidaric/theme.json
index 58d0ee155..3031117e1 100644
--- a/theme/solidaric/theme.json
+++ b/theme/solidaric/theme.json
@@ -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",
diff --git a/theme/starlight/theme.json b/theme/starlight/theme.json
index 450aa35a0..e05fca0a7 100644
--- a/theme/starlight/theme.json
+++ b/theme/starlight/theme.json
@@ -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",
diff --git a/theme/zen/theme.json b/theme/zen/theme.json
index ff229fea0..bd63fcac9 100644
--- a/theme/zen/theme.json
+++ b/theme/zen/theme.json
@@ -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",
diff --git a/threads.py b/threads.py
index cdb3cb95d..89a43a1eb 100644
--- a/threads.py
+++ b/threads.py
@@ -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
diff --git a/translations/ar.json b/translations/ar.json
index 1afa3ef36..630067626 100644
--- a/translations/ar.json
+++ b/translations/ar.json
@@ -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": "إظهار الحسابات"
}
diff --git a/translations/ca.json b/translations/ca.json
index 257b46953..853f94914 100644
--- a/translations/ca.json
+++ b/translations/ca.json
@@ -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"
}
diff --git a/translations/cy.json b/translations/cy.json
index dd3f5b920..eafc25d65 100644
--- a/translations/cy.json
+++ b/translations/cy.json
@@ -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"
}
diff --git a/translations/de.json b/translations/de.json
index 67d572256..6749671f9 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -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"
}
diff --git a/translations/en.json b/translations/en.json
index 9453645d0..7dd037eb0 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -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"
}
diff --git a/translations/es.json b/translations/es.json
index 75c5f3e6a..dcf167a97 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -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"
}
diff --git a/translations/fr.json b/translations/fr.json
index 8b37aa74c..3d8dc2b55 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -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"
}
diff --git a/translations/ga.json b/translations/ga.json
index 9f1825bf5..a4e46b1f0 100644
--- a/translations/ga.json
+++ b/translations/ga.json
@@ -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"
}
diff --git a/translations/hi.json b/translations/hi.json
index feac20689..49f6d60e2 100644
--- a/translations/hi.json
+++ b/translations/hi.json
@@ -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": "खाते दिखाएं"
}
diff --git a/translations/it.json b/translations/it.json
index 735ecbdab..bd4b3db9c 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -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"
}
diff --git a/translations/ja.json b/translations/ja.json
index 6cb030885..e2029f181 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -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": "アカウントを表示する"
}
diff --git a/translations/oc.json b/translations/oc.json
index d8e91c995..dc474d00d 100644
--- a/translations/oc.json
+++ b/translations/oc.json
@@ -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"
}
diff --git a/translations/pt.json b/translations/pt.json
index 93d13383e..b4cb1996a 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -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"
}
diff --git a/translations/ru.json b/translations/ru.json
index 80c171d44..a95083395 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -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": "Показать счета"
}
diff --git a/translations/zh.json b/translations/zh.json
index 31c9e9954..36ea2b053 100644
--- a/translations/zh.json
+++ b/translations/zh.json
@@ -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": "显示帐户"
}
diff --git a/webapp_column_left.py b/webapp_column_left.py
index 5d3d8b0d3..f8d0d826b 100644
--- a/webapp_column_left.py
+++ b/webapp_column_left.py
@@ -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 += \
'' + \
'
\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)
diff --git a/webapp_column_right.py b/webapp_column_right.py
index 6bc29bdda..450d2034a 100644
--- a/webapp_column_right.py
+++ b/webapp_column_right.py
@@ -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 += \
'' + \
'
\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)
diff --git a/webapp_create_post.py b/webapp_create_post.py
index 6681aa303..048548c83 100644
--- a/webapp_create_post.py
+++ b/webapp_create_post.py
@@ -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'):
diff --git a/webapp_frontscreen.py b/webapp_frontscreen.py
index 6589b78cc..621b1cb23 100644
--- a/webapp_frontscreen.py
+++ b/webapp_frontscreen.py
@@ -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 = \
'\n'
@@ -124,7 +125,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
getLeftColumnContent(baseDir, 'news', domainFull,
httpPrefix, translate,
False, False, None, rssIconAtTop, True,
- True)
+ True, theme)
profileHeaderStr += ' \n'
profileHeaderStr += '
\n'
+ infoForm += ' | \n'
+ col += 1
+ if col == cols:
+ # new row of accounts
+ infoForm += '
' + \ translate[msgStr2] + \ - '
', '').replace('
', '') + 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 += \ '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 += '