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 += \ '' + \ ' 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 += \ '' + \ ' 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' @@ -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 += ' \n' profileFooterStr += ' \n' profileFooterStr += ' \n' diff --git a/webapp_hashtagswarm.py b/webapp_hashtagswarm.py index 3dd92f54c..2a2cb52ac 100644 --- a/webapp_hashtagswarm.py +++ b/webapp_hashtagswarm.py @@ -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 += '\n' diff --git a/webapp_moderation.py b/webapp_moderation.py index 63889536b..51e38e3dd 100644 --- a/webapp_moderation.py +++ b/webapp_moderation.py @@ -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: {}, '
' 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 += '
' + translate['Show Accounts'] + infoForm += '\n' + infoForm += '
\n' + infoForm += '\n' + infoForm += ' \n' + for col in range(cols): + infoForm += ' \n' + infoForm += ' \n' + infoForm += '\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 += '\n' + col += 1 + if col == cols: + # new row of accounts + infoForm += '\n\n' + infoForm += '\n
\n' + infoForm += '' + infoForm += '
' + if isModerator(baseDir, acctNickname): + infoForm += '' + acctNickname + '' + else: + infoForm += acctNickname + if isEditor(baseDir, acctNickname): + infoForm += ' ✍' + infoForm += '
\n
\n' + infoForm += '
\n' + if len(accounts) > 10: + infoForm += '
\n' + suspendedFilename = baseDir + '/accounts/suspended.txt' if os.path.isfile(suspendedFilename): with open(suspendedFilename, "r") as f: suspendedStr = f.read() - infoForm += '
' + infoForm += '
\n' infoForm += '
' + \ translate['Suspended accounts'] + '' infoForm += '
' + \ @@ -164,15 +230,15 @@ def htmlModerationInfo(cssCache: {}, translate: {}, infoForm += \ ' ' - infoForm += '
' + suspendedStr + '\n' + infoForm += '
\n' infoShown = True blockingFilename = baseDir + '/accounts/blocking.txt' if os.path.isfile(blockingFilename): with open(blockingFilename, "r") as f: blockedStr = f.read() - infoForm += '
' + infoForm += '
\n' infoForm += \ '
' + \ translate['Blocked accounts and hashtags'] + '' @@ -182,13 +248,29 @@ def htmlModerationInfo(cssCache: {}, translate: {}, infoForm += \ ' ' - infoForm += '
' + blockedStr + '\n' + infoForm += '
\n' infoShown = True + + filtersFilename = baseDir + '/accounts/filters.txt' + if os.path.isfile(filtersFilename): + with open(filtersFilename, "r") as f: + filteredStr = f.read() + infoForm += '
\n' + infoForm += \ + '
' + \ + translate['Filtered words'] + '' + infoForm += \ + ' \n' + infoForm += '
\n' + infoShown = True + if not infoShown: infoForm += \ '

' + \ translate[msgStr2] + \ - '

' + '

\n' infoForm += htmlFooter() return infoForm diff --git a/webapp_person_options.py b/webapp_person_options.py index 4d845eb2d..77f1d26f8 100644 --- a/webapp_person_options.py +++ b/webapp_person_options.py @@ -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 += \ '