From a23f47cdf3f5d2e8628481802f0cf47e23a8fa59 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 11:27:12 +0000 Subject: [PATCH 01/25] Keep track of when people that you are following were last seen --- inbox.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/inbox.py b/inbox.py index 8969bc4b5..c8d71b1fd 100644 --- a/inbox.py +++ b/inbox.py @@ -67,6 +67,7 @@ from followingCalendar import receivingCalendarEvents from content import dangerousMarkup from happening import saveEventPost from delete import removeOldHashtags +from follow import isFollowingActor def guessHashtagCategory(tagName: str, hashtagCategories: {}) -> str: @@ -2066,6 +2067,30 @@ def inboxUpdateIndex(boxname: str, baseDir: str, handle: str, return False +def updateLastSeen(baseDir: str, handle: str, actor: str) -> None: + """Updates the time when the given handle last saw the given actor + """ + if '@' not in handle: + return + nickname = handle.split('@')[0] + domain = handle.split('@')[1] + if ':' in domain: + domain = domain.split(':')[0] + accountPath = baseDir + '/accounts/' + nickname + '@' + domain + if not os.path.isdir(accountPath): + return + if not isFollowingActor(baseDir, nickname, domain, actor): + return + lastSeenPath = accountPath + '/lastseen' + if not os.path.isdir(lastSeenPath): + os.mkdir(lastSeenPath) + lastSeenFilename = lastSeenPath + '/' + actor.replace('/', '#') + '.txt' + currTime = datetime.datetime.utcnow() + daysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days + with open(lastSeenFilename, 'w+') as lastSeenFile: + lastSeenFile.write(str(daysSinceEpoch)) + + def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, session, keyId: str, handle: str, messageJson: {}, baseDir: str, httpPrefix: str, sendThreads: [], @@ -2086,6 +2111,8 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, if '#' in actor: actor = keyId.split('#')[0] + updateLastSeen(baseDir, handle, actor) + isGroup = groupHandle(baseDir, handle) if receiveLike(recentPostsCache, From 1cf2ea73f9746399b6b314aa57ad83629adce2fb Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 11:28:23 +0000 Subject: [PATCH 02/25] Comment --- inbox.py | 1 + 1 file changed, 1 insertion(+) diff --git a/inbox.py b/inbox.py index c8d71b1fd..5a86bfb71 100644 --- a/inbox.py +++ b/inbox.py @@ -2069,6 +2069,7 @@ def inboxUpdateIndex(boxname: str, baseDir: str, handle: str, def updateLastSeen(baseDir: str, handle: str, actor: str) -> None: """Updates the time when the given handle last saw the given actor + This can later be used to indicate if accounts are dormant/abandoned/moved """ if '@' not in handle: return From 9ba729c6fd9b48966e541070af5417370375ba59 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 11:31:16 +0000 Subject: [PATCH 03/25] Fix unit test --- tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.py b/tests.py index 3af8473b3..f856cf750 100644 --- a/tests.py +++ b/tests.py @@ -1683,7 +1683,7 @@ def testWebLinks(): 'This post has a web links https://somesite.net\n\nAnd some other text' linkedText = addWebLinks(exampleText) assert \ - 'somesite.net Date: Sun, 13 Dec 2020 12:44:17 +0000 Subject: [PATCH 04/25] Mark dormant followed accounts on profile --- daemon.py | 13 ++++++++++++- epicyon.py | 8 +++++++- tests.py | 12 +++++++++--- utils.py | 27 +++++++++++++++++++++++++++ webapp_profile.py | 29 +++++++++++++++++++++-------- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/daemon.py b/daemon.py index 3789becc2..8ae2d41ab 100644 --- a/daemon.py +++ b/daemon.py @@ -6491,6 +6491,7 @@ class PubServer(BaseHTTPRequestHandler): YTReplacementDomain, self.server.showPublishedDateOnly, self.server.newswire, + self.server.dormantMonths, actorJson['roles'], None, None) msg = msg.encode('utf-8') @@ -6570,6 +6571,7 @@ class PubServer(BaseHTTPRequestHandler): YTReplacementDomain, showPublishedDateOnly, self.server.newswire, + self.server.dormantMonths, actorJson['skills'], None, None) msg = msg.encode('utf-8') @@ -8258,6 +8260,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.YTReplacementDomain, self.server.showPublishedDateOnly, self.server.newswire, + self.server.dormantMonths, shares, pageNumber, sharesPerPage) msg = msg.encode('utf-8') @@ -8349,6 +8352,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.YTReplacementDomain, self.server.showPublishedDateOnly, self.server.newswire, + self.server.dormantMonths, following, pageNumber, followsPerPage).encode('utf-8') @@ -8440,6 +8444,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.YTReplacementDomain, self.server.showPublishedDateOnly, self.server.newswire, + self.server.dormantMonths, followers, pageNumber, followsPerPage).encode('utf-8') @@ -8506,6 +8511,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.YTReplacementDomain, self.server.showPublishedDateOnly, self.server.newswire, + self.server.dormantMonths, None, None).encode('utf-8') self._set_headers('text/html', len(msg), cookie, callingDomain) @@ -12926,7 +12932,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None: tokensLookup[token] = nickname -def runDaemon(maxNewswirePosts: int, +def runDaemon(dormantMonths: int, + maxNewswirePosts: int, allowLocalNetworkAccess: bool, maxFeedItemSizeKb: int, publishButtonAtTop: bool, @@ -13120,6 +13127,10 @@ def runDaemon(maxNewswirePosts: int, # maximum size of a hashtag category, in K httpd.maxCategoriesFeedItemSizeKb = 1024 + # how many months does a followed account need to be unseen + # for it to be considered dormant? + httpd.dormantMonths = dormantMonths + if registration == 'open': httpd.registration = True else: diff --git a/epicyon.py b/epicyon.py index 37df367e9..a9bfef608 100644 --- a/epicyon.py +++ b/epicyon.py @@ -116,6 +116,11 @@ parser.add_argument('--postsPerSource', dest='maxNewswirePostsPerSource', type=int, default=4, help='Maximum newswire posts per feed or account') +parser.add_argument('--dormantMonths', + dest='dormantMonths', type=int, + default=3, + help='How many months does a followed account need to ' + + 'be unseen for before being considered dormant') parser.add_argument('--maxNewswirePosts', dest='maxNewswirePosts', type=int, default=20, @@ -2080,7 +2085,8 @@ if setTheme(baseDir, themeName, domain, args.allowLocalNetworkAccess): print('Theme set to ' + themeName) if __name__ == "__main__": - runDaemon(args.maxNewswirePosts, + runDaemon(args.dormantMonths, + args.maxNewswirePosts, args.allowLocalNetworkAccess, args.maxFeedItemSizeKb, args.publishButtonAtTop, diff --git a/tests.py b/tests.py index f856cf750..c6c087707 100644 --- a/tests.py +++ b/tests.py @@ -296,8 +296,10 @@ def createServerAlice(path: str, domain: str, port: int, i2pDomain = None allowLocalNetworkAccess = True maxNewswirePosts = 20 + dormantMonths = 3 print('Server running: Alice') - runDaemon(maxNewswirePosts, allowLocalNetworkAccess, + runDaemon(dormantMonths, maxNewswirePosts, + allowLocalNetworkAccess, 2048, False, True, False, False, True, 10, False, 0, 100, 1024, 5, False, 0, False, 1, False, False, False, @@ -364,8 +366,10 @@ def createServerBob(path: str, domain: str, port: int, i2pDomain = None allowLocalNetworkAccess = True maxNewswirePosts = 20 + dormantMonths = 3 print('Server running: Bob') - runDaemon(maxNewswirePosts, allowLocalNetworkAccess, + runDaemon(dormantMonths, maxNewswirePosts, + allowLocalNetworkAccess, 2048, False, True, False, False, True, 10, False, 0, 100, 1024, 5, False, 0, False, 1, False, False, False, @@ -406,8 +410,10 @@ def createServerEve(path: str, domain: str, port: int, federationList: [], i2pDomain = None allowLocalNetworkAccess = True maxNewswirePosts = 20 + dormantMonths = 3 print('Server running: Eve') - runDaemon(maxNewswirePosts, allowLocalNetworkAccess, + runDaemon(dormantMonths, maxNewswirePosts, + allowLocalNetworkAccess, 2048, False, True, False, False, True, 10, False, 0, 100, 1024, 5, False, 0, False, 1, False, False, False, diff --git a/utils.py b/utils.py index b02b71936..b79f760a2 100644 --- a/utils.py +++ b/utils.py @@ -19,6 +19,33 @@ from calendar import monthrange from followingCalendar import addPersonToCalendar +def isDormant(baseDir: str, nickname: str, domain: str, actor: str, + dormantMonths=3) -> bool: + """Is the given followed actor dormant, from the standpoint + of the given account + """ + lastSeenFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + \ + '/lastseen/' + actor.replace('/', '#') + '.txt' + + if not os.path.isfile(lastSeenFilename): + return False + + with open(lastSeenFilename, 'r') as lastSeenFile: + daysSinceEpochStr = lastSeenFile.read() + if not daysSinceEpochStr: + return False + if not daysSinceEpochStr.isdigit(): + return False + currTime = datetime.datetime.utcnow() + currDaysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days + timeDiffMonths = \ + int((currDaysSinceEpoch - int(daysSinceEpochStr)) / 30) + if timeDiffMonths >= dormantMonths: + return True + return False + + def getHashtagCategory(baseDir: str, hashtag: str) -> str: """Returns the category for the hashtag """ diff --git a/webapp_profile.py b/webapp_profile.py index a6985d1f9..b8a1eb816 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -8,6 +8,7 @@ __status__ = "Production" import os from pprint import pprint +from utils import isDormant from utils import getNicknameFromActor from utils import getDomainFromActor from utils import isSystemAccount @@ -364,8 +365,9 @@ def htmlProfile(rssIconAtTop: bool, session, wfRequest: {}, personCache: {}, YTReplacementDomain: str, showPublishedDateOnly: bool, - newswire: {}, extraJson=None, - pageNumber=None, maxItemsPerPage=None) -> str: + newswire: {}, dormantMonths: int, + extraJson=None, pageNumber=None, + maxItemsPerPage=None) -> str: """Show the profile page as html """ nickname = profileJson['preferredUsername'] @@ -628,7 +630,8 @@ def htmlProfile(rssIconAtTop: bool, domain, port, session, wfRequest, personCache, extraJson, projectVersion, ["unfollow"], selected, - usersPath, pageNumber, maxItemsPerPage) + usersPath, pageNumber, maxItemsPerPage, + dormantMonths) elif selected == 'followers': profileStr += \ htmlProfileFollowing(translate, baseDir, httpPrefix, @@ -637,7 +640,7 @@ def htmlProfile(rssIconAtTop: bool, wfRequest, personCache, extraJson, projectVersion, ["block"], selected, usersPath, pageNumber, - maxItemsPerPage) + maxItemsPerPage, dormantMonths) elif selected == 'roles': profileStr += \ htmlProfileRoles(translate, nickname, domainFull, @@ -719,7 +722,8 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, buttons: [], feedName: str, actor: str, pageNumber: int, - maxItemsPerPage: int) -> str: + maxItemsPerPage: int, + dormantMonths: int) -> str: """Shows following on the profile screen """ profileStr = '' @@ -737,12 +741,18 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, translate['Page up'] + '">\n' + \ ' \n' - for item in followingJson['orderedItems']: + for followingActor in followingJson['orderedItems']: + dormant = False + if feedName == 'following': + dormant = \ + isDormant(baseDir, nickname, domain, followingActor, + dormantMonths) profileStr += \ individualFollowAsHtml(translate, baseDir, session, wfRequest, personCache, - domain, item, authorized, nickname, - httpPrefix, projectVersion, + domain, followingActor, + authorized, nickname, + httpPrefix, projectVersion, dormant, buttons) if authorized and maxItemsPerPage and pageNumber: if len(followingJson['orderedItems']) >= maxItemsPerPage: @@ -1436,12 +1446,15 @@ def individualFollowAsHtml(translate: {}, actorNickname: str, httpPrefix: str, projectVersion: str, + dormant: bool, buttons=[]) -> str: """An individual follow entry on the profile screen """ nickname = getNicknameFromActor(followUrl) domain, port = getDomainFromActor(followUrl) titleStr = '@' + nickname + '@' + domain + if dormant: + titleStr += '💤' avatarUrl = getPersonAvatarUrl(baseDir, followUrl, personCache, True) if not avatarUrl: avatarUrl = followUrl + '/avatar.png' From 7905c9b35dff4fc1cf796b46e85c20e62fe050e0 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 12:45:29 +0000 Subject: [PATCH 05/25] Only show dormant status if authorized --- webapp_profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp_profile.py b/webapp_profile.py index b8a1eb816..b1651d278 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -743,7 +743,7 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, for followingActor in followingJson['orderedItems']: dormant = False - if feedName == 'following': + if authorized and feedName == 'following': dormant = \ isDormant(baseDir, nickname, domain, followingActor, dormantMonths) From e4c34ec6ad5a0daf52176dfb952cef2c02d3258b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 12:48:04 +0000 Subject: [PATCH 06/25] Comment --- webapp_profile.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webapp_profile.py b/webapp_profile.py index b1651d278..c2c5ac9c7 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -742,11 +742,13 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, ' \n' for followingActor in followingJson['orderedItems']: + # is this a dormant followed account? dormant = False if authorized and feedName == 'following': dormant = \ isDormant(baseDir, nickname, domain, followingActor, dormantMonths) + profileStr += \ individualFollowAsHtml(translate, baseDir, session, wfRequest, personCache, @@ -754,6 +756,7 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, authorized, nickname, httpPrefix, projectVersion, dormant, buttons) + if authorized and maxItemsPerPage and pageNumber: if len(followingJson['orderedItems']) >= maxItemsPerPage: # page down arrow From 9ed343865d3dee33e91b12dd754e70f003ba3f14 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 12:57:57 +0000 Subject: [PATCH 07/25] Show dormant status on person options screen --- daemon.py | 3 ++- webapp_person_options.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/daemon.py b/daemon.py index 8ae2d41ab..3002e21c7 100644 --- a/daemon.py +++ b/daemon.py @@ -4987,7 +4987,8 @@ class PubServer(BaseHTTPRequestHandler): ssbAddress, blogAddress, toxAddress, jamiAddress, PGPpubKey, PGPfingerprint, - emailAddress).encode('utf-8') + emailAddress, + self.server.dormantMonths).encode('utf-8') self._set_headers('text/html', len(msg), cookie, callingDomain) self._write(msg) diff --git a/webapp_person_options.py b/webapp_person_options.py index aa60ed3e1..f6f098e81 100644 --- a/webapp_person_options.py +++ b/webapp_person_options.py @@ -11,6 +11,7 @@ from shutil import copyfile from petnames import getPetName from person import isPersonSnoozed from posts import isModerator +from utils import isDormant from utils import removeHtml from utils import getDomainFromActor from utils import getNicknameFromActor @@ -39,7 +40,8 @@ def htmlPersonOptions(defaultTimeline: str, jamiAddress: str, PGPpubKey: str, PGPfingerprint: str, - emailAddress) -> str: + emailAddress: str, + dormantMonths: int) -> str: """Show options for a person: view/follow/block/report """ optionsDomain, optionsPort = getDomainFromActor(optionsActor) @@ -53,6 +55,7 @@ def htmlPersonOptions(defaultTimeline: str, copyfile(baseDir + '/accounts/options-background.jpg', baseDir + '/accounts/options-background.jpg') + dormant = False followStr = 'Follow' blockStr = 'Block' nickname = None @@ -66,6 +69,9 @@ def htmlPersonOptions(defaultTimeline: str, followerDomain, followerPort = getDomainFromActor(optionsActor) if isFollowingActor(baseDir, nickname, domain, optionsActor): followStr = 'Unfollow' + dormant = \ + isDormant(baseDir, nickname, domain, optionsActor, + dormantMonths) optionsNickname = getNicknameFromActor(optionsActor) optionsDomainFull = optionsDomain @@ -107,9 +113,12 @@ def htmlPersonOptions(defaultTimeline: str, optionsStr += ' \n' handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain + handleShown = handle + if dormant: + handleShown += '💤' optionsStr += \ '

' + translate['Options for'] + \ - ' @' + handle + '

\n' + ' @' + handleShown + '

\n' if emailAddress: optionsStr += \ '

' + translate['Email'] + \ From 2127252ca012a6fb7ee450818cba77472f108121 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 13:43:09 +0000 Subject: [PATCH 08/25] Add space --- utils.py | 7 ++----- webapp_person_options.py | 2 +- webapp_profile.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/utils.py b/utils.py index b79f760a2..120a02394 100644 --- a/utils.py +++ b/utils.py @@ -33,14 +33,11 @@ def isDormant(baseDir: str, nickname: str, domain: str, actor: str, with open(lastSeenFilename, 'r') as lastSeenFile: daysSinceEpochStr = lastSeenFile.read() - if not daysSinceEpochStr: - return False - if not daysSinceEpochStr.isdigit(): - return False + daysSinceEpoch = int(daysSinceEpoch) currTime = datetime.datetime.utcnow() currDaysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days timeDiffMonths = \ - int((currDaysSinceEpoch - int(daysSinceEpochStr)) / 30) + int((currDaysSinceEpoch - daysSinceEpochStr) / 30) if timeDiffMonths >= dormantMonths: return True return False diff --git a/webapp_person_options.py b/webapp_person_options.py index f6f098e81..b18d5460a 100644 --- a/webapp_person_options.py +++ b/webapp_person_options.py @@ -115,7 +115,7 @@ def htmlPersonOptions(defaultTimeline: str, handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain handleShown = handle if dormant: - handleShown += '💤' + handleShown += ' 💤' optionsStr += \ '

' + translate['Options for'] + \ ' @' + handleShown + '

\n' diff --git a/webapp_profile.py b/webapp_profile.py index c2c5ac9c7..21fdbc4e1 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -1457,7 +1457,7 @@ def individualFollowAsHtml(translate: {}, domain, port = getDomainFromActor(followUrl) titleStr = '@' + nickname + '@' + domain if dormant: - titleStr += '💤' + titleStr += ' 💤' avatarUrl = getPersonAvatarUrl(baseDir, followUrl, personCache, True) if not avatarUrl: avatarUrl = followUrl + '/avatar.png' From e727cc4c22dbab326a10ca0a43590af0ebd6d509 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 13:45:06 +0000 Subject: [PATCH 09/25] String --- utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils.py b/utils.py index 120a02394..84c001a24 100644 --- a/utils.py +++ b/utils.py @@ -33,11 +33,11 @@ def isDormant(baseDir: str, nickname: str, domain: str, actor: str, with open(lastSeenFilename, 'r') as lastSeenFile: daysSinceEpochStr = lastSeenFile.read() - daysSinceEpoch = int(daysSinceEpoch) + daysSinceEpoch = int(daysSinceEpochStr) currTime = datetime.datetime.utcnow() currDaysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days timeDiffMonths = \ - int((currDaysSinceEpoch - daysSinceEpochStr) / 30) + int((currDaysSinceEpoch - daysSinceEpoch) / 30) if timeDiffMonths >= dormantMonths: return True return False From e1123279c82e8d2cf3387604b5503a76ccd274c5 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 13:49:32 +0000 Subject: [PATCH 10/25] Dormant months can be set in config file --- epicyon.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/epicyon.py b/epicyon.py index a9bfef608..214605140 100644 --- a/epicyon.py +++ b/epicyon.py @@ -2037,6 +2037,11 @@ maxFeedItemSizeKb = \ if maxFeedItemSizeKb is not None: args.maxFeedItemSizeKb = int(maxFeedItemSizeKb) +dormantMonths = \ + getConfigParam(baseDir, 'dormantMonths') +if dormantMonths is not None: + args.dormantMonths = int(dormantMonths) + allowNewsFollowers = \ getConfigParam(baseDir, 'allowNewsFollowers') if allowNewsFollowers is not None: From f94f6eb997e3fec2fc3dc6828405f5dc5f17ce5b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 14:31:22 +0000 Subject: [PATCH 11/25] Don't write time to file if it hasn't changed --- inbox.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/inbox.py b/inbox.py index 5a86bfb71..0489aff7a 100644 --- a/inbox.py +++ b/inbox.py @@ -2088,6 +2088,13 @@ def updateLastSeen(baseDir: str, handle: str, actor: str) -> None: lastSeenFilename = lastSeenPath + '/' + actor.replace('/', '#') + '.txt' currTime = datetime.datetime.utcnow() daysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days + # has the value changed? + if os.path.isfile(lastSeenFilename): + with open(lastSeenFilename, 'r') as lastSeenFile: + daysSinceEpochFile = lastSeenFile.read() + if int(daysSinceEpochFile) == daysSinceEpoch: + # value hasn't changed, so we can save writing anything to file + return with open(lastSeenFilename, 'w+') as lastSeenFile: lastSeenFile.write(str(daysSinceEpoch)) From 121a24d432fb2b10089acbc07586a3966335dda4 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 14:48:45 +0000 Subject: [PATCH 12/25] Test that css is not dangerous --- tests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests.py b/tests.py index c6c087707..05cbd5b4d 100644 --- a/tests.py +++ b/tests.py @@ -75,6 +75,7 @@ from inbox import guessHashtagCategory from content import htmlReplaceEmailQuote from content import htmlReplaceQuoteMarks from content import dangerousMarkup +from content import dangerousCSS from content import addWebLinks from content import replaceEmojiFromTags from content import addHtmlTags @@ -1984,6 +1985,17 @@ def testRemoveHtml(): assert(removeHtml(testStr) == 'This string has html.') +def testDangerousCSS(): + print('testDangerousCSS') + baseDir = os.getcwd() + for subdir, dirs, files in os.walk(baseDir): + for f in files: + if not f.endswith('.css'): + continue + assert not dangerousCSS(baseDir + '/' + f, False) + break + + def testDangerousMarkup(): print('testDangerousMarkup') allowLocalNetworkAccess = False @@ -2483,6 +2495,7 @@ def runAllTests(): testRemoveIdEnding() testJsonPostAllowsComments() runHtmlReplaceQuoteMarks() + testDangerousCSS() testDangerousMarkup() testRemoveHtml() testSiteIsActive() From 3872426f3583babada9b8e2d702a63c222bea3a1 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 18:24:25 +0000 Subject: [PATCH 13/25] Ensure that mentions appear in cc --- posts.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/posts.py b/posts.py index 01d8b9fd6..115cbf344 100644 --- a/posts.py +++ b/posts.py @@ -898,6 +898,16 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int, if eventUUID: postObjectType = 'Event' + # public posts with mentions should include the mentioned + # actors within CC + # "I think we don’t notify about mentions unless the person + # is also addressed in to/cc" -- gargron + if toRecipients and toCC and mentionedRecipients: + if len(toRecipients) == 1 and len(toCC) == 1: + if toRecipients[0].endswith('#Public') and \ + toCC[0].endswith('/followers'): + toCC += mentionedRecipients + if not clientToServer: actorUrl = httpPrefix + '://' + domain + '/users/' + nickname From e9c0c4c3a97524c7bf8c0535ca0b8e2bb2900f3a Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 19:05:26 +0000 Subject: [PATCH 14/25] Unit test for getting mentions --- posts.py | 10 ---------- tests.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/posts.py b/posts.py index 115cbf344..01d8b9fd6 100644 --- a/posts.py +++ b/posts.py @@ -898,16 +898,6 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int, if eventUUID: postObjectType = 'Event' - # public posts with mentions should include the mentioned - # actors within CC - # "I think we don’t notify about mentions unless the person - # is also addressed in to/cc" -- gargron - if toRecipients and toCC and mentionedRecipients: - if len(toRecipients) == 1 and len(toCC) == 1: - if toRecipients[0].endswith('#Public') and \ - toCC[0].endswith('/followers'): - toCC += mentionedRecipients - if not clientToServer: actorUrl = httpPrefix + '://' + domain + '/users/' + nickname diff --git a/tests.py b/tests.py index 05cbd5b4d..a1befe72c 100644 --- a/tests.py +++ b/tests.py @@ -20,6 +20,7 @@ from cache import getPersonFromCache from threads import threadWithTrace from daemon import runDaemon from session import createSession +from posts import getMentionedPeople from posts import validContentWarning from posts import deleteAllPosts from posts import createPublicPost @@ -2479,8 +2480,23 @@ def testGuessHashtagCategory() -> None: assert guess == "bar" +def testGetMentionedPeople() -> None: + print('testGetMentionedPeople') + baseDir = os.getcwd() + + content = "@dragon@cave.site @bat@cave.site This is a test." + actors = getMentionedPeople(baseDir, 'https', + content, + 'mydomain', False) + assert actors + assert len(actors) == 2 + assert actors[0] == "https://cave.site/users/dragon" + assert actors[1] == "https://cave.site/users/bat" + + def runAllTests(): print('Running tests...') + testGetMentionedPeople() testGuessHashtagCategory() testValidNickname() testParseFeedDate() From 9d176ab77dd89bd3655a7e2e4aca7d962f76bb9e Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 19:53:31 +0000 Subject: [PATCH 15/25] Unit test for reply to public post --- tests.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests.py b/tests.py index a1befe72c..8329152d2 100644 --- a/tests.py +++ b/tests.py @@ -2494,8 +2494,41 @@ def testGetMentionedPeople() -> None: assert actors[1] == "https://cave.site/users/bat" +def testReplyToPublicPost() -> None: + baseDir = os.getcwd() + nickname = 'test7492362' + domain = 'other.site' + port = 443 + httpPrefix = 'https' + postId = httpPrefix + '://rat.site/users/ninjarodent/statuses/63746173435' + reply = \ + createPublicPost(baseDir, nickname, domain, port, httpPrefix, + "@ninjarodent@rat.site This is a test.", + False, False, False, True, + None, None, False, postId) + print(str(reply)) + assert reply['object']['content'] == \ + '

' + \ + '@ninjarodent' + \ + ' This is a test.

' + assert reply['object']['tag'][0]['type'] == 'Mention' + assert reply['object']['tag'][0]['name'] == '@ninjarodent@rat.site' + assert reply['object']['tag'][0]['href'] == \ + 'https://rat.site/users/ninjarodent' + assert len(reply['object']['to']) == 1 + assert reply['object']['to'][0].endswith('#Public') + assert len(reply['object']['cc']) >= 1 + assert reply['object']['cc'][0].endswith(nickname + '/followers') + assert len(reply['object']['tag']) == 1 + assert len(reply['object']['cc']) == 2 + assert reply['object']['cc'][1] == \ + httpPrefix + '://rat.site/users/ninjarodent' + + def runAllTests(): print('Running tests...') + testReplyToPublicPost() testGetMentionedPeople() testGuessHashtagCategory() testValidNickname() From 88b0a6aa6f11f91a2e8d1f7836ed6fa139bbfe58 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 20:07:45 +0000 Subject: [PATCH 16/25] Check if tag already exists --- content.py | 9 +++++++++ posts.py | 4 +++- tests.py | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/content.py b/content.py index 4de6effa4..be9afd3ae 100644 --- a/content.py +++ b/content.py @@ -508,6 +508,15 @@ def addEmoji(baseDir: str, wordStr: str, return True +def tagExists(tagType: str, tagName: str, tags: {}) -> bool: + """Returns true if a tag exists in the given dict + """ + for tag in tags: + if tag['name'] == tagName and tag['type'] == tagType: + return True + return False + + def addMention(wordStr: str, httpPrefix: str, following: str, replaceMentions: {}, recipients: [], tags: {}) -> bool: """Detects mentions and adds them to the replacements dict and diff --git a/posts.py b/posts.py index 01d8b9fd6..0d3715013 100644 --- a/posts.py +++ b/posts.py @@ -52,6 +52,7 @@ from utils import votesOnNewswireItem from utils import removeHtml from media import attachMedia from media import replaceYouTube +from content import tagExists from content import removeLongWords from content import addHtmlTags from content import replaceEmojiFromTags @@ -801,7 +802,8 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int, isPublic = True break for tagName, tag in hashtagsDict.items(): - tags.append(tag) + if not tagExists(tag['type'], tag['name'], tags): + tags.append(tag) if isPublic: updateHashtagsIndex(baseDir, tag, newPostId) print('Content tags: ' + str(tags)) diff --git a/tests.py b/tests.py index 8329152d2..fc8d9d739 100644 --- a/tests.py +++ b/tests.py @@ -2506,7 +2506,7 @@ def testReplyToPublicPost() -> None: "@ninjarodent@rat.site This is a test.", False, False, False, True, None, None, False, postId) - print(str(reply)) + # print(str(reply)) assert reply['object']['content'] == \ '

' + \ ' Date: Sun, 13 Dec 2020 20:30:08 +0000 Subject: [PATCH 17/25] Fix reply unit test --- posts.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/posts.py b/posts.py index 0d3715013..b5c3cd3b1 100644 --- a/posts.py +++ b/posts.py @@ -1012,6 +1012,16 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int, if newPost.get('object'): newPost['object']['cc'] = [ccUrl] + # if this is a public post then include any mentions in cc + toCC = newPost['object']['cc'] + if len(toRecipients) == 1: + if toRecipients[0].endswith('#Public') and \ + ccUrl.endswith('/followers'): + for tag in tags: + if tag['type'] == 'Mention': + if tag['href'] not in toCC: + toCC.append(tag['href']) + # if this is a moderation report then add a status if isModerationReport: # add status From 12cccfdeb6679601aefd8efc9cffc77cfcc82260 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:01:10 +0000 Subject: [PATCH 18/25] Populate last seen files on startup --- daemon.py | 3 +++ follow.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/daemon.py b/daemon.py index 3002e21c7..0465c4696 100644 --- a/daemon.py +++ b/daemon.py @@ -89,6 +89,7 @@ from inbox import getPersonPubKey from follow import getFollowingFeed from follow import sendFollowRequest from follow import unfollowPerson +from follow import createInitialLastSeen from auth import authorize from auth import createPassword from auth import createBasicAuthHeader @@ -13261,6 +13262,8 @@ def runDaemon(dormantMonths: int, httpd.iconsCache = {} httpd.fontsCache = {} + createInitialLastSeen(baseDir, httpPrefix) + print('Creating inbox queue') httpd.thrInboxQueue = \ threadWithTrace(target=runInboxQueue, diff --git a/follow.py b/follow.py index 9403f92ea..53349f050 100644 --- a/follow.py +++ b/follow.py @@ -27,6 +27,42 @@ from auth import createBasicAuthHeader from session import postJson +def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None: + """Creates initial lastseen files for all follows + """ + 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) + followingFilename = accountDir + '/following.txt' + if not os.path.isfile(followingFilename): + continue + lastSeenDir = accountDir + '/lastseen' + if not os.path.isdir(lastSeenDir): + os.mkdir(lastSeenDir) + with open(followingFilename, 'r') as fp: + followingHandles = fp.readlines() + for handle in followingHandles: + if '#' in handle: + continue + if '@' not in handle: + continue + nickname = handle.split('@')[0] + domain = handle.split('@')[1] + actor = \ + httpPrefix + '://' + \ + nickname + '@' + domain + '/users/' + nickname + lastSeenFilename = \ + lastSeenDir + '/' + actor.replace('/', '#') + '.txt' + if not os.path.isfile(lastSeenFilename): + with open(lastSeenFilename, 'w+') as fp: + fp.write(str(0)) + break + + def preApprovedFollower(baseDir: str, nickname: str, domain: str, approveHandle: str, From e87fd5a168f90c7edcfe998fe5067e94725c9591 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:13:45 +0000 Subject: [PATCH 19/25] Break after loop --- blog.py | 4 ++++ daemon.py | 3 +++ delete.py | 1 + devices.py | 1 + follow.py | 1 + inbox.py | 4 ++++ media.py | 1 + migrate.py | 1 + newswire.py | 1 + posts.py | 1 + schedule.py | 1 + shares.py | 2 ++ theme.py | 1 + utils.py | 6 ++++++ webapp_hashtagswarm.py | 1 + webapp_search.py | 3 +++ webapp_utils.py | 1 + 17 files changed, 33 insertions(+) diff --git a/blog.py b/blog.py index 418821163..3216f9b8a 100644 --- a/blog.py +++ b/blog.py @@ -622,6 +622,7 @@ def getBlogIndexesForAccounts(baseDir: str) -> {}: blogsIndex = accountDir + '/tlblogs.index' if os.path.isfile(blogsIndex): blogIndexes[acct] = blogsIndex + break return blogIndexes @@ -639,6 +640,7 @@ def noOfBlogAccounts(baseDir: str) -> int: blogsIndex = accountDir + '/tlblogs.index' if os.path.isfile(blogsIndex): ctr += 1 + break return ctr @@ -655,6 +657,7 @@ def singleBlogAccountNickname(baseDir: str) -> str: blogsIndex = accountDir + '/tlblogs.index' if os.path.isfile(blogsIndex): return acct.split('@')[0] + break return None @@ -698,6 +701,7 @@ def htmlBlogView(authorized: bool, httpPrefix + '://' + domainFull + '/blog/' + \ acct.split('@')[0] + '">' + acct + '' blogStr += '

' + break return blogStr + htmlFooter() diff --git a/daemon.py b/daemon.py index 0465c4696..cb8add174 100644 --- a/daemon.py +++ b/daemon.py @@ -4789,6 +4789,7 @@ class PubServer(BaseHTTPRequestHandler): port, maxPostsInRSSFeed, 1, False) + break if msg: msg = rss2Header(httpPrefix, 'news', domainFull, @@ -12133,6 +12134,7 @@ class PubServer(BaseHTTPRequestHandler): contentJson = loadJson(deviceFilename) if contentJson: devicesList.append(contentJson) + break # return the list of devices for this handle msg = \ json.dumps(devicesList, @@ -12932,6 +12934,7 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None: continue tokensDict[nickname] = token tokensLookup[token] = nickname + break def runDaemon(dormantMonths: int, diff --git a/delete.py b/delete.py index 3b195db7e..58dbd5d28 100644 --- a/delete.py +++ b/delete.py @@ -321,6 +321,7 @@ def removeOldHashtags(baseDir: str, maxMonths: int) -> str: # check of the file is too old if fileDaysSinceEpoch < maxDaysSinceEpoch: removeHashtags.append(tagsFilename) + break for removeFilename in removeHashtags: try: diff --git a/devices.py b/devices.py index 69c8d2ca7..3f0ef52bc 100644 --- a/devices.py +++ b/devices.py @@ -152,6 +152,7 @@ def E2EEdevicesCollection(baseDir: str, nickname: str, domain: str, devJson = loadJson(deviceFilename) if devJson: deviceList.append(devJson) + break devicesDict = { 'id': personId + '/collections/devices', diff --git a/follow.py b/follow.py index 53349f050..a8b2bd89f 100644 --- a/follow.py +++ b/follow.py @@ -1203,6 +1203,7 @@ def getFollowersOfActor(baseDir: str, actor: str, debug: bool) -> {}: print('DEBUG: ' + account + ' follows ' + actorHandle) recipientsDict[account] = None + break return recipientsDict diff --git a/inbox.py b/inbox.py index 0489aff7a..4772a763b 100644 --- a/inbox.py +++ b/inbox.py @@ -200,6 +200,7 @@ def validInbox(baseDir: str, nickname: str, domain: str) -> bool: if 'postNickname' in open(filename).read(): print('queue file incorrectly saved to ' + filename) return False + break return True @@ -224,6 +225,7 @@ def validInboxFilenames(baseDir: str, nickname: str, domain: str, print('Expected: ' + expectedStr) print('Invalid filename: ' + filename) return False + break return True @@ -2471,6 +2473,7 @@ def clearQueueItems(baseDir: str, queue: []) -> None: ctr += 1 except BaseException: pass + break if ctr > 0: print('Removed ' + str(ctr) + ' inbox queue items') @@ -2487,6 +2490,7 @@ def restoreQueueItems(baseDir: str, queue: []) -> None: for queuesubdir, queuedirs, queuefiles in os.walk(queueDir): for qfile in queuefiles: queue.append(os.path.join(queueDir, qfile)) + break if len(queue) > 0: print('Restored ' + str(len(queue)) + ' inbox queue items') diff --git a/media.py b/media.py index 2163820d8..df6409f78 100644 --- a/media.py +++ b/media.py @@ -221,3 +221,4 @@ def archiveMedia(baseDir: str, archiveDirectory: str, maxWeeks=4) -> None: else: # archive to /dev/null rmtree(os.path.join(baseDir + '/media', weekDir)) + break diff --git a/migrate.py b/migrate.py index 6fb8bee28..4b04d8520 100644 --- a/migrate.py +++ b/migrate.py @@ -51,3 +51,4 @@ def migrateAccount(baseDir: str, oldHandle: str, newHandle: str) -> None: migrateFollows(followFilename, oldHandle, newHandle) followFilename = accountDir + '/followers.txt' migrateFollows(followFilename, oldHandle, newHandle) + break diff --git a/newswire.py b/newswire.py index 144e7854a..a71ee2bff 100644 --- a/newswire.py +++ b/newswire.py @@ -760,6 +760,7 @@ def addBlogsToNewswire(baseDir: str, domain: str, newswire: {}, addAccountBlogsToNewswire(baseDir, nickname, domain, newswire, maxBlogsPerAccount, blogsIndex, maxTags) + break # sort the moderation dict into chronological order, latest first sortedModerationDict = \ diff --git a/posts.py b/posts.py index b5c3cd3b1..b35c0505b 100644 --- a/posts.py +++ b/posts.py @@ -3195,6 +3195,7 @@ def archivePosts(baseDir: str, httpPrefix: str, archiveDir: str, archivePostsForPerson(httpPrefix, nickname, domain, baseDir, 'outbox', archiveSubdir, recentPostsCache, maxPostsInBox) + break def archivePostsForPerson(httpPrefix: str, nickname: str, domain: str, diff --git a/schedule.py b/schedule.py index 919ab2b70..c213c4ec5 100644 --- a/schedule.py +++ b/schedule.py @@ -146,6 +146,7 @@ def runPostSchedule(baseDir: str, httpd, maxScheduledPosts: int): if not os.path.isfile(scheduleIndexFilename): continue updatePostSchedule(baseDir, account, httpd, maxScheduledPosts) + break def runPostScheduleWatchdog(projectVersion: str, httpd) -> None: diff --git a/shares.py b/shares.py index 9c9cba297..d4b19b36e 100644 --- a/shares.py +++ b/shares.py @@ -167,6 +167,7 @@ def addShare(baseDir: str, '/users/' + nickname + '/tlshares') except BaseException: pass + break def expireShares(baseDir: str) -> None: @@ -179,6 +180,7 @@ def expireShares(baseDir: str) -> None: nickname = account.split('@')[0] domain = account.split('@')[1] expireSharesForAccount(baseDir, nickname, domain) + break def expireSharesForAccount(baseDir: str, nickname: str, domain: str) -> None: diff --git a/theme.py b/theme.py index 4f85a9d5c..bc34fe1c3 100644 --- a/theme.py +++ b/theme.py @@ -557,6 +557,7 @@ def setThemeImages(baseDir: str, name: str) -> None: os.remove(accountDir + '/right_col_image.png') except BaseException: pass + break def setNewsAvatar(baseDir: str, name: str, diff --git a/utils.py b/utils.py index 84c001a24..4fd05450d 100644 --- a/utils.py +++ b/utils.py @@ -110,6 +110,7 @@ def getHashtagCategories(baseDir: str, recent=False, category=None) -> None: else: if hashtag not in hashtagCategories[categoryStr]: hashtagCategories[categoryStr].append(hashtag) + break return hashtagCategories @@ -407,6 +408,7 @@ def getFollowersOfPerson(baseDir: str, if account not in followers: followers.append(account) break + break return followers @@ -932,6 +934,7 @@ def clearFromPostCaches(baseDir: str, recentPostsCache: {}, if recentPostsCache.get('html'): if recentPostsCache['html'].get(postId): del recentPostsCache['html'][postId] + break def locatePost(baseDir: str, nickname: str, domain: str, @@ -1195,6 +1198,7 @@ def noOfAccounts(baseDir: str) -> bool: if '@' in account: if not account.startswith('inbox@'): accountCtr += 1 + break return accountCtr @@ -1217,6 +1221,7 @@ def noOfActiveAccountsMonthly(baseDir: str, months: int) -> bool: timeDiff = (currTime - int(lastUsed)) if timeDiff < monthSeconds: accountCtr += 1 + break return accountCtr @@ -1493,6 +1498,7 @@ def searchBoxPosts(baseDir: str, nickname: str, domain: str, res.append(filePath) if len(res) >= maxResults: return res + break return res diff --git a/webapp_hashtagswarm.py b/webapp_hashtagswarm.py index 37e51060a..3dd92f54c 100644 --- a/webapp_hashtagswarm.py +++ b/webapp_hashtagswarm.py @@ -197,6 +197,7 @@ def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str: if categoryStr not in categorySwarm: categorySwarm.append(categoryStr) break + break if not tagSwarm: return '' diff --git a/webapp_search.py b/webapp_search.py index 378fa653d..13d8ea87a 100644 --- a/webapp_search.py +++ b/webapp_search.py @@ -256,6 +256,7 @@ def htmlSearchSharedItems(cssCache: {}, translate: {}, sharedItemsForm += '\n' break ctr = 0 + break if not resultsExist: sharedItemsForm += \ '
' + translate['No results'] + '
\n' @@ -428,6 +429,7 @@ def htmlSkillsSearch(actor: str, ';' + actorJson['icon']['url'] if indexStr not in results: results.append(indexStr) + break if not instanceOnly: # search actor cache for subdir, dirs, files in os.walk(baseDir + '/cache/actors/'): @@ -465,6 +467,7 @@ def htmlSkillsSearch(actor: str, ';' + actorJson['icon']['url'] if indexStr not in results: results.append(indexStr) + break results.sort(reverse=True) diff --git a/webapp_utils.py b/webapp_utils.py index ce54a0f49..f70d76036 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -429,6 +429,7 @@ def sharesTimelineJson(actor: str, pageNumber: int, itemsPerPage: int, ctr += 1 if ctr >= maxSharesPerAccount: break + break # sort the shared items in descending order of publication date sharesJson = OrderedDict(sorted(allSharesJson.items(), reverse=True)) lastPage = False From 498d9035112ad7cedef15985f9af591c6f473920 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:20:28 +0000 Subject: [PATCH 20/25] Remove newlines --- follow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/follow.py b/follow.py index a8b2bd89f..5fe8f9f69 100644 --- a/follow.py +++ b/follow.py @@ -51,7 +51,7 @@ def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None: if '@' not in handle: continue nickname = handle.split('@')[0] - domain = handle.split('@')[1] + domain = handle.split('@')[1].strip() actor = \ httpPrefix + '://' + \ nickname + '@' + domain + '/users/' + nickname From f3e0d4e17c73e5d555274fbcc55646a72296c8eb Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:23:39 +0000 Subject: [PATCH 21/25] Strip handle --- follow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/follow.py b/follow.py index 5fe8f9f69..952c1d6f0 100644 --- a/follow.py +++ b/follow.py @@ -50,8 +50,9 @@ def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None: continue if '@' not in handle: continue + handle = handle.strip() nickname = handle.split('@')[0] - domain = handle.split('@')[1].strip() + domain = handle.split('@')[1] actor = \ httpPrefix + '://' + \ nickname + '@' + domain + '/users/' + nickname From f1886ddd9c5912b4cbdf85d19f784e95c62c32ba Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:24:02 +0000 Subject: [PATCH 22/25] Remove newline --- follow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/follow.py b/follow.py index 952c1d6f0..d278f85e1 100644 --- a/follow.py +++ b/follow.py @@ -50,7 +50,7 @@ def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None: continue if '@' not in handle: continue - handle = handle.strip() + handle = handle.replace('\n', '') nickname = handle.split('@')[0] domain = handle.split('@')[1] actor = \ From 5fe459e656e8ea61be8ef8b7e46fe14d27aa805d Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:24:41 +0000 Subject: [PATCH 23/25] Debug --- follow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/follow.py b/follow.py index d278f85e1..3c8389a99 100644 --- a/follow.py +++ b/follow.py @@ -58,6 +58,7 @@ def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None: nickname + '@' + domain + '/users/' + nickname lastSeenFilename = \ lastSeenDir + '/' + actor.replace('/', '#') + '.txt' + print('lastSeenFilename: ' + lastSeenFilename) if not os.path.isfile(lastSeenFilename): with open(lastSeenFilename, 'w+') as fp: fp.write(str(0)) From a5597d1dcf03c686ea380f4fd6347ba16c3d929d Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:28:39 +0000 Subject: [PATCH 24/25] Non zero value --- follow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/follow.py b/follow.py index 3c8389a99..1ff251852 100644 --- a/follow.py +++ b/follow.py @@ -61,7 +61,7 @@ def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None: print('lastSeenFilename: ' + lastSeenFilename) if not os.path.isfile(lastSeenFilename): with open(lastSeenFilename, 'w+') as fp: - fp.write(str(0)) + fp.write(str(100)) break From 8d29ac3cbe9ca0ede7cca91bc7b61700a17307e1 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sun, 13 Dec 2020 22:32:13 +0000 Subject: [PATCH 25/25] Wrong actor url --- follow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/follow.py b/follow.py index 1ff251852..bf44bc83e 100644 --- a/follow.py +++ b/follow.py @@ -54,8 +54,7 @@ def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None: nickname = handle.split('@')[0] domain = handle.split('@')[1] actor = \ - httpPrefix + '://' + \ - nickname + '@' + domain + '/users/' + nickname + httpPrefix + '://' + domain + '/users/' + nickname lastSeenFilename = \ lastSeenDir + '/' + actor.replace('/', '#') + '.txt' print('lastSeenFilename: ' + lastSeenFilename)