From d2fcb37dd9ad7f58b7e53eb94a2b481cb682407b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sat, 13 Jul 2019 20:28:14 +0100 Subject: [PATCH] Handle displaying replies --- daemon.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- inbox.py | 50 +++++++++---------------------- utils.py | 12 +++++--- 3 files changed, 109 insertions(+), 42 deletions(-) diff --git a/daemon.py b/daemon.py index 9180b3635..7b5aef136 100644 --- a/daemon.py +++ b/daemon.py @@ -284,13 +284,98 @@ class PubServer(BaseHTTPRequestHandler): else: self._404() self.server.GETbusy=False - return + return + # get replies to a post /users/nickname/statuses/number/replies + if self.path.endswith('/replies'): + if '/statuses/' in self.path and '/users/' in self.path: + namedStatus=self.path.split('/users/')[1] + if '/' in namedStatus: + postSections=namedStatus.split('/') + if len(postSections)>=4: + if postSections[3].startswith('replies'): + nickname=postSections[0] + statusNumber=postSections[2] + if len(statusNumber)>10 and statusNumber.isdigit(): + #get the replies file + domainFull=self.server.domain + if self.server.port!=80 and self.server.port!=443: + domainFull=self.server.domain+':'+str(self.server.port) + boxname='outbox' + postDir=self.server.baseDir+'/accounts/'+nickname+'@'+self.server.domain+'/'+boxname + postRepliesFilename= \ + postDir+'/'+ \ + self.server.httpPrefix+':##'+domainFull+'#users#'+nickname+'#statuses#'+statusNumber+'.replies' + if not os.path.isfile(postRepliesFilename): + # There are no replies, so show empty collection + repliesJson = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'first': self.server.httpPrefix+'://'+self.server.domain+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true', + 'id': self.server.httpPrefix+'://'+self.server.domain+'/users/'+nickname+'/statuses/'+statusNumber, + 'last': self.server.httpPrefix+'://'+self.server.domain+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true', + 'totalItems': 0, + 'type': 'OrderedCollection'} + self._set_headers('application/json') + self.wfile.write(json.dumps(repliesJson).encode('utf-8')) + self.server.GETbusy=False + return + else: + # replies exist. Itterate through the text file containing message ids + repliesJson = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': self.server.httpPrefix+'://'+self.server.domain+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true', + 'orderedItems': [ + ], + 'partOf': self.server.httpPrefix+'://'+self.server.domain+'/users/'+nickname+'/statuses/'+statusNumber, + 'type': 'OrderedCollectionPage'} + # some messages could be private, so check authorization state + authorized=self._isAuthorized() + # populate the items list with replies + repliesBoxes=['outbox','inbox'] + with open(postRepliesFilename,'r') as repliesFile: + for messageId in repliesFile: + replyFound=False + # examine inbox and outbox + for boxname in repliesBoxes: + searchFilename= \ + self.server.baseDir+ \ + '/accounts/'+nickname+'@'+ \ + self.server.domain+'/'+ \ + boxname+'/'+ \ + messageId.replace('\n','').replace('/','#')+'.json' + if os.path.isfile(searchFilename): + if authorized or \ + 'https://www.w3.org/ns/activitystreams#Public' in open(searchFilename).read(): + with open(searchFilename, 'r') as fp: + postJson=commentjson.load(fp) + repliesJson['orderedItems'].append(postJson) + replyFound=True + break + # if not in either inbox or outbox then examine the shared inbox + if not replyFound: + searchFilename= \ + self.server.baseDir+ \ + '/accounts/inbox@'+ \ + self.server.domain+'/inbox/'+ \ + messageId.replace('\n','').replace('/','#')+'.json' + if os.path.isfile(searchFilename): + if authorized or \ + 'https://www.w3.org/ns/activitystreams#Public' in open(searchFilename).read(): + # get the json of the reply and append it to the collection + with open(searchFilename, 'r') as fp: + postJson=commentjson.load(fp) + repliesJson['orderedItems'].append(postJson) + # send the replies json + self._set_headers('application/json') + self.wfile.write(json.dumps(repliesJson).encode('utf-8')) + self.server.GETbusy=False + return + # get an individual post from the path /users/nickname/statuses/number if '/statuses/' in self.path and '/users/' in self.path: namedStatus=self.path.split('/users/')[1] if '/' in namedStatus: postSections=namedStatus.split('/') - if len(postSections)>=2: + if len(postSections)>=3: nickname=postSections[0] statusNumber=postSections[2] if len(statusNumber)>10 and statusNumber.isdigit(): diff --git a/inbox.py b/inbox.py index 7fab3c754..6dde3ee95 100644 --- a/inbox.py +++ b/inbox.py @@ -673,42 +673,20 @@ def populateReplies(baseDir :str,httpPrefix :str,domain :str, \ if not postFilename: if debug: print('DEBUG: post may have expired - '+replyTo) - return False - with open(postFilename, 'r') as fp: - repliedToJson=commentjson.load(fp) - if not repliedToJson.get('object'): - if debug: - print('DEBUG: replied to post has no object - '+postFilename) - return False - if not isinstance(repliedToJson['object'], dict): - if debug: - print('DEBUG: replied to post object is not dict - '+postFilename) - return False - if not repliedToJson['object'].get('replies'): - if debug: - print('DEBUG: replied to post has no replies attribute - '+postFilename) - return False - if not repliedToJson['object']['replies'].get('first'): - if debug: - print('DEBUG: replied to post has no first attribute - '+postFilename) - return False - if not repliedToJson['object']['replies']['first'].get('items'): - if debug: - print('DEBUG: replied to post has no items attribute - '+postFilename) - return False - messageId=messageJson['id'] - repliesList=repliedToJson['object']['replies']['first']['items'] - if messageId not in repliesList: - repliesList.append(messageId) - with open(postFilename, 'w') as fp: - commentjson.dump(repliedToJson, fp, indent=4, sort_keys=False) - if debug: - print('DEBUG: updated replies for '+postFilename) - return True - else: - if debug: - print('DEBUG: reply was already added to list') - return False + return False + # populate a text file containing the ids of replies + postRepliesFilename=postFilename.replace('.json','.replies') + messageId=messageJson['id'].replace('/activity','') + if os.path.isfile(postRepliesFilename): + if messageId not in open(postRepliesFilename).read(): + repliesFile=open(postRepliesFilename, "a") + repliesFile.write(messageId+'\n') + repliesFile.close() + else: + repliesFile=open(postRepliesFilename, "w") + repliesFile.write(messageId+'\n') + repliesFile.close() + return True def inboxAfterCapabilities(session,keyId: str,handle: str,messageJson: {}, \ baseDir: str,httpPrefix: str,sendThreads: [], \ diff --git a/utils.py b/utils.py index af41846f4..ec266cab5 100644 --- a/utils.py +++ b/utils.py @@ -120,20 +120,24 @@ def followPerson(baseDir: str,nickname: str, domain: str, \ followfile.write(handleToFollow+'\n') return True -def locatePost(baseDir: str,nickname: str,domain: str,postUrl: str): +def locatePost(baseDir: str,nickname: str,domain: str,postUrl: str,replies=False) -> str: """Returns the filename for the given status post url """ + if not replies: + extension='json' + else: + extension='replies' # if this post in the shared inbox? handle='inbox@'+domain boxName='inbox' - postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.json' + postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.'+extension if not os.path.isfile(postFilename): boxName='outbox' - postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.json' + postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.'+extension if not os.path.isfile(postFilename): # if this post in the inbox of the person? boxName='inbox' - postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.json' + postFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxName+'/'+postUrl.replace('/','#')+'.'+extension if not os.path.isfile(postFilename): postFilename=None return postFilename