From 0bd778d553f4e9ce5f9c3050f9c441139452268d Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Mon, 24 Feb 2020 14:39:25 +0000 Subject: [PATCH] Blogs timeline --- daemon.py | 102 +++++++++++++++++++++++++++++++++++++++++++++++- epicyon.py | 11 +++++- inbox.py | 20 ++++++++++ person.py | 5 +++ posts.py | 20 +++++++++- tests.py | 6 +-- utils.py | 2 +- webinterface.py | 53 ++++++++++++++++++++++--- 8 files changed, 205 insertions(+), 14 deletions(-) diff --git a/daemon.py b/daemon.py index 2c4b1446c..bd5c84f35 100644 --- a/daemon.py +++ b/daemon.py @@ -115,6 +115,7 @@ from webinterface import htmlRemoveSharedItem from webinterface import htmlInboxDMs from webinterface import htmlInboxReplies from webinterface import htmlInboxMedia +from webinterface import htmlInboxBlogs from webinterface import htmlUnblockConfirm from webinterface import htmlPersonOptions from webinterface import htmlIndividualPost @@ -186,6 +187,9 @@ maxPostsInFeed=12 # reduced posts for media feed because it can take a while maxPostsInMediaFeed=6 +# Blogs can be longer, so don't show many per page +maxPostsInBlogsFeed=4 + # number of follows/followers per page followsPerPage=12 @@ -2894,6 +2898,84 @@ class PubServer(BaseHTTPRequestHandler): self.server.GETbusy=False return + # get the blogs for a given person + if self.path.endswith('/tlblogs') or '/tlblogs?page=' in self.path: + if '/users/' in self.path: + if authorized: + inboxBlogsFeed= \ + personBoxJson(self.server.recentPostsCache, \ + self.server.session, \ + self.server.baseDir, \ + self.server.domain, \ + self.server.port, \ + self.path, \ + self.server.httpPrefix, \ + maxPostsInBlogsFeed, 'tlblogs', \ + True,self.server.ocapAlways) + if not inboxBlogsFeed: + inboxBlogsFeed=[] + if self._requestHTTP(): + nickname=self.path.replace('/users/','').replace('/tlblogs','') + pageNumber=1 + if '?page=' in nickname: + pageNumber=nickname.split('?page=')[1] + nickname=nickname.split('?page=')[0] + if pageNumber.isdigit(): + pageNumber=int(pageNumber) + else: + pageNumber=1 + if 'page=' not in self.path: + # if no page was specified then show the first + inboxBlogsFeed= \ + personBoxJson(self.server.recentPostsCache, \ + self.server.session, \ + self.server.baseDir, \ + self.server.domain, \ + self.server.port, \ + self.path+'?page=1', \ + self.server.httpPrefix, \ + maxPostsInBlogsFeed, 'tlblogs', \ + True,self.server.ocapAlways) + msg=htmlInboxBlogs(self.server.defaultTimeline, \ + self.server.recentPostsCache, \ + self.server.maxRecentPosts, \ + self.server.translate, \ + pageNumber,maxPostsInBlogsFeed, \ + self.server.session, \ + self.server.baseDir, \ + self.server.cachedWebfingers, \ + self.server.personCache, \ + nickname, \ + self.server.domain, \ + self.server.port, \ + inboxBlogsFeed, \ + self.server.allowDeletion, \ + self.server.httpPrefix, \ + self.server.projectVersion).encode('utf-8') + self._set_headers('text/html',len(msg),cookie) + self._write(msg) + else: + # don't need authenticated fetch here because there is + # already the authorization check + msg=json.dumps(inboxBlogsFeed,ensure_ascii=False).encode('utf-8') + self._set_headers('application/json',len(msg),None) + self._write(msg) + self.server.GETbusy=False + return + else: + if self.server.debug: + nickname=self.path.replace('/users/','').replace('/tlblogs','') + print('DEBUG: '+nickname+ \ + ' was not authorized to access '+self.path) + if self.path!='/tlblogs': + # not the blogs inbox + if self.server.debug: + print('DEBUG: GET access to blogs is unauthorized') + self.send_response(405) + self.end_headers() + self.server.GETbusy=False + return + self._benchmarkGETtimings(GETstartTime,GETtimings,46) # get the shared items timeline for a given person @@ -4343,6 +4425,20 @@ class PubServer(BaseHTTPRequestHandler): self.server.defaultTimeline='inbox' setConfigParam(self.server.baseDir,"mediaInstance", \ self.server.mediaInstance) + if fields.get('blogsInstance'): + self.server.blogsInstance=False + self.server.defaultTimeline='inbox' + if fields['blogsInstance']=='on': + self.server.blogsInstance=True + self.server.defaultTimeline='tlblogs' + setConfigParam(self.server.baseDir,"blogsInstance", \ + self.server.blogsInstance) + else: + if self.server.blogsInstance: + self.server.blogsInstance=False + self.server.defaultTimeline='inbox' + setConfigParam(self.server.baseDir,"blogsInstance", \ + self.server.blogsInstance) # only receive DMs from accounts you follow followDMsFilename= \ self.server.baseDir+'/accounts/'+ \ @@ -5574,7 +5670,8 @@ def loadTokens(baseDir: str,tokensDict: {},tokensLookup: {}) -> None: tokensDict[nickname]=token tokensLookup[token]=nickname -def runDaemon(mediaInstance: bool,maxRecentPosts: int, \ +def runDaemon(blogsInstance: bool,mediaInstance: bool, \ + maxRecentPosts: int, \ enableSharedInbox: bool,registration: bool, \ language: str,projectVersion: str, \ instanceId: str,clientToServer: bool, \ @@ -5615,9 +5712,12 @@ def runDaemon(mediaInstance: bool,maxRecentPosts: int, \ httpd.useBlurHash=useBlurHash httpd.mediaInstance=mediaInstance + httpd.blogsInstance=blogsInstance httpd.defaultTimeline='inbox' if mediaInstance: httpd.defaultTimeline='tlmedia' + if blogsInstance: + httpd.defaultTimeline='tlblogs' # load translations dictionary httpd.translate={} diff --git a/epicyon.py b/epicyon.py index 0e91ac36a..9d8b43a7e 100644 --- a/epicyon.py +++ b/epicyon.py @@ -151,6 +151,9 @@ parser.add_argument('-f','--federate', nargs='+',dest='federationList', \ parser.add_argument("--mediainstance", type=str2bool, nargs='?', \ const=True, default=False, \ help="Media Instance - favor media over text") +parser.add_argument("--blogsinstance", type=str2bool, nargs='?', \ + const=True, default=False, \ + help="Blogs Instance - favor blogs over microblogging") parser.add_argument("--debug", type=str2bool, nargs='?', \ const=True, default=False, \ help="Show debug messages") @@ -386,6 +389,11 @@ if not args.mediainstance: mediaInstance=getConfigParam(baseDir,'mediaInstance') if mediaInstance!=None: args.mediainstance=mediaInstance + +if not args.blogsinstance: + blogsInstance=getConfigParam(baseDir,'blogsInstance') + if blogsInstance!=None: + args.blogsinstance=blogsInstance # set the instance title in config.json title=getConfigParam(baseDir,'instanceTitle') @@ -1504,7 +1512,8 @@ if not registration: if setTheme(baseDir,themeName): print('Theme set to '+themeName) -runDaemon(args.mediainstance,args.maxRecentPosts, \ +runDaemon(args.blogsinstance,args.mediainstance, \ + args.maxRecentPosts, \ not args.nosharedinbox, \ registration,args.language,__version__, \ instanceId,args.client,baseDir, \ diff --git a/inbox.py b/inbox.py index 5e3914858..000dd3ded 100644 --- a/inbox.py +++ b/inbox.py @@ -62,6 +62,23 @@ from webinterface import getIconsDir from question import questionUpdateVotes from media import replaceYouTube +def isBlogPost(postJsonObject: {}) -> bool: + """Is the given post a blog post? + """ + if postJsonObject['type']!='Create': + return False + if not postJsonObject.get('object'): + return False + if not isinstance(postJsonObject['object'], dict): + return False + if not postJsonObject['object'].get('type'): + return False + if not postJsonObject['object'].get('content'): + return False + if postJsonObject['object']['type']!='Article': + return False + return True + def storeHashTags(baseDir: str,nickname: str,postJsonObject: {}) -> None: """Extracts hashtags from an incoming post and updates the relevant tags files. @@ -1924,6 +1941,9 @@ def inboxAfterCapabilities(recentPostsCache: {},maxRecentPosts: int, \ if isImageMedia(session,baseDir,httpPrefix,nickname,domain,postJsonObject): # media index will be updated updateIndexList.append('tlmedia') + if isBlogPost(postJsonObject): + # blogs index will be updated + updateIndexList.append('tlblogs') # get the avatar for a reply/announce obtainAvatarForReplyPost(session,baseDir,httpPrefix,domain,personCache,postJsonObject,debug) diff --git a/person.py b/person.py index 1ca2888ce..fc8ca9636 100644 --- a/person.py +++ b/person.py @@ -21,6 +21,7 @@ from webfinger import storeWebfingerEndpoint from posts import createDMTimeline from posts import createRepliesTimeline from posts import createMediaTimeline +from posts import createBlogsTimeline from posts import createBookmarksTimeline from posts import createInbox from posts import createOutbox @@ -470,6 +471,7 @@ def personBoxJson(recentPostsCache: {}, \ """ if boxname!='inbox' and boxname!='dm' and \ boxname!='tlreplies' and boxname!='tlmedia' and \ + boxname!='tlblogs' and \ boxname!='outbox' and boxname!='moderation' and \ boxname!='tlbookmarks': return None @@ -521,6 +523,9 @@ def personBoxJson(recentPostsCache: {}, \ elif boxname=='tlmedia': return createMediaTimeline(session,baseDir,nickname,domain,port,httpPrefix, \ noOfItems,headerOnly,ocapAlways,pageNumber) + elif boxname=='tlblogs': + return createBlogsTimeline(session,baseDir,nickname,domain,port,httpPrefix, \ + noOfItems,headerOnly,ocapAlways,pageNumber) elif boxname=='outbox': return createOutbox(session,baseDir,nickname,domain,port,httpPrefix, \ noOfItems,headerOnly,authorized,pageNumber) diff --git a/posts.py b/posts.py index d21d8e793..109cc60a1 100644 --- a/posts.py +++ b/posts.py @@ -1956,6 +1956,13 @@ def createInbox(recentPostsCache: {}, \ session,baseDir,'inbox',nickname,domain,port,httpPrefix, \ itemsPerPage,headerOnly,True,ocapAlways,pageNumber) +def createBlogsTimeline(recentPostsCache: {}, \ + session,baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \ + itemsPerPage: int,headerOnly: bool,ocapAlways: bool,pageNumber=None) -> {}: + return createBoxIndexed(recentPostsCache, \ + session,baseDir,'tlblogs',nickname,domain,port,httpPrefix, \ + itemsPerPage,headerOnly,True,ocapAlways,pageNumber) + def createBookmarksTimeline(session,baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \ itemsPerPage: int,headerOnly: bool,ocapAlways: bool,pageNumber=None) -> {}: return createBoxIndexed({},session,baseDir,'tlbookmarks',nickname,domain,port,httpPrefix, \ @@ -2240,6 +2247,11 @@ def addPostStringToTimeline(postStr: str,boxname: str, \ elif boxname=='tlreplies': if boxActor not in postStr: return False + elif boxname=='tlblogs': + if '"Create"' not in postStr: + return False + if '"Article"' not in postStr: + return False elif boxname=='tlmedia': if '"Create"' in postStr: if 'mediaType' not in postStr or 'image/' not in postStr: @@ -2270,11 +2282,13 @@ def createBoxIndexed(recentPostsCache: {}, \ if boxname!='inbox' and boxname!='dm' and \ boxname!='tlreplies' and boxname!='tlmedia' and \ + boxname!='tlblogs' and \ boxname!='outbox' and boxname!='tlbookmarks': return None if boxname!='dm' and boxname!='tlreplies' and \ - boxname!='tlmedia' and boxname!='tlbookmarks': + boxname!='tlmedia' and boxname!='tlblogs' and \ + boxname!='tlbookmarks': boxDir = createPersonDir(nickname,domain,baseDir,boxname) else: # extract DMs or replies or media from the inbox @@ -2284,7 +2298,7 @@ def createBoxIndexed(recentPostsCache: {}, \ sharedBoxDir=None if boxname=='inbox' or boxname=='tlreplies' or \ - boxname=='tlmedia': + boxname=='tlmedia' or boxname=='tlblogs': sharedBoxDir = createPersonDir('inbox',domain,baseDir,boxname) # bookmarks timeline is like the inbox but has its own separate index @@ -2297,6 +2311,8 @@ def createBoxIndexed(recentPostsCache: {}, \ indexBoxName='tlreplies' elif boxname=='tlmedia': indexBoxName='tlmedia' + elif boxname=='tlblogs': + indexBoxName='tlblogs' if port: if port!=80 and port!=443: diff --git a/tests.py b/tests.py index f2a7ccd28..a180d6a75 100644 --- a/tests.py +++ b/tests.py @@ -236,7 +236,7 @@ def createServerAlice(path: str,domain: str,port: int,bobAddress: str,federation maxMentions=10 maxEmoji=10 print('Server running: Alice') - runDaemon(False,5,True,True,'en',__version__, \ + runDaemon(False,False,5,True,True,'en',__version__, \ "instanceId",False,path,domain,port,port, \ httpPrefix,federationList,maxMentions,maxEmoji,False, \ noreply,nolike,nopics,noannounce,cw,ocapAlways, \ @@ -292,7 +292,7 @@ def createServerBob(path: str,domain: str,port: int,aliceAddress: str,federation maxMentions=10 maxEmoji=10 print('Server running: Bob') - runDaemon(False,5,True,True,'en',__version__, \ + runDaemon(False,False,5,True,True,'en',__version__, \ "instanceId",False,path,domain,port,port, \ httpPrefix,federationList,maxMentions,maxEmoji,False, \ noreply,nolike,nopics,noannounce,cw,ocapAlways, \ @@ -328,7 +328,7 @@ def createServerEve(path: str,domain: str,port: int,federationList: [], \ maxMentions=10 maxEmoji=10 print('Server running: Eve') - runDaemon(False,5,True,True,'en',__version__, \ + runDaemon(False,False,5,True,True,'en',__version__, \ "instanceId",False,path,domain,port,port, \ httpPrefix,federationList,maxMentions,maxEmoji,False, \ noreply,nolike,nopics,noannounce,cw,ocapAlways, \ diff --git a/utils.py b/utils.py index 059b306dd..47db0ca80 100644 --- a/utils.py +++ b/utils.py @@ -429,7 +429,7 @@ def validNickname(domain: str,nickname: str) -> bool: return False if nickname==domain: return False - reservedNames=['inbox','dm','outbox','following','public','followers','profile','channel','capabilities','calendar','tlreplies','tlmedia','moderation','activity','undo','reply','replies','question','like','likes','users','statuses','updates','repeat','announce','shares'] + reservedNames=['inbox','dm','outbox','following','public','followers','profile','channel','capabilities','calendar','tlreplies','tlmedia','tlblogs','moderation','activity','undo','reply','replies','question','like','likes','users','statuses','updates','repeat','announce','shares'] if nickname in reservedNames: return False return True diff --git a/webinterface.py b/webinterface.py index 07a743513..1cdf24209 100644 --- a/webinterface.py +++ b/webinterface.py @@ -3395,6 +3395,7 @@ def htmlTimeline(defaultTimeline: str, \ moderator=isModerator(baseDir,nickname) inboxButton='button' + blogsButton='button' dmButton='button' if newDM: dmButton='buttonhighlighted' @@ -3412,6 +3413,8 @@ def htmlTimeline(defaultTimeline: str, \ moderationButton='buttonhighlighted' if boxName=='inbox': inboxButton='buttonselected' + elif boxName=='tlblogs': + blogsButton='buttonselected' elif boxName=='dm': dmButton='buttonselected' if newDM: @@ -3511,16 +3514,24 @@ def htmlTimeline(defaultTimeline: str, \ tlStr+='
' tlStr+='
' tlStr+='
\n' - if defaultTimeline!='tlmedia': - tlStr+= \ - ' ' - else: + + # first button + if defaultTimeline=='tlmedia': tlStr+= \ ' ' + elif defaultTimeline=='tlblogs': + tlStr+= \ + ' ' + else: + tlStr+= \ + ' ' + tlStr+= \ ' ' @@ -3528,6 +3539,8 @@ def htmlTimeline(defaultTimeline: str, \ ' ' + + # typically the media button if defaultTimeline!='tlmedia': tlStr+= \ ' ' + + # typically the blogs button + if defaultTimeline!='tlblogs': + tlStr+= \ + ' ' + else: + tlStr+= \ + ' ' + tlStr+= \ '