diff --git a/blog.py b/blog.py index e1db7597..190d6146 100644 --- a/blog.py +++ b/blog.py @@ -282,7 +282,8 @@ def htmlBlogPostRSS2(authorized: bool, messageLink = postJsonObject['object']['id'].replace('/statuses/', '/') if not restrictToDomain or \ (restrictToDomain and '/' + domain in messageLink): - if postJsonObject['object'].get('summary'): + if postJsonObject['object'].get('summary') and \ + postJsonObject['object'].get('published'): published = postJsonObject['object']['published'] pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ") titleStr = postJsonObject['object']['summary'] @@ -307,7 +308,8 @@ def htmlBlogPostRSS3(authorized: bool, messageLink = postJsonObject['object']['id'].replace('/statuses/', '/') if not restrictToDomain or \ (restrictToDomain and '/' + domain in messageLink): - if postJsonObject['object'].get('summary'): + if postJsonObject['object'].get('summary') and \ + postJsonObject['object'].get('published'): published = postJsonObject['object']['published'] pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ") titleStr = postJsonObject['object']['summary'] diff --git a/daemon.py b/daemon.py index fe192e17..9bcd5e07 100644 --- a/daemon.py +++ b/daemon.py @@ -148,6 +148,7 @@ from webinterface import htmlTermsOfService from webinterface import htmlSkillsSearch from webinterface import htmlHistorySearch from webinterface import htmlHashtagSearch +from webinterface import rssHashtagSearch from webinterface import htmlModerationInfo from webinterface import htmlSearchSharedItems from webinterface import htmlHashtagBlocked @@ -4093,6 +4094,60 @@ class PubServer(BaseHTTPRequestHandler): 'login shown done', 'hashtag search') + def _hashtagSearchRSS2(self, callingDomain: str, + path: str, cookie: str, + baseDir: str, httpPrefix: str, + domain: str, domainFull: str, port: int, + onionDomain: str, i2pDomain: str, + GETstartTime, GETtimings: {}): + """Return an RSS 2 feed for a hashtag + """ + hashtag = path.split('/tags/rss2/')[1] + if isBlockedHashtag(baseDir, hashtag): + self._400() + self.server.GETbusy = False + return + nickname = None + if '/users/' in path: + actor = \ + httpPrefix + '://' + domainFull + path + nickname = \ + getNicknameFromActor(actor) + hashtagStr = \ + rssHashtagSearch(nickname, + domain, port, + self.server.recentPostsCache, + self.server.maxRecentPosts, + self.server.translate, + baseDir, hashtag, + maxPostsInFeed, self.server.session, + self.server.cachedWebfingers, + self.server.personCache, + httpPrefix, + self.server.projectVersion, + self.server.YTReplacementDomain) + if hashtagStr: + msg = hashtagStr.encode('utf-8') + self._set_headers('text/xml', len(msg), + cookie, callingDomain) + self._write(msg) + else: + originPathStr = path.split('/tags/rss2/')[0] + originPathStrAbsolute = \ + httpPrefix + '://' + domainFull + originPathStr + if callingDomain.endswith('.onion') and onionDomain: + originPathStrAbsolute = \ + 'http://' + onionDomain + originPathStr + elif (callingDomain.endswith('.i2p') and onionDomain): + originPathStrAbsolute = \ + 'http://' + i2pDomain + originPathStr + self._redirect_headers(originPathStrAbsolute + '/search', + cookie, callingDomain) + self.server.GETbusy = False + self._benchmarkGETtimings(GETstartTime, GETtimings, + 'login shown done', + 'hashtag rss feed') + def _announceButton(self, callingDomain: str, path: str, baseDir: str, cookie: str, proxyType: str, @@ -8068,6 +8123,18 @@ class PubServer(BaseHTTPRequestHandler): # hashtag search if self.path.startswith('/tags/') or \ (authorized and '/tags/' in self.path): + if self.path.startswith('/tags/rss2/'): + self._hashtagSearchRSS2(callingDomain, + self.path, cookie, + self.server.baseDir, + self.server.httpPrefix, + self.server.domain, + self.server.domainFull, + self.server.port, + self.server.onionDomain, + self.server.i2pDomain, + GETstartTime, GETtimings) + return self._hashtagSearch(callingDomain, self.path, cookie, self.server.baseDir, diff --git a/webinterface.py b/webinterface.py index 32ded79d..f9942ebb 100644 --- a/webinterface.py +++ b/webinterface.py @@ -787,7 +787,7 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int, else: postFields = postId.split(' ') if len(postFields) != 3: - index = +1 + index += 1 continue nickname = postFields[1] postId = postFields[2] @@ -833,6 +833,117 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int, return hashtagSearchForm +def rss2TagHeader(hashtag: str, httpPrefix: str, domainFull: str) -> str: + rssStr = "" + rssStr += "" + rssStr += '' + rssStr += ' #' + hashtag + '' + rssStr += ' ' + httpPrefix + '://' + domainFull + \ + '/tags/rss2/' + hashtag + '' + return rssStr + + +def rss2TagFooter() -> str: + rssStr = '' + rssStr += '' + return rssStr + + +def rssHashtagSearch(nickname: str, domain: str, port: int, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, + baseDir: str, hashtag: str, + postsPerPage: int, + session, wfRequest: {}, personCache: {}, + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str) -> str: + """Show an rss feed for a hashtag + """ + if hashtag.startswith('#'): + hashtag = hashtag[1:] + hashtag = urllib.parse.unquote(hashtag) + hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt' + if not os.path.isfile(hashtagIndexFile): + if hashtag != hashtag.lower(): + hashtag = hashtag.lower() + hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt' + if not os.path.isfile(hashtagIndexFile): + print('WARN: hashtag file not found ' + hashtagIndexFile) + return None + + # check that the directory for the nickname exists + if nickname: + if not os.path.isdir(baseDir + '/accounts/' + + nickname + '@' + domain): + nickname = None + + # read the index + lines = [] + with open(hashtagIndexFile, "r") as f: + lines = f.readlines() + if not lines: + return None + + domainFull = domain + if port: + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + + maxFeedLength = 10 + hashtagFeed = \ + rss2TagHeader(hashtag, httpPrefix, domainFull) + for index in range(len(lines)): + postId = lines[index].strip('\n').strip('\r') + if ' ' not in postId: + nickname = getNicknameFromActor(postId) + if not nickname: + index += 1 + if index >= maxFeedLength: + break + continue + else: + postFields = postId.split(' ') + if len(postFields) != 3: + index += 1 + if index >= maxFeedLength: + break + continue + nickname = postFields[1] + postId = postFields[2] + postFilename = locatePost(baseDir, nickname, domain, postId) + if not postFilename: + index += 1 + if index >= maxFeedLength: + break + continue + postJsonObject = loadJson(postFilename) + if postJsonObject: + if not isPublicPost(postJsonObject): + index += 1 + if index >= maxFeedLength: + break + continue + # add to feed + if postJsonObject['object'].get('id') and \ + postJsonObject['object'].get('published'): + messageLink = \ + postJsonObject['object']['id'].replace('/statuses/', '/') + published = postJsonObject['object']['published'] + pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ") + rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT") + hashtagFeed += ' ' + hashtagFeed += \ + ' ' + messageLink + '' + hashtagFeed += \ + ' ' + rssDateStr + '' + hashtagFeed += ' ' + index += 1 + if index >= maxFeedLength: + break + + return hashtagFeed + rss2TagFooter() + + def htmlSkillsSearch(translate: {}, baseDir: str, httpPrefix: str, skillsearch: str, instanceOnly: bool,