diff --git a/blog.py b/blog.py index 9235b8e22..f2651b8db 100644 --- a/blog.py +++ b/blog.py @@ -619,7 +619,9 @@ def _noOfBlogAccounts(baseDir: str) -> int: for acct in dirs: if '@' not in acct: continue - if 'inbox@' in acct: + if acct.startswith('inbox@'): + continue + elif acct.startswith('news@'): continue accountDir = os.path.join(baseDir + '/accounts', acct) blogsIndex = accountDir + '/tlblogs.index' @@ -636,7 +638,9 @@ def _singleBlogAccountNickname(baseDir: str) -> str: for acct in dirs: if '@' not in acct: continue - if 'inbox@' in acct: + if acct.startswith('inbox@'): + continue + elif acct.startswith('news@'): continue accountDir = os.path.join(baseDir + '/accounts', acct) blogsIndex = accountDir + '/tlblogs.index' @@ -676,7 +680,9 @@ def htmlBlogView(authorized: bool, for acct in dirs: if '@' not in acct: continue - if 'inbox@' in acct: + if acct.startswith('inbox@'): + continue + elif acct.startswith('news@'): continue accountDir = os.path.join(baseDir + '/accounts', acct) blogsIndex = accountDir + '/tlblogs.index' diff --git a/code-of-conduct.md b/code-of-conduct.md index 01c77166c..73e4b2a1e 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -26,6 +26,12 @@ No stalking, unwanted personal attention, or unwelcome revealing or speculating In cases of sincere, good-faith curiosity about someone’s experience or identity, ask politely in a manner such that they will feel free to decline the request. +## No non-consenting research + +People contributing to, or maintaining, this project should not be treated as research subjects in academic studies without their prior written consent. If anthropological, security, or other types of research are being conducted upon contributors then they must be made aware of this and formally agree to it taking place. + +Publishing software under an AGPL license does not imply consent to become a research subject. + ## No hostile communication No insults, harassment (sexual or otherwise), condescension, ad hominem, threats, or other intimidation. Claims that such communications were intended as "ironic" or humerous will also be considered a code of conduct violation. diff --git a/daemon.py b/daemon.py index 701c49a1d..668ad4236 100644 --- a/daemon.py +++ b/daemon.py @@ -134,6 +134,8 @@ from webapp_utils import getBlogAddress from webapp_calendar import htmlCalendarDeleteConfirm from webapp_calendar import htmlCalendar from webapp_about import htmlAbout +from webapp_accesskeys import htmlAccessKeys +from webapp_accesskeys import loadAccessKeysForAccounts from webapp_confirm import htmlConfirmDelete from webapp_confirm import htmlConfirmRemoveSharedItem from webapp_confirm import htmlConfirmUnblock @@ -1800,6 +1802,93 @@ class PubServer(BaseHTTPRequestHandler): self.server.POSTbusy = False return + def _keyShortcuts(self, path: str, + callingDomain: str, cookie: str, + baseDir: str, httpPrefix: str, nickname: str, + domain: str, domainFull: str, port: int, + onionDomain: str, i2pDomain: str, + debug: bool, accessKeys: {}, + defaultTimeline: str) -> None: + """Receive POST from webapp_accesskeys + """ + usersPath = '/users/' + nickname + originPathStr = \ + httpPrefix + '://' + domainFull + usersPath + '/' + defaultTimeline + length = int(self.headers['Content-length']) + + try: + accessKeysParams = self.rfile.read(length).decode('utf-8') + except SocketError as e: + if e.errno == errno.ECONNRESET: + print('WARN: POST accessKeysParams ' + + 'connection reset by peer') + else: + print('WARN: POST accessKeysParams socket error') + self.send_response(400) + self.end_headers() + self.server.POSTbusy = False + return + except ValueError as e: + print('ERROR: POST accessKeysParams rfile.read failed') + print(e) + self.send_response(400) + self.end_headers() + self.server.POSTbusy = False + return + accessKeysParams = \ + urllib.parse.unquote_plus(accessKeysParams) + + # key shortcuts screen, back button + # See htmlAccessKeys + if 'submitAccessKeysCancel=' in accessKeysParams or \ + 'submitAccessKeys=' not in accessKeysParams: + if callingDomain.endswith('.onion') and onionDomain: + originPathStr = \ + 'http://' + onionDomain + usersPath + '/' + defaultTimeline + elif callingDomain.endswith('.i2p') and i2pDomain: + originPathStr = \ + 'http://' + i2pDomain + usersPath + '/' + defaultTimeline + self._redirect_headers(originPathStr, cookie, callingDomain) + self.server.POSTbusy = False + return + + saveKeys = False + accessKeysTemplate = self.server.accessKeys + for variableName, key in accessKeysTemplate.items(): + if not accessKeys.get(variableName): + accessKeys[variableName] = accessKeysTemplate[variableName] + + variableName2 = variableName.replace(' ', '_') + if variableName2 + '=' in accessKeysParams: + newKey = accessKeysParams.split(variableName2 + '=')[1] + if '&' in newKey: + newKey = newKey.split('&')[0] + if newKey: + if len(newKey) > 1: + newKey = newKey[0] + if newKey != accessKeys[variableName]: + accessKeys[variableName] = newKey + saveKeys = True + + if saveKeys: + accessKeysFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + \ + '/accessKeys.json' + saveJson(accessKeys, accessKeysFilename) + if not self.server.keyShortcuts.get(nickname): + self.server.keyShortcuts[nickname] = accessKeys.copy() + + # redirect back from key shortcuts screen + if callingDomain.endswith('.onion') and onionDomain: + originPathStr = \ + 'http://' + onionDomain + usersPath + '/' + defaultTimeline + elif callingDomain.endswith('.i2p') and i2pDomain: + originPathStr = \ + 'http://' + i2pDomain + usersPath + '/' + defaultTimeline + self._redirect_headers(originPathStr, cookie, callingDomain) + self.server.POSTbusy = False + return + def _personOptions(self, path: str, callingDomain: str, cookie: str, baseDir: str, httpPrefix: str, @@ -2168,6 +2257,15 @@ class PubServer(BaseHTTPRequestHandler): if debug: print('Sending DM to ' + optionsActor) reportPath = path.replace('/personoptions', '') + '/newdm' + + accessKeys = self.server.accessKeys + if '/users/' in path: + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + msg = htmlNewPost(self.server.cssCache, False, self.server.translate, baseDir, @@ -2181,7 +2279,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.defaultTimeline, self.server.newswire, self.server.themeName, - True).encode('utf-8') + True, accessKeys).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) @@ -2268,6 +2366,15 @@ class PubServer(BaseHTTPRequestHandler): print('Reporting ' + optionsActor) reportPath = \ path.replace('/personoptions', '') + '/newreport' + + accessKeys = self.server.accessKeys + if '/users/' in path: + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + msg = htmlNewPost(self.server.cssCache, False, self.server.translate, baseDir, @@ -2280,7 +2387,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.defaultTimeline, self.server.newswire, self.server.themeName, - True).encode('utf-8') + True, accessKeys).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) @@ -2843,6 +2950,11 @@ class PubServer(BaseHTTPRequestHandler): showPublishedDateOnly = self.server.showPublishedDateOnly allowLocalNetworkAccess = \ self.server.allowLocalNetworkAccess + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + profileStr = \ htmlProfileAfterSearch(self.server.cssCache, self.server.recentPostsCache, @@ -2865,7 +2977,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.defaultTimeline, self.server.peertubeInstances, allowLocalNetworkAccess, - self.server.themeName) + self.server.themeName, + accessKeys) if profileStr: msg = profileStr.encode('utf-8') msglen = len(msg) @@ -5019,11 +5132,24 @@ class PubServer(BaseHTTPRequestHandler): if os.path.isfile(blockedFilename): os.remove(blockedFilename) + # Save DM allowed instances list. + # The allow list for incoming DMs, + # if the .followDMs flag file exists + dmAllowedInstancesFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/dmAllowedinstances.txt' + if fields.get('dmAllowedInstances'): + with open(dmAllowedInstancesFilename, 'w+') as aFile: + aFile.write(fields['dmAllowedInstances']) + else: + if os.path.isfile(dmAllowedInstancesFilename): + os.remove(dmAllowedInstancesFilename) + # save allowed instances list + # This is the account level allow list allowedInstancesFilename = \ baseDir + '/accounts/' + \ - nickname + '@' + domain + \ - '/allowedinstances.txt' + nickname + '@' + domain + '/allowedinstances.txt' if fields.get('allowedInstances'): with open(allowedInstancesFilename, 'w+') as aFile: aFile.write(fields['allowedInstances']) @@ -5703,6 +5829,13 @@ class PubServer(BaseHTTPRequestHandler): optionsActor, optionsProfileUrl, self.server.personCache, 5) + accessKeys = self.server.accessKeys + if '/users/' in path: + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] msg = htmlPersonOptions(self.server.defaultTimeline, self.server.cssCache, self.server.translate, @@ -5725,7 +5858,8 @@ class PubServer(BaseHTTPRequestHandler): movedTo, alsoKnownAs, self.server.textModeBanner, self.server.newsInstance, - authorized).encode('utf-8') + authorized, + accessKeys).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) @@ -7329,6 +7463,11 @@ class PubServer(BaseHTTPRequestHandler): self.server.YTReplacementDomain iconsAsButtons = \ self.server.iconsAsButtons + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + msg = \ htmlProfile(self.server.rssIconAtTop, self.server.cssCache, @@ -7352,6 +7491,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowLocalNetworkAccess, self.server.textModeBanner, self.server.debug, + accessKeys, actorJson['roles'], None, None) msg = msg.encode('utf-8') @@ -7418,6 +7558,10 @@ class PubServer(BaseHTTPRequestHandler): self.server.iconsAsButtons allowLocalNetworkAccess = \ self.server.allowLocalNetworkAccess + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] msg = \ htmlProfile(self.server.rssIconAtTop, self.server.cssCache, @@ -7441,6 +7585,7 @@ class PubServer(BaseHTTPRequestHandler): allowLocalNetworkAccess, self.server.textModeBanner, self.server.debug, + accessKeys, actorJson['skills'], None, None) msg = msg.encode('utf-8') @@ -7808,6 +7953,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = htmlInbox(self.server.cssCache, defaultTimeline, recentPostsCache, @@ -7839,7 +7990,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) if GETstartTime: self._benchmarkGETtimings(GETstartTime, GETtimings, 'show status done', @@ -7937,6 +8089,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlInboxDMs(self.server.cssCache, self.server.defaultTimeline, @@ -7968,7 +8126,8 @@ class PubServer(BaseHTTPRequestHandler): authorized, self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8059,6 +8218,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlInboxReplies(self.server.cssCache, self.server.defaultTimeline, @@ -8090,7 +8255,8 @@ class PubServer(BaseHTTPRequestHandler): authorized, self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8181,6 +8347,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlInboxMedia(self.server.cssCache, self.server.defaultTimeline, @@ -8213,7 +8385,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8304,6 +8477,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlInboxBlogs(self.server.cssCache, self.server.defaultTimeline, @@ -8336,7 +8515,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8435,6 +8615,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlInboxNews(self.server.cssCache, self.server.defaultTimeline, @@ -8468,7 +8654,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8564,6 +8751,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlInboxFeatures(self.server.cssCache, self.server.defaultTimeline, @@ -8596,7 +8789,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8656,6 +8850,12 @@ class PubServer(BaseHTTPRequestHandler): pageNumber = int(pageNumber) else: pageNumber = 1 + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlShares(self.server.cssCache, self.server.defaultTimeline, @@ -8685,7 +8885,8 @@ class PubServer(BaseHTTPRequestHandler): authorized, self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8759,6 +8960,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlBookmarks(self.server.cssCache, self.server.defaultTimeline, @@ -8791,7 +8998,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -8885,6 +9093,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlEvents(self.server.cssCache, self.server.defaultTimeline, @@ -8917,7 +9131,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -9003,6 +9218,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader minimalNick = self._isMinimal(nickname) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlOutbox(self.server.cssCache, self.server.defaultTimeline, @@ -9035,7 +9256,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -9112,6 +9334,12 @@ class PubServer(BaseHTTPRequestHandler): fullWidthTimelineButtonHeader = \ self.server.fullWidthTimelineButtonHeader moderationActionStr = '' + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlModeration(self.server.cssCache, self.server.defaultTimeline, @@ -9143,7 +9371,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.themeName, self.server.peertubeInstances, self.server.allowLocalNetworkAccess, - self.server.textModeBanner) + self.server.textModeBanner, + accessKeys) msg = msg.encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -9222,6 +9451,16 @@ class PubServer(BaseHTTPRequestHandler): self._404() self.server.GETbusy = False return True + + accessKeys = self.server.accessKeys + if '/users/' in path: + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlProfile(self.server.rssIconAtTop, self.server.cssCache, @@ -9246,6 +9485,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowLocalNetworkAccess, self.server.textModeBanner, self.server.debug, + accessKeys, shares, pageNumber, sharesPerPage) msg = msg.encode('utf-8') @@ -9322,6 +9562,15 @@ class PubServer(BaseHTTPRequestHandler): self.server.GETbusy = False return True + accessKeys = self.server.accessKeys + if '/users/' in path: + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlProfile(self.server.rssIconAtTop, self.server.cssCache, @@ -9346,6 +9595,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowLocalNetworkAccess, self.server.textModeBanner, self.server.debug, + accessKeys, following, pageNumber, followsPerPage).encode('utf-8') @@ -9420,6 +9670,16 @@ class PubServer(BaseHTTPRequestHandler): self._404() self.server.GETbusy = False return True + + accessKeys = self.server.accessKeys + if '/users/' in path: + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlProfile(self.server.rssIconAtTop, self.server.cssCache, @@ -9445,6 +9705,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowLocalNetworkAccess, self.server.textModeBanner, self.server.debug, + accessKeys, followers, pageNumber, followsPerPage).encode('utf-8') @@ -9542,6 +9803,16 @@ class PubServer(BaseHTTPRequestHandler): self._404() self.server.GETbusy = False return True + + accessKeys = self.server.accessKeys + if '/users/' in path: + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + msg = \ htmlProfile(self.server.rssIconAtTop, self.server.cssCache, @@ -9567,6 +9838,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowLocalNetworkAccess, self.server.textModeBanner, self.server.debug, + accessKeys, None, None).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, @@ -10091,6 +10363,11 @@ class PubServer(BaseHTTPRequestHandler): break if isNewPostEndpoint: nickname = getNicknameFromActor(path) + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + msg = htmlNewPost(self.server.cssCache, mediaInstance, translate, @@ -10105,7 +10382,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.defaultTimeline, self.server.newswire, self.server.themeName, - noDropDown).encode('utf-8') + noDropDown, accessKeys).encode('utf-8') if not msg: print('Error replying to ' + inReplyToUrl) self._404() @@ -10158,6 +10435,14 @@ class PubServer(BaseHTTPRequestHandler): """Show the links from the left column """ if '/users/' in path and path.endswith('/editlinks'): + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + msg = htmlEditLinks(self.server.cssCache, translate, baseDir, @@ -10165,7 +10450,7 @@ class PubServer(BaseHTTPRequestHandler): port, httpPrefix, self.server.defaultTimeline, - theme).encode('utf-8') + theme, accessKeys).encode('utf-8') if msg: msglen = len(msg) self._set_headers('text/html', msglen, @@ -10184,6 +10469,14 @@ class PubServer(BaseHTTPRequestHandler): """Show the newswire from the right column """ if '/users/' in path and path.endswith('/editnewswire'): + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + msg = htmlEditNewswire(self.server.cssCache, translate, baseDir, @@ -10191,7 +10484,8 @@ class PubServer(BaseHTTPRequestHandler): port, httpPrefix, self.server.defaultTimeline, - self.server.themeName).encode('utf-8') + self.server.themeName, + accessKeys).encode('utf-8') if msg: msglen = len(msg) self._set_headers('text/html', msglen, @@ -10984,6 +11278,34 @@ class PubServer(BaseHTTPRequestHandler): 'show about screen') return + if htmlGET and usersInPath and authorized and \ + self.path.endswith('/accesskeys'): + nickname = self.path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = \ + self.server.keyShortcuts[nickname] + + msg = \ + htmlAccessKeys(self.server.cssCache, + self.server.baseDir, + nickname, self.server.domain, + self.server.translate, + accessKeys, + self.server.accessKeys, + self.server.defaultTimeline) + msg = msg.encode('utf-8') + msglen = len(msg) + self._login_headers('text/html', msglen, callingDomain) + self._write(msg) + self._benchmarkGETtimings(GETstartTime, GETtimings, + 'following accounts done', + 'show accesskeys screen') + return + self._benchmarkGETtimings(GETstartTime, GETtimings, 'following accounts done', 'show about screen done') @@ -11465,6 +11787,9 @@ class PubServer(BaseHTTPRequestHandler): rssIconAtTop = self.server.rssIconAtTop iconsAsButtons = self.server.iconsAsButtons defaultTimeline = self.server.defaultTimeline + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] msg = htmlNewswireMobile(self.server.cssCache, self.server.baseDir, nickname, @@ -11480,7 +11805,8 @@ class PubServer(BaseHTTPRequestHandler): rssIconAtTop, iconsAsButtons, defaultTimeline, - self.server.themeName).encode('utf-8') + self.server.themeName, + accessKeys).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) @@ -11499,6 +11825,9 @@ class PubServer(BaseHTTPRequestHandler): self._404() self.server.GETbusy = False return + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] timelinePath = \ '/users/' + nickname + '/' + self.server.defaultTimeline iconsAsButtons = self.server.iconsAsButtons @@ -11513,7 +11842,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.rssIconAtTop, iconsAsButtons, defaultTimeline, - self.server.themeName).encode('utf-8') + self.server.themeName, + accessKeys).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) self._write(msg) @@ -11577,6 +11907,15 @@ class PubServer(BaseHTTPRequestHandler): '/search?' in self.path: if '?' in self.path: self.path = self.path.split('?')[0] + + nickname = self.path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + # show the search screen msg = htmlSearch(self.server.cssCache, self.server.translate, @@ -11584,7 +11923,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.domain, self.server.defaultTimeline, self.server.themeName, - self.server.textModeBanner).encode('utf-8') + self.server.textModeBanner, + accessKeys).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) self._write(msg) @@ -11619,6 +11959,14 @@ class PubServer(BaseHTTPRequestHandler): # Show the calendar for a user if htmlGET and usersInPath: if '/calendar' in self.path: + nickname = self.path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + + accessKeys = self.server.accessKeys + if self.server.keyShortcuts.get(nickname): + accessKeys = self.server.keyShortcuts[nickname] + # show the calendar screen msg = htmlCalendar(self.server.personCache, self.server.cssCache, @@ -11626,7 +11974,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.baseDir, self.path, self.server.httpPrefix, self.server.domainFull, - self.server.textModeBanner).encode('utf-8') + self.server.textModeBanner, + accessKeys).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) self._write(msg) @@ -13987,6 +14336,33 @@ class PubServer(BaseHTTPRequestHandler): self.server.debug) return + # Change the key shortcuts + if usersInPath and \ + self.path.endswith('/changeAccessKeys'): + nickname = self.path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + + if not self.server.keyShortcuts.get(nickname): + accessKeys = self.server.accessKeys + self.server.keyShortcuts[nickname] = accessKeys.copy() + accessKeys = self.server.keyShortcuts[nickname] + + self._keyShortcuts(self.path, + callingDomain, cookie, + self.server.baseDir, + self.server.httpPrefix, + nickname, + self.server.domain, + self.server.domainFull, + self.server.port, + self.server.onionDomain, + self.server.i2pDomain, + self.server.debug, + accessKeys, + self.server.defaultTimeline) + return + self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 14) # receive different types of post created by htmlNewPost @@ -14464,6 +14840,48 @@ def runDaemon(brochMode: bool, # ASCII/ANSI text banner used in shell browsers, such as Lynx httpd.textModeBanner = getTextModeBanner(baseDir) + # key shortcuts SHIFT + ALT + [key] + httpd.accessKeys = { + 'Page up': ',', + 'Page down': '.', + 'submitButton': 'y', + 'followButton': 'f', + 'blockButton': 'b', + 'infoButton': 'i', + 'snoozeButton': 's', + 'reportButton': '[', + 'viewButton': 'v', + 'enterPetname': 'p', + 'enterNotes': 'n', + 'menuTimeline': 't', + 'menuEdit': 'e', + 'menuProfile': 'p', + 'menuInbox': 'i', + 'menuSearch': '/', + 'menuNewPost': 'n', + 'menuCalendar': 'c', + 'menuDM': 'd', + 'menuReplies': 'r', + 'menuOutbox': 's', + 'menuBookmarks': 'q', + 'menuShares': 'h', + 'menuBlogs': 'b', + 'menuNewswire': 'w', + 'menuLinks': 'l', + 'menuMedia': 'm', + 'menuModeration': 'o', + 'menuFollowing': 'f', + 'menuFollowers': 'g', + 'menuRoles': 'o', + 'menuSkills': 'a', + 'menuLogout': 'x', + 'menuKeys': 'k', + 'Public': 'p', + 'Reminder': 'r' + } + httpd.keyShortcuts = {} + loadAccessKeysForAccounts(baseDir, httpd.keyShortcuts, httpd.accessKeys) + httpd.unitTest = unitTest httpd.allowLocalNetworkAccess = allowLocalNetworkAccess if unitTest: diff --git a/epicyon-profile.css b/epicyon-profile.css index 8b2efc23a..29978a54f 100644 --- a/epicyon-profile.css +++ b/epicyon-profile.css @@ -197,6 +197,17 @@ figure { height: auto; } +.accesskeys { + border: 0; + width: 100%; +} +.accesskeys-left { + width: 90%; +} +.accesskeys-right { + width: 10%; +} + .cw { font-style: var(--cw-style); font-weight: var(--cw-weight); diff --git a/inbox.py b/inbox.py index 864e5a1ac..da64d8f54 100644 --- a/inbox.py +++ b/inbox.py @@ -11,6 +11,7 @@ import os import datetime import time from linked_data_sig import verifyJsonSignature +from utils import dmAllowedFromDomain from utils import isRecentPost from utils import getConfigParam from utils import hasUsersPath @@ -2466,28 +2467,32 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, if not isFollowingActor(baseDir, nickname, domain, sendH): - # send back a bounce DM - if postJsonObject.get('id') and \ - postJsonObject.get('object'): - # don't send bounces back to - # replies to bounce messages - obj = postJsonObject['object'] - if isinstance(obj, dict): - if not obj.get('inReplyTo'): - senderPostId = \ - postJsonObject['id'] - _bounceDM(senderPostId, - session, httpPrefix, - baseDir, - nickname, domain, - port, sendH, - federationList, - sendThreads, postLog, - cachedWebfingers, - personCache, - translate, debug, - lastBounceMessage) - return False + # DMs may always be allowed from some domains + if not dmAllowedFromDomain(baseDir, + nickname, domain, + sendingActorDomain): + # send back a bounce DM + if postJsonObject.get('id') and \ + postJsonObject.get('object'): + # don't send bounces back to + # replies to bounce messages + obj = postJsonObject['object'] + if isinstance(obj, dict): + if not obj.get('inReplyTo'): + senderPostId = \ + postJsonObject['id'] + _bounceDM(senderPostId, + session, httpPrefix, + baseDir, + nickname, domain, + port, sendH, + federationList, + sendThreads, postLog, + cachedWebfingers, + personCache, + translate, debug, + lastBounceMessage) + return False # dm index will be updated updateIndexList.append('dm') diff --git a/theme.py b/theme.py index 3a8d9c897..4aec87b13 100644 --- a/theme.py +++ b/theme.py @@ -551,7 +551,9 @@ def _setThemeImages(baseDir: str, name: str) -> None: for acct in dirs: if '@' not in acct: continue - if 'inbox@' in acct: + if acct.startswith('inbox@'): + continue + elif acct.startswith('news@'): continue accountDir = \ os.path.join(baseDir + '/accounts', acct) diff --git a/translations/ar.json b/translations/ar.json index 0f9847479..fbd426865 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -401,5 +401,41 @@ "counselor": "مستشار", "Counselors": "المستشارين", "shocked": "صدمت", - "Encrypted": "مشفر" + "Encrypted": "مشفر", + "Direct Message permitted instances": "الرسالة المباشرة المسموح بها", + "Direct messages are always allowed from these instances.": "الرسائل المباشرة مسموح بها دائما من هذه المثيلات.", + "Key Shortcuts": "الاختصارات الرئيسية", + "menuTimeline": "عرض الجدول الزمني", + "menuEdit": "يحرر", + "menuProfile": "عرض الملف الشخصي", + "menuInbox": "صندوق الوارد", + "menuSearch": "البحث / المتتالية", + "menuNewPost": "منشور جديد", + "menuCalendar": "تقويم", + "menuDM": "رسالة مباشرة", + "menuReplies": "الردود", + "menuOutbox": "مرسل", + "menuBookmarks": "إشارات مرجعية", + "menuShares": "البنود المشتركة", + "menuBlogs": "المدونات", + "menuNewswire": "Newswire", + "menuLinks": "روابط انترنت", + "menuModeration": "الاعتدال", + "menuFollowing": "التالية", + "menuFollowers": "متابعون", + "menuRoles": "أدوار", + "menuSkills": "مهارات", + "menuLogout": "تسجيل خروج", + "menuKeys": "الاختصارات الرئيسية", + "submitButton": "زر الإرسال", + "menuMedia": "وسائط", + "followButton": "زر متابعة / متابعة", + "blockButton": "زر كتلة", + "infoButton": "زر المعلومات", + "snoozeButton": "زر الغفوة", + "reportButton": "زر تقرير", + "viewButton": "عرض زر", + "enterPetname": "أدخل PETNAME", + "enterNotes": "أدخل الملاحظات", + "These access keys may be used": "قد يتم استخدام مفاتيح الوصول هذه، عادة مع مفتاح ALT + SHIFT + مفتاح ALT +" } diff --git a/translations/ca.json b/translations/ca.json index b44f228fe..589f5013c 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -401,5 +401,41 @@ "counselor": "conseller", "Counselors": "Consellers", "shocked": "sorprès", - "Encrypted": "Xifrat" + "Encrypted": "Xifrat", + "Direct Message permitted instances": "Instàncies permeses del missatge directe", + "Direct messages are always allowed from these instances.": "Els missatges directes sempre estan permesos d'aquests casos.", + "Key Shortcuts": "Dreceres clau", + "menuTimeline": "Vista de la línia de temps", + "menuEdit": "Preprarar una edició", + "menuProfile": "Vista de perfil", + "menuInbox": "Capa inferior", + "menuSearch": "Cerca / Segueix", + "menuNewPost": "Nou missatge", + "menuCalendar": "Calendari", + "menuDM": "Missatges directes", + "menuReplies": "Resum", + "menuOutbox": "Present", + "menuBookmarks": "Adreces d'interès", + "menuShares": "Articles compartits", + "menuBlogs": "Blocs", + "menuNewswire": "Newswire", + "menuLinks": "Enllaços web", + "menuModeration": "Moderació", + "menuFollowing": "Proper", + "menuFollowers": "Seguidors", + "menuRoles": "Rols", + "menuSkills": "Habilitats", + "menuLogout": "Tancar sessió", + "menuKeys": "Dreceres clau", + "submitButton": "Envia el botó", + "menuMedia": "Medis de comunicació", + "followButton": "Seguiu / no seguit", + "blockButton": "Botó de bloc", + "infoButton": "Botó d'informació", + "snoozeButton": "Botó de snooze", + "reportButton": "Botó d'informe", + "viewButton": "Botó Veure", + "enterPetname": "Introduïu PETNAME", + "enterNotes": "Introduïu notes", + "These access keys may be used": "Es poden utilitzar aquestes tecles d'accés, típicament amb Alt + Maj + tecla o Alt + clau" } diff --git a/translations/cy.json b/translations/cy.json index 0f8362073..2a3eb079f 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -401,5 +401,41 @@ "counselor": "cynghorydd", "Counselors": "Cynghorwyr", "shocked": "sioc", - "Encrypted": "Amgryptio" + "Encrypted": "Amgryptio", + "Direct Message permitted instances": "Achosion a ganiateir negeseuon uniongyrchol", + "Direct messages are always allowed from these instances.": "Caniateir negeseuon uniongyrchol bob amser o'r achosion hyn.", + "Key Shortcuts": "Llwybrau byr allweddol", + "menuTimeline": "View View", + "menuEdit": "Golygaf", + "menuProfile": "Gweld Proffil", + "menuInbox": "Mewnflwch", + "menuSearch": "Chwilio / Dilyn", + "menuNewPost": "Swydd newydd", + "menuCalendar": "Galendr", + "menuDM": "Negeseuon Uniongyrchol", + "menuReplies": "Atebion", + "menuOutbox": "Hanfon", + "menuBookmarks": "Nodau tudalen", + "menuShares": "Eitemau a Rennir", + "menuBlogs": "Blogiau", + "menuNewswire": "Newswire", + "menuLinks": "Cysylltiadau", + "menuModeration": "Safoniad", + "menuFollowing": "Ddilynol", + "menuFollowers": "Ddilynwyr", + "menuRoles": "Rolau", + "menuSkills": "Medrau", + "menuLogout": "Allgofnodi", + "menuKeys": "Llwybrau byr allweddol", + "submitButton": "Cyflwyno botwm", + "menuMedia": "Chyfryngau", + "followButton": "Dilynwch / Peidiwch â Dilynwch y botwm", + "blockButton": "Botwm bloc", + "infoButton": "Botwm info", + "snoozeButton": "Botwm Snooze", + "reportButton": "Botwm adroddiadau", + "viewButton": "Gweld y botwm", + "enterPetname": "Rhowch enw PETName", + "enterNotes": "Rhowch nodiadau", + "These access keys may be used": "Gellir defnyddio'r allweddi mynediad hyn, fel arfer gyda ALT + Shift + Allwedd Allwedd neu ALT +" } diff --git a/translations/de.json b/translations/de.json index bf5189e6c..6c50a2ddd 100644 --- a/translations/de.json +++ b/translations/de.json @@ -401,5 +401,41 @@ "counselor": "Beraterin", "Counselors": "Berater", "shocked": "schockiert", - "Encrypted": "Verschlüsselt" + "Encrypted": "Verschlüsselt", + "Direct Message permitted instances": "Direktnachricht erlaubte Instanzen", + "Direct messages are always allowed from these instances.": "Direkte Nachrichten sind in diesen Fällen immer zulässig.", + "Key Shortcuts": "Schlüsselverknüpfungen", + "menuTimeline": "Timeline-Ansicht", + "menuEdit": "Bearbeiten", + "menuProfile": "Profilansicht", + "menuInbox": "Inbox", + "menuSearch": "Suche / Folgen", + "menuNewPost": "Neuer Beitrag", + "menuCalendar": "Kalender", + "menuDM": "Direkte Nachrichten", + "menuReplies": "Antworten", + "menuOutbox": "Geschickt", + "menuBookmarks": "Lesezeichen", + "menuShares": "Gemeinsame Artikel", + "menuBlogs": "Blogs", + "menuNewswire": "Newswire", + "menuLinks": "Web-Links", + "menuModeration": "Mäßigung", + "menuFollowing": "Folgen", + "menuFollowers": "Anhänger", + "menuRoles": "Rollen", + "menuSkills": "Kompetenzen", + "menuLogout": "Ausloggen", + "menuKeys": "Schlüsselverknüpfungen", + "submitButton": "Button einreichen", + "menuMedia": "Medien", + "followButton": "Folgen / folgen Sie nicht der Taste", + "blockButton": "Blockknopf", + "infoButton": "Info-Taste", + "snoozeButton": "Schlummertaste", + "reportButton": "Berichtsknopf", + "viewButton": "Schaltfläche anzeigen", + "enterPetname": "Petname eingeben", + "enterNotes": "Notizen eingeben", + "These access keys may be used": "Diese Zugriffstasten können verwendet werden, typischerweise mit ALT + SHIFT + -Taste oder ALT + -Taste" } diff --git a/translations/en.json b/translations/en.json index 6840c67f2..0a5a5c8f2 100644 --- a/translations/en.json +++ b/translations/en.json @@ -401,5 +401,41 @@ "counselor": "counselor", "Counselors": "Counselors", "shocked": "shocked", - "Encrypted": "Encrypted" + "Encrypted": "Encrypted", + "Direct Message permitted instances": "Direct Message permitted instances", + "Direct messages are always allowed from these instances.": "Direct messages are always allowed from these instances.", + "Key Shortcuts": "Key Shortcuts", + "menuTimeline": "Timeline view", + "menuEdit": "Edit", + "menuProfile": "Profile view", + "menuInbox": "Inbox", + "menuSearch": "Search/follow", + "menuNewPost": "New post", + "menuCalendar": "Calendar", + "menuDM": "Direct Messages", + "menuReplies": "Replies", + "menuOutbox": "Sent", + "menuBookmarks": "Bookmarks", + "menuShares": "Shared items", + "menuBlogs": "Blogs", + "menuNewswire": "Newswire", + "menuLinks": "Links", + "menuModeration": "Moderation", + "menuFollowing": "Following", + "menuFollowers": "Followers", + "menuRoles": "Roles", + "menuSkills": "Skills", + "menuLogout": "Logout", + "menuKeys": "Key Shortcuts", + "submitButton": "Submit button", + "menuMedia": "Media", + "followButton": "Follow/unfollow button", + "blockButton": "Block button", + "infoButton": "Info button", + "snoozeButton": "Snooze button", + "reportButton": "Report button", + "viewButton": "View button", + "enterPetname": "Enter petname", + "enterNotes": "Enter notes", + "These access keys may be used": "These access keys may be used, typically with ALT + SHIFT + key or ALT + key" } diff --git a/translations/es.json b/translations/es.json index 901f8b515..4c4a8b48f 100644 --- a/translations/es.json +++ b/translations/es.json @@ -401,5 +401,41 @@ "counselor": "Consejera", "Counselors": "Consejeras", "shocked": "conmocionada", - "Encrypted": "Cifrada" + "Encrypted": "Cifrada", + "Direct Message permitted instances": "Mensaje directo permitido instancias", + "Direct messages are always allowed from these instances.": "Los mensajes directos siempre están permitidos de estas instancias.", + "Key Shortcuts": "Atajos clave", + "menuTimeline": "Vista de la línea de tiempo", + "menuEdit": "Editar", + "menuProfile": "Vista del perfil", + "menuInbox": "Bandeja de entrada", + "menuSearch": "Búsqueda / Seguir", + "menuNewPost": "Nueva publicación", + "menuCalendar": "Calendario", + "menuDM": "Mensajes directos", + "menuReplies": "Respuestas", + "menuOutbox": "Enviada", + "menuBookmarks": "Marcadores", + "menuShares": "Artículos compartidos", + "menuBlogs": "Blogs", + "menuNewswire": "Newswire", + "menuLinks": "Enlaces web", + "menuModeration": "Moderación", + "menuFollowing": "Siguiente", + "menuFollowers": "De seguidores", + "menuRoles": "Roles", + "menuSkills": "Habilidades", + "menuLogout": "Cerrar sesión", + "menuKeys": "Atajos clave", + "submitButton": "Botón de enviar", + "menuMedia": "Medios de comunicación", + "followButton": "Botón de seguimiento / dejo", + "blockButton": "Botón de bloqueo", + "infoButton": "Botón de información", + "snoozeButton": "El botón de dormitar", + "reportButton": "Botón de informe", + "viewButton": "Botón de vista", + "enterPetname": "Entrar en nombre de pettname", + "enterNotes": "Ingresar notas", + "These access keys may be used": "Se pueden usar estas teclas de acceso, típicamente con teclas ALT + MAYÚS + teclas o ALT +" } diff --git a/translations/fr.json b/translations/fr.json index 6d25dbd97..a625f66fb 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -401,5 +401,41 @@ "counselor": "Conseillère", "Counselors": "Conseillères", "shocked": "sous le choc", - "Encrypted": "Crypté" + "Encrypted": "Crypté", + "Direct Message permitted instances": "Message direct des instances autorisées", + "Direct messages are always allowed from these instances.": "Les messages directs sont toujours autorisés dans ces instances.", + "Key Shortcuts": "Raccourcis clés", + "menuTimeline": "Vue de la chronologie", + "menuEdit": "Éditer", + "menuProfile": "Voir", + "menuInbox": "Boîte de réception", + "menuSearch": "Rechercher / suivre", + "menuNewPost": "Nouveau poste", + "menuCalendar": "Calendrier", + "menuDM": "Messages directs", + "menuReplies": "réponses", + "menuOutbox": "Envoyée", + "menuBookmarks": "Favoris", + "menuShares": "Articles partagés", + "menuBlogs": "Blogs", + "menuNewswire": "Newswire", + "menuLinks": "Liens web", + "menuModeration": "Modération", + "menuFollowing": "Suivante", + "menuFollowers": "Suiveuses", + "menuRoles": "Les rôles", + "menuSkills": "Compétences", + "menuLogout": "Se déconnecter", + "menuKeys": "Raccourcis clés", + "submitButton": "Bouton de soumission", + "menuMedia": "Médias", + "followButton": "Suivez / Bouton Suivi", + "blockButton": "Bouton de bloc", + "infoButton": "Bouton info", + "snoozeButton": "Le bouton de la sieste", + "reportButton": "Bouton de rapport", + "viewButton": "Bouton d'affichage", + "enterPetname": "Entrez PETNAME", + "enterNotes": "Faire entrer des notes", + "These access keys may be used": "Ces touches d'accès peuvent être utilisées typiquement avec une touche Alt + Maj + ou Alt +" } diff --git a/translations/ga.json b/translations/ga.json index a997cb359..7f06c2759 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -401,5 +401,41 @@ "counselor": "Comhairleoir", "Counselors": "Comhairleoirí", "shocked": "ionadh", - "Encrypted": "Criptithe" + "Encrypted": "Criptithe", + "Direct Message permitted instances": "Ceadaíonn teachtaireacht dhíreach cásanna", + "Direct messages are always allowed from these instances.": "Ceadaítear teachtaireachtaí díreacha i gcónaí ó na cásanna seo.", + "Key Shortcuts": "Príomh-aicearraí", + "menuTimeline": "Amlíne View", + "menuEdit": "Eagarthóireacht a dhéanamh ar", + "menuProfile": "Dearcadh próifíle", + "menuInbox": "Bosca isteach", + "menuSearch": "Cuardaigh / Lean", + "menuNewPost": "Post nua", + "menuCalendar": "Caileandar", + "menuDM": "Teachtaireachtaí díreacha", + "menuReplies": "Freagraí", + "menuOutbox": "Seoltar", + "menuBookmarks": "Leabharmharcanna", + "menuShares": "Míreanna Comhroinnte", + "menuBlogs": "Blaganna", + "menuNewswire": "Newswire", + "menuLinks": "Naisc Ghréasáin", + "menuModeration": "Modhnóireacht a dhéanamh ar", + "menuFollowing": "Lucht tacaíochta", + "menuFollowers": "Leanúna", + "menuRoles": "Róil", + "menuSkills": "Scileanna", + "menuLogout": "Logáil Amach", + "menuKeys": "Príomh-aicearraí", + "submitButton": "Cuir an cnaipe isteach", + "menuMedia": "Na meáin", + "followButton": "Lean / Cnaipe Unurollow", + "blockButton": "Cnaipe bloc", + "infoButton": "Cnaipe Info", + "snoozeButton": "Cnaipe snooze", + "reportButton": "Cnaipe Tuairisce", + "viewButton": "Féach an cnaipe", + "enterPetname": "Cuir isteach PetName", + "enterNotes": "Cuir nótaí isteach", + "These access keys may be used": "Is féidir na heochracha rochtana seo a úsáid, de ghnáth le Alt + Shift + Eochair nó Alt + Eochair" } diff --git a/translations/hi.json b/translations/hi.json index 0bc6813a1..be8a7b9f1 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -401,5 +401,41 @@ "counselor": "काउंसलर", "Counselors": "सलाहकार", "shocked": "हैरान", - "Encrypted": "को गोपित" + "Encrypted": "को गोपित", + "Direct Message permitted instances": "प्रत्यक्ष संदेश अनुमत उदाहरण", + "Direct messages are always allowed from these instances.": "इन उदाहरणों से प्रत्यक्ष संदेश हमेशा अनुमति देते हैं।", + "Key Shortcuts": "कुंजी शॉर्टकट", + "menuTimeline": "समयरेखा दृश्य", + "menuEdit": "संपादित करें", + "menuProfile": "प्रोफ़ाइल दृश्य", + "menuInbox": "इनबॉक्स", + "menuSearch": "खोज / अनुसरण करें", + "menuNewPost": "नई पोस्ट", + "menuCalendar": "पंचांग", + "menuDM": "सीधे संदेश", + "menuReplies": "जवाब", + "menuOutbox": "भेज दिया", + "menuBookmarks": "बुकमार्क", + "menuShares": "साझा आइटम", + "menuBlogs": "ब्लॉग", + "menuNewswire": "न्यूज़वायर", + "menuLinks": "वेब लिंक", + "menuModeration": "संयम", + "menuFollowing": "निम्नलिखित", + "menuFollowers": "समर्थक", + "menuRoles": "भूमिकाएँ", + "menuSkills": "कौशल", + "menuLogout": "लॉग आउट", + "menuKeys": "कुंजी शॉर्टकट", + "submitButton": "जमा करने वाला बटन", + "menuMedia": "मीडिया", + "followButton": "फॉलो / अनफ़ॉलो बटन", + "blockButton": "ब्लॉक बटन", + "infoButton": "जानकारी बटन", + "snoozeButton": "अलार्म को थोड़ी देर बंद करने वाला बटन", + "reportButton": "रिपोर्ट बटन", + "viewButton": "देखें बटन", + "enterPetname": "PETNAME दर्ज करें", + "enterNotes": "नोट्स दर्ज करें", + "These access keys may be used": "इन एक्सेस कुंजियों का उपयोग किया जा सकता है, आमतौर पर Alt + Shift + कुंजी या Alt + कुंजी के साथ" } diff --git a/translations/it.json b/translations/it.json index 347d904e7..902b7adfe 100644 --- a/translations/it.json +++ b/translations/it.json @@ -401,5 +401,41 @@ "counselor": "Consulente", "Counselors": "Consiglieri", "shocked": "scioccata", - "Encrypted": "Crittografato" + "Encrypted": "Crittografato", + "Direct Message permitted instances": "Messaggio diretto istanze consentite", + "Direct messages are always allowed from these instances.": "I messaggi diretti sono sempre ammessi da questi casi.", + "Key Shortcuts": "Scorciatoie chiave", + "menuTimeline": "Vista della cronologia", + "menuEdit": "Modificare", + "menuProfile": "Visualizzazione del profilo", + "menuInbox": "Posta in arrivo", + "menuSearch": "Cerca / Segui", + "menuNewPost": "Nuovo post.", + "menuCalendar": "Calendario", + "menuDM": "Messaggi diretti", + "menuReplies": "Risposte", + "menuOutbox": "Inviata", + "menuBookmarks": "Segnalibri", + "menuShares": "Articoli condivisi", + "menuBlogs": "Blog", + "menuNewswire": "Newswire", + "menuLinks": "Link internet", + "menuModeration": "Moderazione", + "menuFollowing": "A seguire", + "menuFollowers": "Seguaci", + "menuRoles": "Ruoli", + "menuSkills": "Competenze", + "menuLogout": "Disconnettersi", + "menuKeys": "Scorciatoie chiave", + "submitButton": "Invia il pulsante", + "menuMedia": "Media", + "followButton": "Segui il pulsante Segui / Unfollow", + "blockButton": "Blocco pulsante", + "infoButton": "Pulsante info", + "snoozeButton": "Pulsante snooze.", + "reportButton": "Pulsante report.", + "viewButton": "Visualizza il pulsante", + "enterPetname": "Inserisci PetName", + "enterNotes": "Inserisci le note", + "These access keys may be used": "Questi tasti di accesso possono essere utilizzati, in genere con tasto ALT + MAIUSC + o ALT + Key" } diff --git a/translations/ja.json b/translations/ja.json index ba402b9b3..8a14f543e 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -401,5 +401,41 @@ "counselor": "カウンセラー", "Counselors": "カウンセラー", "shocked": "ショックを受けた", - "Encrypted": "暗号化" + "Encrypted": "暗号化", + "Direct Message permitted instances": "直接メッセージ許可インスタンス", + "Direct messages are always allowed from these instances.": "直接メッセージは常にこれらのインスタンスから許可されています。", + "Key Shortcuts": "キーショートカット", + "menuTimeline": "タイムラインビュー", + "menuEdit": "編集する", + "menuProfile": "プロファイルビュー", + "menuInbox": "受信箱", + "menuSearch": "検索/フォロー", + "menuNewPost": "新しい投稿", + "menuCalendar": "カレンダー", + "menuDM": "ダイレクトメッセージ", + "menuReplies": "返信", + "menuOutbox": "送り返した", + "menuBookmarks": "ブックマーク", + "menuShares": "共有アイテム", + "menuBlogs": "ブログ", + "menuNewswire": "ニューワイヤー", + "menuLinks": "Webリンク", + "menuModeration": "節度", + "menuFollowing": "以下", + "menuFollowers": "フォロワー", + "menuRoles": "役割", + "menuSkills": "スキル", + "menuLogout": "ログアウト", + "menuKeys": "キーショートカット", + "submitButton": "送信ボタン", + "menuMedia": "メディア", + "followButton": "フォロー/フォローダウンボタン", + "blockButton": "ブロックボタン", + "infoButton": "情報ボタン", + "snoozeButton": "スヌーズボタン", + "reportButton": "レポートボタン", + "viewButton": "ボタンを見る", + "enterPetname": "PetNameを入力してください", + "enterNotes": "ノートを入力してください", + "These access keys may be used": "これらのアクセスキーは、通常はAlt + Shift +キーまたはAlt +キーを使用して使用できます。" } diff --git a/translations/ku.json b/translations/ku.json index 5e74514b1..97dc9cc6a 100644 --- a/translations/ku.json +++ b/translations/ku.json @@ -401,5 +401,41 @@ "counselor": "Pêşnîyarvan", "Counselors": "Selêwirmendan", "shocked": "şok kirin", - "Encrypted": "Encîfre kirin" + "Encrypted": "Encîfre kirin", + "Direct Message permitted instances": "Peyama rasterast destûrê", + "Direct messages are always allowed from these instances.": "Peyamên rasterast her gav ji van deman têne destûr kirin.", + "Key Shortcuts": "Kurteyên Key", + "menuTimeline": "Dîtina Timeline", + "menuEdit": "Weşandin", + "menuProfile": "View Profîl", + "menuInbox": "Inbott", + "menuSearch": "Lêgerîn / bişopîne", + "menuNewPost": "Peyama nû", + "menuCalendar": "Salname", + "menuDM": "Peyamên rasterast", + "menuReplies": "Bersiv", + "menuOutbox": "Şandin", + "menuBookmarks": "Emîrê", + "menuShares": "Tiştên parvekirî", + "menuBlogs": "Blogs", + "menuNewswire": "Newswire", + "menuLinks": "Girêdanên malperê", + "menuModeration": "Nermbûn", + "menuFollowing": "Pêketînî", + "menuFollowers": "Followers", + "menuRoles": "Roles", + "menuSkills": "Şarezayên", + "menuLogout": "Derkeve", + "menuKeys": "Kurteyên Key", + "submitButton": "Bişkojka bişînin", + "menuMedia": "Medya", + "followButton": "Bişkojka bişopînin / Nexşe", + "blockButton": "Bişkojka Block", + "infoButton": "Bişkoja INFO", + "snoozeButton": "Bişkojka Snooze", + "reportButton": "Bişkoja Report", + "viewButton": "Bişkoja View", + "enterPetname": "Porê binivîse", + "enterNotes": "Nîşan binivîse", + "These access keys may be used": "Dibe ku ev keysên gihîştinê bikar bînin, bi gelemperî bi alt + shift + key an alt + key" } diff --git a/translations/oc.json b/translations/oc.json index f355a9485..b51578212 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -397,5 +397,41 @@ "counselor": "Counselors", "Counselors": "Counselors", "shocked": "shocked", - "Encrypted": "Encrypted" + "Encrypted": "Encrypted", + "Direct Message permitted instances": "Direct Message permitted instances", + "Direct messages are always allowed from these instances.": "Direct messages are always allowed from these instances.", + "Key Shortcuts": "Key Shortcuts", + "menuTimeline": "Timeline view", + "menuEdit": "Edit", + "menuProfile": "Profile view", + "menuInbox": "Inbox", + "menuSearch": "Search/follow", + "menuNewPost": "New post", + "menuCalendar": "Calendar", + "menuDM": "Direct Messages", + "menuReplies": "Replies", + "menuOutbox": "Sent", + "menuBookmarks": "Bookmarks", + "menuShares": "Shared items", + "menuBlogs": "Blogs", + "menuNewswire": "Newswire", + "menuLinks": "Links", + "menuModeration": "Moderation", + "menuFollowing": "Following", + "menuFollowers": "Followers", + "menuRoles": "Roles", + "menuSkills": "Skills", + "menuLogout": "Logout", + "menuKeys": "Key Shortcuts", + "submitButton": "Submit button", + "menuMedia": "Media", + "followButton": "Follow/unfollow button", + "blockButton": "Block button", + "infoButton": "Info button", + "snoozeButton": "Snooze button", + "reportButton": "Report button", + "viewButton": "View button", + "enterPetname": "Enter petname", + "enterNotes": "Enter notes", + "These access keys may be used": "These access keys may be used, typically with ALT + SHIFT + key or ALT + key" } diff --git a/translations/pt.json b/translations/pt.json index b7b3206c8..6225a7986 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -401,5 +401,41 @@ "counselor": "Conselheira", "Counselors": "Conselheiras", "shocked": "chocada", - "Encrypted": "Criptografada" + "Encrypted": "Criptografada", + "Direct Message permitted instances": "Mensagens diretas permitidas instâncias", + "Direct messages are always allowed from these instances.": "Mensagens diretas são sempre permitidas a partir dessas instâncias.", + "Key Shortcuts": "Atalhos-chave", + "menuTimeline": "Vista da linha do tempo", + "menuEdit": "Editar", + "menuProfile": "Vista de perfil", + "menuInbox": "Caixa de entrada", + "menuSearch": "Pesquisa / Siga", + "menuNewPost": "Nova postagem", + "menuCalendar": "Calendário", + "menuDM": "Mensagens diretas", + "menuReplies": "Respostas", + "menuOutbox": "Enviei", + "menuBookmarks": "Favoritas", + "menuShares": "Itens compartilhados", + "menuBlogs": "Blogs", + "menuNewswire": "Newswire", + "menuLinks": "Links da Web", + "menuModeration": "Moderação", + "menuFollowing": "Seguindo", + "menuFollowers": "Seguidoras", + "menuRoles": "Papéis", + "menuSkills": "Habilidades", + "menuLogout": "Sair", + "menuKeys": "Atalhos-chave", + "submitButton": "Botão de envio", + "menuMedia": "meios de comunicação", + "followButton": "Siga / Deixar botão", + "blockButton": "Botão de bloco", + "infoButton": "Botão de informação", + "snoozeButton": "Botão Snooze", + "reportButton": "Botão de relatório", + "viewButton": "Botão de visualização", + "enterPetname": "Digite Petname", + "enterNotes": "Digite notas", + "These access keys may be used": "Essas teclas de acesso podem ser usadas, normalmente com tecla Alt + Shift + Key ou Alt +" } diff --git a/translations/ru.json b/translations/ru.json index bdf9754f8..460e78fcc 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -401,5 +401,41 @@ "counselor": "Советник", "Counselors": "Советники", "shocked": "потрясенный", - "Encrypted": "Зашифрованный" + "Encrypted": "Зашифрованный", + "Direct Message permitted instances": "Прямое сообщение разрешено экземпляры", + "Direct messages are always allowed from these instances.": "Прямые сообщения всегда допускаются из этих экземпляров.", + "Key Shortcuts": "Клавичные ярлыки", + "menuTimeline": "Сроки зрения", + "menuEdit": "Редактировать", + "menuProfile": "вид профиля", + "menuInbox": "Входящие", + "menuSearch": "Поиск / следующее", + "menuNewPost": "Новый пост", + "menuCalendar": "Календарь", + "menuDM": "Прямые сообщения", + "menuReplies": "Отвечает", + "menuOutbox": "Отправил", + "menuBookmarks": "Закладки", + "menuShares": "Общие предметы", + "menuBlogs": "Блоги", + "menuNewswire": "Новобранец", + "menuLinks": "веб ссылки", + "menuModeration": "На модерации", + "menuFollowing": "Следующий", + "menuFollowers": "Подписчики", + "menuRoles": "Роли", + "menuSkills": "Навыки и умения", + "menuLogout": "Выйти", + "menuKeys": "Клавичные ярлыки", + "submitButton": "Отправить кнопку", + "menuMedia": "СМИ", + "followButton": "Следуйте / отписаться кнопка", + "blockButton": "Кнопка блокировки", + "infoButton": "Информация Кнопка", + "snoozeButton": "Кнопка сножения", + "reportButton": "Кнопка отчета", + "viewButton": "Кнопка просмотра", + "enterPetname": "Введите petname", + "enterNotes": "Введите ноты", + "These access keys may be used": "Эти ключевые ключи доступа могут быть использованы, обычно с ALT + Shift + Key или Alt + Key" } diff --git a/translations/zh.json b/translations/zh.json index 7814ba6bd..54c153c8e 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -401,5 +401,41 @@ "counselor": "顾问", "Counselors": "辅导员", "shocked": "震惊的", - "Encrypted": "加密的" + "Encrypted": "加密的", + "Direct Message permitted instances": "直接留言允许实例", + "Direct messages are always allowed from these instances.": "这些实例始终允许直接消息。", + "Key Shortcuts": "关键捷径", + "menuTimeline": "时间表视图", + "menuEdit": "编辑", + "menuProfile": "个人资料视图", + "menuInbox": "收件箱", + "menuSearch": "搜索/关注", + "menuNewPost": "最新帖子", + "menuCalendar": "日历", + "menuDM": "直接留言", + "menuReplies": "答案", + "menuOutbox": "发送", + "menuBookmarks": "书签", + "menuShares": "共享项目", + "menuBlogs": "博客", + "menuNewswire": "新闻界.", + "menuLinks": "网页链接", + "menuModeration": "适度", + "menuFollowing": "下列的", + "menuFollowers": "追随者", + "menuRoles": "角色", + "menuSkills": "技能", + "menuLogout": "登出", + "menuKeys": "关键捷径", + "submitButton": "提交按钮", + "menuMedia": "媒体", + "followButton": "关注/取消关注按钮", + "blockButton": "块按钮", + "infoButton": "信息按钮", + "snoozeButton": "贪睡按钮", + "reportButton": "报告按钮", + "viewButton": "查看按钮", + "enterPetname": "进入宠物名", + "enterNotes": "输入笔记", + "These access keys may be used": "可以使用这些访问密钥,通常使用Alt + Shift +键或ALT +键" } diff --git a/utils.py b/utils.py index d1addc920..2542e90a0 100644 --- a/utils.py +++ b/utils.py @@ -388,7 +388,9 @@ def getFollowersOfPerson(baseDir: str, for subdir, dirs, files in os.walk(baseDir + '/accounts'): for account in dirs: filename = os.path.join(subdir, account) + '/' + followFile - if account == handle or account.startswith('inbox@'): + if account == handle or \ + account.startswith('inbox@') or \ + account.startswith('news@'): continue if not os.path.isfile(filename): continue @@ -1057,7 +1059,7 @@ def clearFromPostCaches(baseDir: str, recentPostsCache: {}, for acct in dirs: if '@' not in acct: continue - if 'inbox@' in acct: + if acct.startswith('inbox@'): continue cacheDir = os.path.join(baseDir + '/accounts', acct) postFilename = cacheDir + filename @@ -1405,8 +1407,11 @@ def noOfAccounts(baseDir: str) -> bool: for subdir, dirs, files in os.walk(baseDir + '/accounts'): for account in dirs: if '@' in account: - if not account.startswith('inbox@'): - accountCtr += 1 + if account.startswith('inbox@'): + continue + elif account.startswith('news@'): + continue + accountCtr += 1 break return accountCtr @@ -1420,7 +1425,8 @@ def noOfActiveAccountsMonthly(baseDir: str, months: int) -> bool: for subdir, dirs, files in os.walk(baseDir + '/accounts'): for account in dirs: if '@' in account: - if not account.startswith('inbox@'): + if not account.startswith('inbox@') and \ + not account.startswith('news@'): lastUsedFilename = \ baseDir + '/accounts/' + account + '/.lastUsed' if os.path.isfile(lastUsedFilename): @@ -2197,3 +2203,22 @@ def loadTranslationsFromFile(baseDir: str, language: str) -> ({}, str): translationsFile = baseDir + '/translations/' + \ systemLanguage + '.json' return loadJson(translationsFile), systemLanguage + + +def dmAllowedFromDomain(baseDir: str, + nickname: str, domain: str, + sendingActorDomain: str) -> bool: + """When a DM is received and the .followDMs flag file exists + Then optionally some domains can be specified as allowed, + regardless of individual follows. + i.e. Mostly you only want DMs from followers, but there are + a few particular instances that you trust + """ + dmAllowedInstancesFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/dmAllowedInstances.txt' + if not os.path.isfile(dmAllowedInstancesFilename): + return False + if sendingActorDomain + '\n' in open(dmAllowedInstancesFilename).read(): + return True + return False diff --git a/webapp_accesskeys.py b/webapp_accesskeys.py new file mode 100644 index 000000000..59634ecb9 --- /dev/null +++ b/webapp_accesskeys.py @@ -0,0 +1,115 @@ +__filename__ = "webapp_accesskeys.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.2.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from utils import loadJson +from utils import getConfigParam +from webapp_utils import htmlHeaderWithExternalStyle +from webapp_utils import htmlFooter + + +def loadAccessKeysForAccounts(baseDir: str, keyShortcuts: {}, + accessKeysTemplate: {}) -> None: + """Loads key shortcuts for each account + """ + 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 + accountDir = os.path.join(baseDir + '/accounts', acct) + accessKeysFilename = accountDir + '/accessKeys.json' + if not os.path.isfile(accessKeysFilename): + continue + nickname = acct.split('@')[0] + accessKeys = loadJson(accessKeysFilename) + if accessKeys: + keyShortcuts[nickname] = accessKeysTemplate.copy() + for variableName, key in accessKeysTemplate.items(): + if accessKeys.get(variableName): + keyShortcuts[nickname][variableName] = \ + accessKeys[variableName] + break + + +def htmlAccessKeys(cssCache: {}, baseDir: str, + nickname: str, domain: str, + translate: {}, accessKeys: {}, + defaultAccessKeys: {}, + defaultTimeline: str) -> str: + """Show and edit key shortcuts + """ + accessKeysFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '/accessKeys.json' + if os.path.isfile(accessKeysFilename): + accessKeysFromFile = loadJson(accessKeysFilename) + if accessKeysFromFile: + accessKeys = accessKeysFromFile + + accessKeysForm = '' + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + instanceTitle = \ + getConfigParam(baseDir, 'instanceTitle') + accessKeysForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) + accessKeysForm += '
' + translate['These access keys may be used'] + \ + '
' + + accessKeysForm += ' \n' + accessKeysForm += '' + \ translate['About this Instance'] + '
' @@ -274,7 +281,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str, rssIconAtTop: bool, iconsAsButtons: bool, defaultTimeline: str, - theme: str) -> str: + theme: str, accessKeys: {}) -> str: """Show the left column links within mobile view """ htmlStr = '' @@ -300,7 +307,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str, bannerFile, bannerFilename = \ getBannerFile(baseDir, nickname, domain, theme) htmlStr += \ - '' + \ + '' + \ '