diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d6501a39a..d3e2d1587 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,6 @@ image: debian:testing test: script: - apt-get update - - apt-get install -y python3-crypto python3-dateutil python3-idna python3-numpy python3-pil.imagetk python3-pycryptodome python3-requests python3-socks python3-setuptools python3-pyqrcode + - apt-get install -y python3-cryptography python3-dateutil python3-idna python3-numpy python3-pil.imagetk python3-requests python3-socks python3-setuptools python3-pyqrcode - python3 epicyon.py --tests - python3 epicyon.py --testsnetwork diff --git a/blocking.py b/blocking.py index f0759f963..29aa2a45b 100644 --- a/blocking.py +++ b/blocking.py @@ -7,6 +7,9 @@ __email__ = "bob@freedombone.net" __status__ = "Production" import os +from datetime import datetime +from utils import fileLastModified +from utils import setConfigParam from utils import hasUsersPath from utils import getFullDomain from utils import removeIdEnding @@ -175,15 +178,27 @@ def isBlockedDomain(baseDir: str, domain: str) -> bool: if noOfSections > 2: shortDomain = domain[noOfSections-2] + '.' + domain[noOfSections-1] - globalBlockingFilename = baseDir + '/accounts/blocking.txt' - if os.path.isfile(globalBlockingFilename): - with open(globalBlockingFilename, 'r') as fpBlocked: - blockedStr = fpBlocked.read() - if '*@' + domain in blockedStr: - return True - if shortDomain: - if '*@' + shortDomain in blockedStr: + allowFilename = baseDir + '/accounts/allowedinstances.txt' + if not os.path.isfile(allowFilename): + # instance block list + globalBlockingFilename = baseDir + '/accounts/blocking.txt' + if os.path.isfile(globalBlockingFilename): + with open(globalBlockingFilename, 'r') as fpBlocked: + blockedStr = fpBlocked.read() + if '*@' + domain in blockedStr: return True + if shortDomain: + if '*@' + shortDomain in blockedStr: + return True + else: + # instance allow list + if not shortDomain: + if domain not in open(allowFilename).read(): + return True + else: + if shortDomain not in open(allowFilename).read(): + return True + return False @@ -344,3 +359,91 @@ def outboxUndoBlock(baseDir: str, httpPrefix: str, nicknameBlocked, domainBlockedFull) if debug: print('DEBUG: post undo blocked via c2s - ' + postFilename) + + +def setBrochMode(baseDir: str, domainFull: str, enabled: bool) -> None: + """Broch mode can be used to lock down the instance during + a period of time when it is temporarily under attack. + For example, where an adversary is constantly spinning up new + instances. + It surveys the following lists of all accounts and uses that + to construct an instance level allow list. Anything arriving + which is then not from one of the allowed domains will be dropped + """ + allowFilename = baseDir + '/accounts/allowedinstances.txt' + + if not enabled: + # remove instance allow list + if os.path.isfile(allowFilename): + os.remove(allowFilename) + print('Broch mode turned off') + else: + if os.path.isfile(allowFilename): + lastModified = fileLastModified(allowFilename) + print('Broch mode already activated ' + lastModified) + return + # generate instance allow list + allowedDomains = [domainFull] + followFiles = ('following.txt', 'followers.txt') + 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) + for followFileType in followFiles: + followingFilename = accountDir + '/' + followFileType + if not os.path.isfile(followingFilename): + continue + with open(followingFilename, "r") as f: + followList = f.readlines() + for handle in followList: + if '@' not in handle: + continue + handle = handle.replace('\n', '') + handleDomain = handle.split('@')[1] + if handleDomain not in allowedDomains: + allowedDomains.append(handleDomain) + break + + # write the allow file + allowFile = open(allowFilename, "w+") + if allowFile: + allowFile.write(domainFull + '\n') + for d in allowedDomains: + allowFile.write(d + '\n') + allowFile.close() + print('Broch mode enabled') + + setConfigParam(baseDir, "brochMode", enabled) + + +def brochModeLapses(baseDir: str, lapseDays=7) -> bool: + """After broch mode is enabled it automatically + elapses after a period of time + """ + allowFilename = baseDir + '/accounts/allowedinstances.txt' + if not os.path.isfile(allowFilename): + return False + lastModified = fileLastModified(allowFilename) + modifiedDate = None + brochMode = True + try: + modifiedDate = \ + datetime.strptime(lastModified, "%Y-%m-%dT%H:%M:%SZ") + except BaseException: + return brochMode + if not modifiedDate: + return brochMode + currTime = datetime.datetime.utcnow() + daysSinceBroch = (currTime - modifiedDate).days + if daysSinceBroch >= lapseDays: + try: + os.remove(allowFilename) + brochMode = False + setConfigParam(baseDir, "brochMode", brochMode) + print('Broch mode has elapsed') + except BaseException: + pass + return brochMode diff --git a/daemon.py b/daemon.py index 577afe37f..f2515ad8e 100644 --- a/daemon.py +++ b/daemon.py @@ -109,6 +109,7 @@ from threads import threadWithTrace from threads import removeDormantThreads from media import replaceYouTube from media import attachMedia +from blocking import setBrochMode from blocking import addBlock from blocking import removeBlock from blocking import addGlobalBlock @@ -185,6 +186,7 @@ from shares import addShare from shares import removeShare from shares import expireShares from categories import setHashtagCategory +from utils import getLocalNetworkAddresses from utils import decodedHost from utils import isPublicPost from utils import getLockedAccount @@ -478,6 +480,10 @@ class PubServer(BaseHTTPRequestHandler): if 'text/html' not in self.headers['Accept']: return False if self.headers['Accept'].startswith('*'): + if self.headers.get('User-Agent'): + if 'ELinks' in self.headers['User-Agent'] or \ + 'Lynx' in self.headers['User-Agent']: + return True return False if 'json' in self.headers['Accept']: return False @@ -1153,20 +1159,46 @@ class PubServer(BaseHTTPRequestHandler): # check for blocked domains so that they can be rejected early messageDomain = None - if messageJson.get('actor'): - messageDomain, messagePort = \ - getDomainFromActor(messageJson['actor']) - if isBlockedDomain(self.server.baseDir, messageDomain): - print('POST from blocked domain ' + messageDomain) - self._400() - self.server.POSTbusy = False - return 3 - else: + if not messageJson.get('actor'): print('Message arriving at inbox queue has no actor') self._400() self.server.POSTbusy = False return 3 + # actor should be a string + if not isinstance(messageJson['actor'], str): + self._400() + self.server.POSTbusy = False + return 3 + + # actor should look like a url + if '://' not in messageJson['actor'] or \ + '.' not in messageJson['actor']: + print('POST actor does not look like a url ' + + messageJson['actor']) + self._400() + self.server.POSTbusy = False + return 3 + + # sent by an actor on a local network address? + if not self.server.allowLocalNetworkAccess: + localNetworkPatternList = getLocalNetworkAddresses() + for localNetworkPattern in localNetworkPatternList: + if localNetworkPattern in messageJson['actor']: + print('POST actor contains local network address ' + + messageJson['actor']) + self._400() + self.server.POSTbusy = False + return 3 + + messageDomain, messagePort = \ + getDomainFromActor(messageJson['actor']) + if isBlockedDomain(self.server.baseDir, messageDomain): + print('POST from blocked domain ' + messageDomain) + self._400() + self.server.POSTbusy = False + return 3 + # if the inbox queue is full then return a busy code if len(self.server.inboxQueue) >= self.server.maxQueueLength: if messageDomain: @@ -4512,6 +4544,18 @@ class PubServer(BaseHTTPRequestHandler): setConfigParam(baseDir, "verifyAllSignatures", verifyAllSignatures) + brochMode = False + if fields.get('brochMode'): + if fields['brochMode'] == 'on': + brochMode = True + currBrochMode = \ + getConfigParam(baseDir, "brochMode") + if brochMode != currBrochMode: + setBrochMode(self.server.baseDir, + self.server.domainFull, + brochMode) + setConfigParam(baseDir, "brochMode", brochMode) + # change moderators list if fields.get('moderators'): if path.startswith('/users/' + @@ -10099,9 +10143,13 @@ class PubServer(BaseHTTPRequestHandler): # manifest for progressive web apps if '/manifest.json' in self.path: - self._progressiveWebAppManifest(callingDomain, - GETstartTime, GETtimings) - return + if self._hasAccept(callingDomain): + if not self._requestHTTP(): + self._progressiveWebAppManifest(callingDomain, + GETstartTime, GETtimings) + return + else: + self.path = '/' # default newswire favicon, for links to sites which # have no favicon @@ -13889,7 +13937,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None: break -def runDaemon(verifyAllSignatures: bool, +def runDaemon(brochMode: bool, + verifyAllSignatures: bool, sendThreadsTimeoutMins: int, dormantMonths: int, maxNewswirePosts: int, @@ -14142,6 +14191,9 @@ def runDaemon(verifyAllSignatures: bool, # cache to store css files httpd.cssCache = {} + # whether to enable broch mode, which locks down the instance + setBrochMode(baseDir, httpd.domainFull, brochMode) + if not os.path.isdir(baseDir + '/accounts/inbox@' + domain): print('Creating shared inbox: inbox@' + domain) createSharedInbox(baseDir, 'inbox', domain, port, httpPrefix) diff --git a/epicyon-profile.css b/epicyon-profile.css index 473fbdb37..f5af28671 100644 --- a/epicyon-profile.css +++ b/epicyon-profile.css @@ -106,11 +106,11 @@ --column-left-icons-margin: 0; --column-right-border-width: 0; --column-left-border-color: black; - --column-left-icon-size: 20%; + --column-left-icon-size: 2.1vw; --column-left-icon-size-mobile: 10%; --column-left-image-width-mobile: 40vw; --column-right-image-width-mobile: 100vw; - --column-right-icon-size: 20%; + --column-right-icon-size: 2.1vw; --column-right-icon-size-mobile: 10%; --newswire-date-color: white; --newswire-voted-background-color: black; diff --git a/epicyon.py b/epicyon.py index 1a741302c..cf0289c65 100644 --- a/epicyon.py +++ b/epicyon.py @@ -279,6 +279,11 @@ parser.add_argument("--verifyAllSignatures", const=True, default=False, help="Whether to require that all incoming " + "posts have valid jsonld signatures") +parser.add_argument("--brochMode", + dest='brochMode', + type=str2bool, nargs='?', + const=True, default=False, + help="Enable broch mode") parser.add_argument("--noapproval", type=str2bool, nargs='?', const=True, default=False, help="Allow followers without approval") @@ -2292,6 +2297,11 @@ verifyAllSignatures = \ if verifyAllSignatures is not None: args.verifyAllSignatures = bool(verifyAllSignatures) +brochMode = \ + getConfigParam(baseDir, 'brochMode') +if brochMode is not None: + args.brochMode = bool(brochMode) + YTDomain = getConfigParam(baseDir, 'youtubedomain') if YTDomain: if '://' in YTDomain: @@ -2305,7 +2315,8 @@ if setTheme(baseDir, themeName, domain, args.allowLocalNetworkAccess): print('Theme set to ' + themeName) if __name__ == "__main__": - runDaemon(args.verifyAllSignatures, + runDaemon(args.brochMode, + args.verifyAllSignatures, args.sendThreadsTimeoutMins, args.dormantMonths, args.maxNewswirePosts, diff --git a/inbox.py b/inbox.py index 8f216e1c6..86507b87d 100644 --- a/inbox.py +++ b/inbox.py @@ -51,6 +51,7 @@ from bookmarks import updateBookmarksCollection from bookmarks import undoBookmarksCollectionEntry from blocking import isBlocked from blocking import isBlockedDomain +from blocking import brochModeLapses from filters import isFiltered from utils import updateAnnounceCollection from utils import undoAnnounceCollectionEntry @@ -2518,6 +2519,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int, # heartbeat to monitor whether the inbox queue is running heartBeatCtr += 5 if heartBeatCtr >= 10: + # turn off broch mode after it has timed out + brochModeLapses(baseDir) print('>>> Heartbeat Q:' + str(len(queue)) + ' ' + '{:%F %T}'.format(datetime.datetime.now())) heartBeatCtr = 0 diff --git a/outbox.py b/outbox.py index 2c3fda855..11d595c6e 100644 --- a/outbox.py +++ b/outbox.py @@ -14,6 +14,7 @@ from posts import outboxMessageCreateWrap from posts import savePostToBox from posts import sendToFollowersThread from posts import sendToNamedAddresses +from utils import getLocalNetworkAddresses from utils import getFullDomain from utils import removeIdEnding from utils import getDomainFromActor @@ -114,6 +115,23 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str, 'Create does not have the "to" parameter ' + str(messageJson)) return False + + # actor should be a string + if not isinstance(messageJson['actor'], str): + return False + + # actor should look like a url + if '://' not in messageJson['actor'] or \ + '.' not in messageJson['actor']: + return False + + # sent by an actor on a local network address? + if not allowLocalNetworkAccess: + localNetworkPatternList = getLocalNetworkAddresses() + for localNetworkPattern in localNetworkPatternList: + if localNetworkPattern in messageJson['actor']: + return False + testDomain, testPort = getDomainFromActor(messageJson['actor']) testDomain = getFullDomain(testDomain, testPort) if isBlockedDomain(baseDir, testDomain): diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..6819ec0fd --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/python3 + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/tests.py b/tests.py index 7186012a1..5a4bd233e 100644 --- a/tests.py +++ b/tests.py @@ -325,8 +325,10 @@ def createServerAlice(path: str, domain: str, port: int, sendThreadsTimeoutMins = 30 maxFollowers = 10 verifyAllSignatures = True + brochMode = False print('Server running: Alice') - runDaemon(verifyAllSignatures, + runDaemon(brochMode, + verifyAllSignatures, sendThreadsTimeoutMins, dormantMonths, maxNewswirePosts, allowLocalNetworkAccess, @@ -420,8 +422,10 @@ def createServerBob(path: str, domain: str, port: int, sendThreadsTimeoutMins = 30 maxFollowers = 10 verifyAllSignatures = True + brochMode = False print('Server running: Bob') - runDaemon(verifyAllSignatures, + runDaemon(brochMode, + verifyAllSignatures, sendThreadsTimeoutMins, dormantMonths, maxNewswirePosts, allowLocalNetworkAccess, @@ -469,8 +473,10 @@ def createServerEve(path: str, domain: str, port: int, federationList: [], sendThreadsTimeoutMins = 30 maxFollowers = 10 verifyAllSignatures = True + brochMode = False print('Server running: Eve') - runDaemon(verifyAllSignatures, + runDaemon(brochMode, + verifyAllSignatures, sendThreadsTimeoutMins, dormantMonths, maxNewswirePosts, allowLocalNetworkAccess, diff --git a/translations/ar.json b/translations/ar.json index 605d5b98b..fd4f8fbbe 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -368,5 +368,6 @@ "Skip to Newswire": "انتقل إلى Newswire", "Skip to Links": "تخطي إلى روابط الويب", "Publish a blog article": "نشر مقال بلوق", - "Featured writer": "كاتب متميز" + "Featured writer": "كاتب متميز", + "Broch mode": "وضع الكتيب" } diff --git a/translations/ca.json b/translations/ca.json index 3c8846bbf..2965f88d8 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Vés a Newswire", "Skip to Links": "Vés als enllaços web", "Publish a blog article": "Publicar un article del bloc", - "Featured writer": "Escriptor destacat" + "Featured writer": "Escriptor destacat", + "Broch mode": "Mode Broch" } diff --git a/translations/cy.json b/translations/cy.json index 6e45de39d..f1e31b718 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Neidio i Newswire", "Skip to Links": "Neidio i Dolenni Gwe", "Publish a blog article": "Cyhoeddi erthygl blog", - "Featured writer": "Awdur dan sylw" + "Featured writer": "Awdur dan sylw", + "Broch mode": "Modd Broch" } diff --git a/translations/de.json b/translations/de.json index c15cb492f..4053a453c 100644 --- a/translations/de.json +++ b/translations/de.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Springe zu Newswire", "Skip to Links": "Springe zu Weblinks", "Publish a blog article": "Veröffentlichen Sie einen Blog-Artikel", - "Featured writer": "Ausgewählter Schriftsteller" + "Featured writer": "Ausgewählter Schriftsteller", + "Broch mode": "Broch-Modus" } diff --git a/translations/en.json b/translations/en.json index 3fbbfdd34..d376154ee 100644 --- a/translations/en.json +++ b/translations/en.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Skip to Newswire", "Skip to Links": "Skip to Links", "Publish a blog article": "Publish a blog article", - "Featured writer": "Featured writer" + "Featured writer": "Featured writer", + "Broch mode": "Broch mode" } diff --git a/translations/es.json b/translations/es.json index 3566d06b0..135ff9c13 100644 --- a/translations/es.json +++ b/translations/es.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Saltar a Newswire", "Skip to Links": "Saltar a enlaces web", "Publish a blog article": "Publica un artículo de blog", - "Featured writer": "Escritora destacada" + "Featured writer": "Escritora destacada", + "Broch mode": "Modo broche" } diff --git a/translations/fr.json b/translations/fr.json index 78c7ff547..308ba5e9b 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Passer à Newswire", "Skip to Links": "Passer aux liens Web", "Publish a blog article": "Publier un article de blog", - "Featured writer": "Écrivain en vedette" + "Featured writer": "Écrivain en vedette", + "Broch mode": "Mode Broch" } diff --git a/translations/ga.json b/translations/ga.json index 75f87780c..92f67c50f 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Scipeáil chuig Newswire", "Skip to Links": "Scipeáil chuig Naisc Ghréasáin", "Publish a blog article": "Foilsigh alt blagála", - "Featured writer": "Scríbhneoir mór le rá" + "Featured writer": "Scríbhneoir mór le rá", + "Broch mode": "Modh broch" } diff --git a/translations/hi.json b/translations/hi.json index acf417f88..82c401422 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Newswire पर जाएं", "Skip to Links": "वेब लिंक पर जाएं", "Publish a blog article": "एक ब्लॉग लेख प्रकाशित करें", - "Featured writer": "फीचर्ड लेखक" + "Featured writer": "फीचर्ड लेखक", + "Broch mode": "ब्रोच मोड" } diff --git a/translations/it.json b/translations/it.json index f2bc1e1ed..c189c2335 100644 --- a/translations/it.json +++ b/translations/it.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Passa a Newswire", "Skip to Links": "Passa a collegamenti Web", "Publish a blog article": "Pubblica un articolo sul blog", - "Featured writer": "Scrittore in primo piano" + "Featured writer": "Scrittore in primo piano", + "Broch mode": "Modalità Broch" } diff --git a/translations/ja.json b/translations/ja.json index 9f1275544..49bfffbef 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Newswireにスキップ", "Skip to Links": "Webリンクにスキップ", "Publish a blog article": "ブログ記事を公開する", - "Featured writer": "注目の作家" + "Featured writer": "注目の作家", + "Broch mode": "ブロッホモード" } diff --git a/translations/oc.json b/translations/oc.json index c9e3717a0..fa4620d9a 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -364,5 +364,6 @@ "Skip to Newswire": "Skip to Newswire", "Skip to Links": "Skip to Links", "Publish a blog article": "Publish a blog article", - "Featured writer": "Featured writer" + "Featured writer": "Featured writer", + "Broch mode": "Broch mode" } diff --git a/translations/pt.json b/translations/pt.json index b2a29ca2d..52694082b 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Pular para Newswire", "Skip to Links": "Pular para links da web", "Publish a blog article": "Publique um artigo de blog", - "Featured writer": "Escritor em destaque" + "Featured writer": "Escritor em destaque", + "Broch mode": "Modo broch" } diff --git a/translations/ru.json b/translations/ru.json index 618c5cbf0..41c2d5a91 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -368,5 +368,6 @@ "Skip to Newswire": "Перейти к ленте новостей", "Skip to Links": "Перейти к веб-ссылкам", "Publish a blog article": "Опубликовать статью в блоге", - "Featured writer": "Избранный писатель" + "Featured writer": "Избранный писатель", + "Broch mode": "Брош режим" } diff --git a/translations/zh.json b/translations/zh.json index e7fab5755..f63deb616 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -368,5 +368,6 @@ "Skip to Newswire": "跳到新闻专线", "Skip to Links": "跳到网页链接", "Publish a blog article": "发布博客文章", - "Featured writer": "特色作家" + "Featured writer": "特色作家", + "Broch mode": "断点模式" } diff --git a/utils.py b/utils.py index f1247e898..8f2348062 100644 --- a/utils.py +++ b/utils.py @@ -605,6 +605,12 @@ def urlPermitted(url: str, federationList: []): return False +def getLocalNetworkAddresses() -> []: + """Returns patterns for local network address detection + """ + return ('localhost', '127.0.', '192.168', '10.0.') + + def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool: """Returns true if the given content contains dangerous html markup """ @@ -615,7 +621,7 @@ def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool: contentSections = content.split('<') invalidPartials = () if not allowLocalNetworkAccess: - invalidPartials = ('localhost', '127.0.', '192.168', '10.0.') + invalidPartials = getLocalNetworkAddresses() invalidStrings = ('script', 'canvas', 'style', 'abbr', 'frame', 'iframe', 'html', 'body', 'hr', 'allow-popups', 'allow-scripts') diff --git a/webapp_profile.py b/webapp_profile.py index 8c7704390..0d2fe362d 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -1278,6 +1278,16 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, ' ' + \ translate['Verify all signatures'] + '
\n' + if getConfigParam(baseDir, "brochMode"): + instanceStr += \ + ' ' + \ + translate['Broch mode'] + '
\n' + else: + instanceStr += \ + ' ' + \ + translate['Broch mode'] + '
\n' instanceStr += '' moderators = '' diff --git a/webapp_timeline.py b/webapp_timeline.py index 0c0407520..b5a9a41b0 100644 --- a/webapp_timeline.py +++ b/webapp_timeline.py @@ -416,19 +416,19 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, translate['Mod'] navLinks = { menuProfile: '/users/' + nickname, - menuInbox: usersPath + '/inbox#timeline', + menuInbox: usersPath + '/inbox#timelineposts', menuSearch: usersPath + '/search', menuNewPost: usersPath + '/newpost', menuCalendar: usersPath + '/calendar', - menuDM: usersPath + '/dm#timeline', - menuReplies: usersPath + '/tlreplies#timeline', - menuOutbox: usersPath + '/inbox#timeline', - menuBookmarks: usersPath + '/tlbookmarks#timeline', - menuShares: usersPath + '/tlshares#timeline', - menuBlogs: usersPath + '/tlblogs#timeline', - # menuEvents: usersPath + '/tlevents#timeline', - menuNewswire: '#newswire', - menuLinks: '#links' + menuDM: usersPath + '/dm#timelineposts', + menuReplies: usersPath + '/tlreplies#timelineposts', + menuOutbox: usersPath + '/inbox#timelineposts', + menuBookmarks: usersPath + '/tlbookmarks#timelineposts', + menuShares: usersPath + '/tlshares#timelineposts', + menuBlogs: usersPath + '/tlblogs#timelineposts', + # menuEvents: usersPath + '/tlevents#timelineposts', + menuNewswire: usersPath + '/newswiremobile', + menuLinks: usersPath + '/linksmobile' } if moderator: navLinks[menuModeration] = usersPath + '/moderation#modtimeline' @@ -502,7 +502,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, calendarImage, followApprovals, iconsAsButtons) - tlStr += '
\n' + tlStr += '
\n' # second row of buttons for moderator actions if moderator and boxName == 'moderation': diff --git a/webapp_utils.py b/webapp_utils.py index 014b42a19..64b1cae89 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -892,26 +892,26 @@ def htmlKeyboardNavigation(banner: str, links: {}, followApprovals=False) -> str: """Given a set of links return the html for keyboard navigation """ - htmlStr = '
    ' + htmlStr = '
      \n' if banner: - htmlStr += '
      ' + banner + '

      ' + htmlStr += '
      \n' + banner + '\n

      \n' if subHeading: htmlStr += '
      ' + subHeading + '
      \n' # show new follower approvals if usersPath and translate and followApprovals: htmlStr += '

      ' + '

      \n' # show the list of links for title, url in links.items(): htmlStr += '
    • ' - htmlStr += '
    ' + str(title) + '\n' + htmlStr += '
\n' return htmlStr diff --git a/website/EN/index.html b/website/EN/index.html index 6ec59cc09..98e6e9b34 100644 --- a/website/EN/index.html +++ b/website/EN/index.html @@ -1,6 +1,9 @@ + + + + + Epicyon ActivityPub server +