diff --git a/inbox.py b/inbox.py index fd18837ac..5ec23b2f7 100644 --- a/inbox.py +++ b/inbox.py @@ -1,17 +1,15 @@ -__filename__="inbox.py" -__author__="Bob Mottram" -__license__="AGPL3+" -__version__="1.1.0" -__maintainer__="Bob Mottram" -__email__="bob@freedombone.net" -__status__="Production" +__filename__ = "inbox.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" import json import os import datetime import time -import json -from shutil import copyfile from utils import isBlogPost from utils import removeAvatarFromCache from utils import isPublicPost @@ -22,10 +20,8 @@ from utils import createInboxQueueDir from utils import getStatusNumber from utils import getDomainFromActor from utils import getNicknameFromActor -from utils import domainPermitted from utils import locatePost from utils import deletePost -from utils import removeAttachment from utils import removeModerationPostFromIndex from utils import loadJson from utils import saveJson @@ -63,7 +59,8 @@ from webinterface import getIconsDir from question import questionUpdateVotes from media import replaceYouTube -def storeHashTags(baseDir: str,nickname: str,postJsonObject: {}) -> None: + +def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None: """Extracts hashtags from an incoming post and updates the relevant tags files. """ @@ -79,21 +76,23 @@ def storeHashTags(baseDir: str,nickname: str,postJsonObject: {}) -> None: return if not isinstance(postJsonObject['object']['tag'], list): return - tagsDir=baseDir+'/tags' + tagsDir = baseDir+'/tags' for tag in postJsonObject['object']['tag']: if not tag.get('type'): continue - if tag['type']!='Hashtag': + if tag['type'] != 'Hashtag': continue if not tag.get('name'): continue - tagName=tag['name'].replace('#','').strip() - tagsFilename=tagsDir+'/'+tagName+'.txt' - postUrl=postJsonObject['id'].replace('/activity','').replace('/','#') - daysSinceEpoch=(datetime.datetime.utcnow() - datetime.datetime(1970,1,1)).days - tagline=str(daysSinceEpoch)+' '+nickname+' '+postUrl+'\n' + tagName = tag['name'].replace('#', '').strip() + tagsFilename = tagsDir + '/' + tagName + '.txt' + postUrl = postJsonObject['id'].replace('/activity', '') + postUrl = postUrl.replace('/', '#') + daysDiff = datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1) + daysSinceEpoch = daysDiff.days + tagline = str(daysSinceEpoch) + ' ' + nickname + ' ' + postUrl + '\n' if not os.path.isfile(tagsFilename): - tagsFile=open(tagsFilename, "w+") + tagsFile = open(tagsFilename, "w+") if tagsFile: tagsFile.write(tagline) tagsFile.close() @@ -101,278 +100,293 @@ def storeHashTags(baseDir: str,nickname: str,postJsonObject: {}) -> None: if postUrl not in open(tagsFilename).read(): try: with open(tagsFilename, 'r+') as tagsFile: - content=tagsFile.read() + content = tagsFile.read() tagsFile.seek(0, 0) - tagsFile.write(tagline+content) + tagsFile.write(tagline + content) except Exception as e: - print('WARN: Failed to write entry to tags file '+ \ - tagsFilename+' '+str(e)) + print('WARN: Failed to write entry to tags file ' + + tagsFilename + ' ' + str(e)) -def inboxStorePostToHtmlCache(recentPostsCache: {},maxRecentPosts: int, \ - translate: {}, \ - baseDir: str,httpPrefix: str, \ - session,cachedWebfingers: {},personCache: {}, \ - nickname: str,domain: str,port: int, \ - postJsonObject: {}, \ + +def inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int, + translate: {}, + baseDir: str, httpPrefix: str, + session, cachedWebfingers: {}, personCache: {}, + nickname: str, domain: str, port: int, + postJsonObject: {}, allowDeletion: bool) -> None: """Converts the json post into html and stores it in a cache This enables the post to be quickly displayed later """ - pageNumber=-999 - showAvatarOptions=True - avatarUrl=None - boxName='inbox' - htmlStr= \ - individualPostAsHtml(recentPostsCache,maxRecentPosts, \ - getIconsDir(baseDir),translate,pageNumber, \ - baseDir,session,cachedWebfingers,personCache, \ - nickname,domain,port,postJsonObject, \ - avatarUrl,True,allowDeletion, \ - httpPrefix,__version__,boxName, \ - not isDM(postJsonObject), \ - True,True,False,True) + pageNumber = -999 + avatarUrl = None + boxName = 'inbox' + individualPostAsHtml(recentPostsCache, maxRecentPosts, + getIconsDir(baseDir), translate, pageNumber, + baseDir, session, cachedWebfingers, personCache, + nickname, domain, port, postJsonObject, + avatarUrl, True, allowDeletion, + httpPrefix, __version__, boxName, + not isDM(postJsonObject), + True, True, False, True) -def validInbox(baseDir: str,nickname: str,domain: str) -> bool: + +def validInbox(baseDir: str, nickname: str, domain: str) -> bool: """Checks whether files were correctly saved to the inbox """ if ':' in domain: - domain=domain.split(':')[0] - inboxDir=baseDir+'/accounts/'+nickname+'@'+domain+'/inbox' + domain = domain.split(':')[0] + inboxDir = baseDir+'/accounts/' + nickname + '@' + domain + '/inbox' if not os.path.isdir(inboxDir): return True for subdir, dirs, files in os.walk(inboxDir): for f in files: - filename=os.path.join(subdir, f) + filename = os.path.join(subdir, f) if not os.path.isfile(filename): - print('filename: '+filename) + print('filename: ' + filename) return False if 'postNickname' in open(filename).read(): - print('queue file incorrectly saved to '+filename) + print('queue file incorrectly saved to ' + filename) return False return True -def validInboxFilenames(baseDir: str,nickname: str,domain: str, \ - expectedDomain: str,expectedPort: int) -> bool: + +def validInboxFilenames(baseDir: str, nickname: str, domain: str, + expectedDomain: str, expectedPort: int) -> bool: """Used by unit tests to check that the port number gets appended to domain names within saved post filenames """ if ':' in domain: - domain=domain.split(':')[0] - inboxDir=baseDir+'/accounts/'+nickname+'@'+domain+'/inbox' + domain = domain.split(':')[0] + inboxDir = baseDir + '/accounts/' + nickname + '@' + domain + '/inbox' if not os.path.isdir(inboxDir): return True - expectedStr=expectedDomain+':'+str(expectedPort) + expectedStr = expectedDomain + ':' + str(expectedPort) for subdir, dirs, files in os.walk(inboxDir): for f in files: - filename=os.path.join(subdir, f) + filename = os.path.join(subdir, f) if not os.path.isfile(filename): - print('filename: '+filename) + print('filename: ' + filename) return False - if not expectedStr in filename: - print('Expected: '+expectedStr) - print('Invalid filename: '+filename) + if expectedStr not in filename: + print('Expected: ' + expectedStr) + print('Invalid filename: ' + filename) return False return True -def getPersonPubKey(baseDir: str,session,personUrl: str, \ - personCache: {},debug: bool, \ - projectVersion: str,httpPrefix: str, \ - domain: str,onionDomain: str) -> str: + +def getPersonPubKey(baseDir: str, session, personUrl: str, + personCache: {}, debug: bool, + projectVersion: str, httpPrefix: str, + domain: str, onionDomain: str) -> str: if not personUrl: return None - personUrl=personUrl.replace('#main-key','') + personUrl = personUrl.replace('#main-key', '') if personUrl.endswith('/users/inbox'): if debug: print('DEBUG: Obtaining public key for shared inbox') - personUrl=personUrl.replace('/users/inbox','/inbox') - personJson=getPersonFromCache(baseDir,personUrl,personCache) + personUrl = personUrl.replace('/users/inbox', '/inbox') + personJson = getPersonFromCache(baseDir, personUrl, personCache) if not personJson: if debug: - print('DEBUG: Obtaining public key for '+personUrl) - personDomain=domain + print('DEBUG: Obtaining public key for ' + personUrl) + personDomain = domain if onionDomain: if '.onion/' in personUrl: - personDomain=onionDomain - asHeader={ - 'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"' + personDomain = onionDomain + profileStr = 'https://www.w3.org/ns/activitystreams' + asHeader = { + 'Accept': 'application/activity+json; profile="' + profileStr + '"' } - personJson= \ - getJson(session,personUrl,asHeader,None,projectVersion, \ - httpPrefix,personDomain) + personJson = \ + getJson(session, personUrl, asHeader, None, projectVersion, + httpPrefix, personDomain) if not personJson: return None - pubKey=None + pubKey = None if personJson.get('publicKey'): if personJson['publicKey'].get('publicKeyPem'): - pubKey=personJson['publicKey']['publicKeyPem'] + pubKey = personJson['publicKey']['publicKeyPem'] else: if personJson.get('publicKeyPem'): - pubKey=personJson['publicKeyPem'] + pubKey = personJson['publicKeyPem'] if not pubKey: if debug: - print('DEBUG: Public key not found for '+personUrl) + print('DEBUG: Public key not found for ' + personUrl) - storePersonInCache(baseDir,personUrl,personJson,personCache) + storePersonInCache(baseDir, personUrl, personJson, personCache) return pubKey + def inboxMessageHasParams(messageJson: {}) -> bool: """Checks whether an incoming message contains expected parameters """ - expectedParams=['type','actor','object'] + expectedParams = ['type', 'actor', 'object'] for param in expectedParams: if not messageJson.get(param): return False if not messageJson.get('to'): - allowedWithoutToParam=['Like','Follow','Request','Accept','Capability','Undo'] + allowedWithoutToParam = ['Like', 'Follow', 'Request', + 'Accept', 'Capability', 'Undo'] if messageJson['type'] not in allowedWithoutToParam: return False return True -def inboxPermittedMessage(domain: str,messageJson: {},federationList: []) -> bool: + +def inboxPermittedMessage(domain: str, messageJson: {}, + federationList: []) -> bool: """ check that we are receiving from a permitted domain """ if not messageJson.get('actor'): return False - actor=messageJson['actor'] + actor = messageJson['actor'] # always allow the local domain if domain in actor: return True - if not urlPermitted(actor,federationList,"inbox:write"): + if not urlPermitted(actor, federationList, "inbox:write"): return False - alwaysAllowedTypes=('Follow','Like','Delete','Announce') + alwaysAllowedTypes = ('Follow', 'Like', 'Delete', 'Announce') if messageJson['type'] not in alwaysAllowedTypes: if not messageJson.get('object'): return True if not isinstance(messageJson['object'], dict): return False if messageJson['object'].get('inReplyTo'): - inReplyTo=messageJson['object']['inReplyTo'] - if not urlPermitted(inReplyTo,federationList,"inbox:write"): + inReplyTo = messageJson['object']['inReplyTo'] + if not urlPermitted(inReplyTo, federationList, "inbox:write"): return False return True + def validPublishedDate(published: str) -> bool: - currTime=datetime.datetime.utcnow() - pubDate=datetime.datetime.strptime(published,"%Y-%m-%dT%H:%M:%SZ") - daysSincePublished=(currTime - pubTime).days - if daysSincePublished>30: + currTime = datetime.datetime.utcnow() + pubDate = datetime.datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ") + daysSincePublished = (currTime - pubDate).days + if daysSincePublished > 30: return False return True -def savePostToInboxQueue(baseDir: str,httpPrefix: str, \ - nickname: str, domain: str, \ - postJsonObject: {}, \ - messageBytes: str, \ - httpHeaders: {}, \ - postPath: str,debug: bool) -> str: + +def savePostToInboxQueue(baseDir: str, httpPrefix: str, + nickname: str, domain: str, + postJsonObject: {}, + messageBytes: str, + httpHeaders: {}, + postPath: str, debug: bool) -> str: """Saves the give json to the inbox queue for the person keyId specifies the actor sending the post """ - if len(messageBytes)>10240: - print('WARN: inbox message too long '+str(len(messageBytes))+' bytes') + if len(messageBytes) > 10240: + print('WARN: inbox message too long ' + + str(len(messageBytes)) + ' bytes') return None - originalDomain=domain + originalDomain = domain if ':' in domain: - domain=domain.split(':')[0] + domain = domain.split(':')[0] # block at the ealiest stage possible, which means the data # isn't written to file - postNickname=None - postDomain=None - actor=None + postNickname = None + postDomain = None + actor = None if postJsonObject.get('actor'): - actor=postJsonObject['actor'] - postNickname=getNicknameFromActor(postJsonObject['actor']) + actor = postJsonObject['actor'] + postNickname = getNicknameFromActor(postJsonObject['actor']) if not postNickname: - print('No post Nickname in actor '+postJsonObject['actor']) + print('No post Nickname in actor ' + postJsonObject['actor']) return None - postDomain,postPort=getDomainFromActor(postJsonObject['actor']) + postDomain, postPort = getDomainFromActor(postJsonObject['actor']) if not postDomain: if debug: pprint(postJsonObject) print('No post Domain in actor') return None - if isBlocked(baseDir,nickname,domain,postNickname,postDomain): + if isBlocked(baseDir, nickname, domain, postNickname, postDomain): if debug: - print('DEBUG: post from '+postNickname+' blocked') + print('DEBUG: post from ' + postNickname + ' blocked') return None if postPort: - if postPort!=80 and postPort!=443: + if postPort != 80 and postPort != 443: if ':' not in postDomain: - postDomain=postDomain+':'+str(postPort) + postDomain = postDomain + ':' + str(postPort) if postJsonObject.get('object'): if isinstance(postJsonObject['object'], dict): if postJsonObject['object'].get('inReplyTo'): if isinstance(postJsonObject['object']['inReplyTo'], str): - replyDomain,replyPort=getDomainFromActor(postJsonObject['object']['inReplyTo']) - if isBlockedDomain(baseDir,replyDomain): - print('WARN: post contains reply from '+str(actor)+' to a blocked domain: '+replyDomain) + inReplyTo = \ + postJsonObject['object']['inReplyTo'] + replyDomain, replyPort = \ + getDomainFromActor(inReplyTo) + if isBlockedDomain(baseDir, replyDomain): + print('WARN: post contains reply from ' + + str(actor) + + ' to a blocked domain: ' + replyDomain) return None else: - replyNickname=getNicknameFromActor(postJsonObject['object']['inReplyTo']) + replyNickname = \ + getNicknameFromActor(inReplyTo) if replyNickname and replyDomain: - if isBlocked(baseDir,nickname,domain,replyNickname,replyDomain): - print('WARN: post contains reply from '+str(actor)+ \ - ' to a blocked account: '+replyNickname+'@'+replyDomain) + if isBlocked(baseDir, nickname, domain, + replyNickname, replyDomain): + print('WARN: post contains reply from ' + + str(actor) + + ' to a blocked account: ' + + replyNickname + '@' + replyDomain) return None - #else: - # print('WARN: post is a reply to an unidentified account: '+postJsonObject['object']['inReplyTo']) - # return None if postJsonObject['object'].get('content'): if isinstance(postJsonObject['object']['content'], str): - if isFiltered(baseDir,nickname,domain,postJsonObject['object']['content']): + if isFiltered(baseDir, nickname, domain, + postJsonObject['object']['content']): print('WARN: post was filtered out due to content') return None - originalPostId=None + originalPostId = None if postJsonObject.get('id'): - originalPostId=postJsonObject['id'].replace('/activity','').replace('/undo','') + originalPostId = \ + postJsonObject['id'].replace('/activity', '').replace('/undo', '') - currTime=datetime.datetime.utcnow() + currTime = datetime.datetime.utcnow() - postId=None + postId = None if postJsonObject.get('id'): - #if '/statuses/' not in postJsonObject['id']: - postId=postJsonObject['id'].replace('/activity','').replace('/undo','') - published=currTime.strftime("%Y-%m-%dT%H:%M:%SZ") + postId = postJsonObject['id'].replace('/activity', '') + postId = postId.replace('/undo', '') + published = currTime.strftime("%Y-%m-%dT%H:%M:%SZ") if not postId: - statusNumber,published=getStatusNumber() + statusNumber, published = getStatusNumber() if actor: - postId=actor+'/statuses/'+statusNumber + postId = actor + '/statuses/' + statusNumber else: - postId=httpPrefix+'://'+originalDomain+'/users/'+nickname+'/statuses/'+statusNumber + postId = httpPrefix + '://' + originalDomain + \ + '/users/' + nickname + '/statuses/' + statusNumber # NOTE: don't change postJsonObject['id'] before signature check - inboxQueueDir=createInboxQueueDir(nickname,domain,baseDir) + inboxQueueDir = createInboxQueueDir(nickname, domain, baseDir) - handle=nickname+'@'+domain - destination=baseDir+'/accounts/'+handle+'/inbox/'+postId.replace('/','#')+'.json' - #if os.path.isfile(destination): - # if debug: - # print(destination) - # print('DEBUG: inbox item already exists') - # return None - filename=inboxQueueDir+'/'+postId.replace('/','#')+'.json' + handle = nickname + '@' + domain + destination = baseDir + '/accounts/' + \ + handle + '/inbox/' + postId.replace('/', '#') + '.json' + filename = inboxQueueDir + '/' + postId.replace('/', '#') + '.json' - sharedInboxItem=False - if nickname=='inbox': - nickname=originalDomain - sharedInboxItem=True + sharedInboxItem = False + if nickname == 'inbox': + nickname = originalDomain + sharedInboxItem = True - digestStartTime=time.time() - digest=messageContentDigest(messageBytes) - timeDiffStr=str(int((time.time()-digestStartTime)*1000)) + digestStartTime = time.time() + digest = messageContentDigest(messageBytes) + timeDiffStr = str(int((time.time() - digestStartTime) * 1000)) if debug: - while len(timeDiffStr)<6: - timeDiffStr='0'+timeDiffStr - print('DIGEST|'+timeDiffStr+'|'+filename) + while len(timeDiffStr) < 6: + timeDiffStr = '0' + timeDiffStr + print('DIGEST|' + timeDiffStr + '|' + filename) - newQueueItem={ + newQueueItem = { 'originalId': originalPostId, 'id': postId, 'actor': actor, @@ -392,50 +406,52 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str, \ if debug: print('Inbox queue item created') - saveJson(newQueueItem,filename) + saveJson(newQueueItem, filename) return filename -def inboxCheckCapabilities(baseDir :str,nickname :str,domain :str, \ - actor: str,queue: [],queueJson: {}, \ - capabilityId: str,debug : bool) -> bool: - if nickname=='inbox': + +def inboxCheckCapabilities(baseDir: str, nickname: str, domain: str, + actor: str, queueFilename: str, queue: [], + queueJson: {}, capabilityId: str, + debug: bool) -> bool: + if nickname == 'inbox': return True - ocapFilename= \ - getOcapFilename(baseDir, \ - queueJson['nickname'],queueJson['domain'], \ - actor,'accept') + ocapFilename = \ + getOcapFilename(baseDir, + queueJson['nickname'], queueJson['domain'], + actor, 'accept') if not ocapFilename: return False if not os.path.isfile(ocapFilename): if debug: - print('DEBUG: capabilities for '+ \ - actor+' do not exist') + print('DEBUG: capabilities for ' + + actor + ' do not exist') if os.path.isfile(queueFilename): os.remove(queueFilename) - if len(queue)>0: + if len(queue) > 0: queue.pop(0) return False - oc=loadJson(ocapFilename) + oc = loadJson(ocapFilename) if not oc: return False if not oc.get('id'): if debug: - print('DEBUG: capabilities for '+actor+' do not contain an id') + print('DEBUG: capabilities for ' + actor + ' do not contain an id') if os.path.isfile(queueFilename): os.remove(queueFilename) - if len(queue)>0: + if len(queue) > 0: queue.pop(0) return False - if oc['id']!=capabilityId: + if oc['id'] != capabilityId: if debug: print('DEBUG: capability id mismatch') if os.path.isfile(queueFilename): os.remove(queueFilename) - if len(queue)>0: + if len(queue) > 0: queue.pop(0) return False @@ -444,16 +460,17 @@ def inboxCheckCapabilities(baseDir :str,nickname :str,domain :str, \ print('DEBUG: missing capability list') if os.path.isfile(queueFilename): os.remove(queueFilename) - if len(queue)>0: + if len(queue) > 0: queue.pop(0) return False - if not CapablePost(queueJson['post'],oc['capability'],debug): + if not CapablePost(queueJson['post'], oc['capability'], debug): if debug: - print('DEBUG: insufficient capabilities to write to inbox from '+actor) + print('DEBUG: insufficient capabilities to write to inbox from ' + + actor) if os.path.isfile(queueFilename): os.remove(queueFilename) - if len(queue)>0: + if len(queue) > 0: queue.pop(0) return False @@ -461,114 +478,120 @@ def inboxCheckCapabilities(baseDir :str,nickname :str,domain :str, \ print('DEBUG: object capabilities check success') return True -def inboxPostRecipientsAdd(baseDir :str,httpPrefix :str,toList :[], \ - recipientsDict :{}, \ - domainMatch: str,domain :str, \ - actor :str,debug: bool) -> bool: + +def inboxPostRecipientsAdd(baseDir: str, httpPrefix: str, toList: [], + recipientsDict: {}, + domainMatch: str, domain: str, + actor: str, debug: bool) -> bool: """Given a list of post recipients (toList) from 'to' or 'cc' parameters populate a recipientsDict with the handle and capabilities id for each """ - followerRecipients=False + followerRecipients = False for recipient in toList: if not recipient: continue # is this a to a local account? if domainMatch in recipient: # get the handle for the local account - nickname=recipient.split(domainMatch)[1] - handle=nickname+'@'+domain - if os.path.isdir(baseDir+'/accounts/'+handle): + nickname = recipient.split(domainMatch)[1] + handle = nickname+'@'+domain + if os.path.isdir(baseDir + '/accounts/' + handle): # are capabilities granted for this account to the # sender (actor) of the post? - ocapFilename=baseDir+'/accounts/'+handle+'/ocap/accept/'+actor.replace('/','#')+'.json' + ocapFilename = \ + baseDir + '/accounts/' + handle + \ + '/ocap/accept/' + actor.replace('/', '#') + '.json' if os.path.isfile(ocapFilename): # read the granted capabilities and obtain the id - ocapJson=loadJson(ocapFilename) + ocapJson = loadJson(ocapFilename) if ocapJson: if ocapJson.get('id'): # append with the capabilities id - recipientsDict[handle]=ocapJson['id'] + recipientsDict[handle] = ocapJson['id'] else: - recipientsDict[handle]=None + recipientsDict[handle] = None else: if debug: - print('DEBUG: '+ocapFilename+' not found') - recipientsDict[handle]=None + print('DEBUG: ' + ocapFilename + ' not found') + recipientsDict[handle] = None else: if debug: - print('DEBUG: '+baseDir+'/accounts/'+handle+' does not exist') + print('DEBUG: ' + baseDir + '/accounts/' + + handle + ' does not exist') else: if debug: - print('DEBUG: '+recipient+' is not local to '+domainMatch) + print('DEBUG: ' + recipient + ' is not local to ' + + domainMatch) print(str(toList)) if recipient.endswith('followers'): if debug: print('DEBUG: followers detected as post recipients') - followerRecipients=True - return followerRecipients,recipientsDict + followerRecipients = True + return followerRecipients, recipientsDict -def inboxPostRecipients(baseDir :str,postJsonObject :{}, \ - httpPrefix :str,domain : str,port :int, \ - debug :bool) -> ([],[]): + +def inboxPostRecipients(baseDir: str, postJsonObject: {}, + httpPrefix: str, domain: str, port: int, + debug: bool) -> ([], []): """Returns dictionaries containing the recipients of the given post The shared dictionary contains followers """ - recipientsDict={} - recipientsDictFollowers={} + recipientsDict = {} + recipientsDictFollowers = {} if not postJsonObject.get('actor'): if debug: pprint(postJsonObject) print('WARNING: inbox post has no actor') - return recipientsDict,recipientsDictFollowers + return recipientsDict, recipientsDictFollowers if ':' in domain: - domain=domain.split(':')[0] - domainBase=domain + domain = domain.split(':')[0] + domainBase = domain if port: - if port!=80 and port!=443: + if port != 80 and port != 443: if ':' not in domain: - domain=domain+':'+str(port) - domainMatch='/'+domain+'/users/' + domain = domain + ':' + str(port) + domainMatch = '/' + domain + '/users/' - actor=postJsonObject['actor'] + actor = postJsonObject['actor'] # first get any specific people which the post is addressed to - followerRecipients=False + followerRecipients = False if postJsonObject.get('object'): if isinstance(postJsonObject['object'], dict): if postJsonObject['object'].get('to'): if isinstance(postJsonObject['object']['to'], list): - recipientsList=postJsonObject['object']['to'] + recipientsList = postJsonObject['object']['to'] else: - recipientsList=[postJsonObject['object']['to']] + recipientsList = [postJsonObject['object']['to']] if debug: print('DEBUG: resolving "to"') - includesFollowers,recipientsDict= \ - inboxPostRecipientsAdd(baseDir,httpPrefix, \ - recipientsList, \ - recipientsDict, \ - domainMatch,domainBase, \ - actor,debug) + includesFollowers, recipientsDict = \ + inboxPostRecipientsAdd(baseDir, httpPrefix, + recipientsList, + recipientsDict, + domainMatch, domainBase, + actor, debug) if includesFollowers: - followerRecipients=True + followerRecipients = True else: if debug: print('DEBUG: inbox post has no "to"') if postJsonObject['object'].get('cc'): if isinstance(postJsonObject['object']['cc'], list): - recipientsList=postJsonObject['object']['cc'] + recipientsList = postJsonObject['object']['cc'] else: - recipientsList=[postJsonObject['object']['cc']] - includesFollowers,recipientsDict= \ - inboxPostRecipientsAdd(baseDir,httpPrefix, \ - recipientsList, \ - recipientsDict, \ - domainMatch,domainBase, \ - actor,debug) + recipientsList = [postJsonObject['object']['cc']] + includesFollowers, recipientsDict = \ + inboxPostRecipientsAdd(baseDir, httpPrefix, + recipientsList, + recipientsDict, + domainMatch, domainBase, + actor, debug) if includesFollowers: - followerRecipients=True + followerRecipients = True else: if debug: print('DEBUG: inbox post has no cc') @@ -583,47 +606,48 @@ def inboxPostRecipients(baseDir :str,postJsonObject :{}, \ if postJsonObject.get('to'): if isinstance(postJsonObject['to'], list): - recipientsList=postJsonObject['to'] + recipientsList = postJsonObject['to'] else: - recipientsList=[postJsonObject['to']] - includesFollowers,recipientsDict= \ - inboxPostRecipientsAdd(baseDir,httpPrefix, \ - recipientsList, \ - recipientsDict, \ - domainMatch,domainBase, \ - actor,debug) + recipientsList = [postJsonObject['to']] + includesFollowers, recipientsDict = \ + inboxPostRecipientsAdd(baseDir, httpPrefix, + recipientsList, + recipientsDict, + domainMatch, domainBase, + actor, debug) if includesFollowers: - followerRecipients=True + followerRecipients = True if postJsonObject.get('cc'): if isinstance(postJsonObject['cc'], list): - recipientsList=postJsonObject['cc'] + recipientsList = postJsonObject['cc'] else: - recipientsList=[postJsonObject['cc']] - includesFollowers,recipientsDict= \ - inboxPostRecipientsAdd(baseDir,httpPrefix, \ - recipientsList, \ - recipientsDict, \ - domainMatch,domainBase, \ - actor,debug) + recipientsList = [postJsonObject['cc']] + includesFollowers, recipientsDict = \ + inboxPostRecipientsAdd(baseDir, httpPrefix, + recipientsList, + recipientsDict, + domainMatch, domainBase, + actor, debug) if includesFollowers: - followerRecipients=True + followerRecipients = True if not followerRecipients: if debug: print('DEBUG: no followers were resolved') - return recipientsDict,recipientsDictFollowers + return recipientsDict, recipientsDictFollowers # now resolve the followers - recipientsDictFollowers= \ - getFollowersOfActor(baseDir,actor,debug) + recipientsDictFollowers = \ + getFollowersOfActor(baseDir, actor, debug) - return recipientsDict,recipientsDictFollowers + return recipientsDict, recipientsDictFollowers -def receiveUndoFollow(session,baseDir: str,httpPrefix: str, \ - port: int,messageJson: {}, \ - federationList: [], \ - debug : bool) -> bool: + +def receiveUndoFollow(session, baseDir: str, httpPrefix: str, + port: int, messageJson: {}, + federationList: [], + debug: bool) -> bool: if not messageJson['object'].get('actor'): if debug: print('DEBUG: follow request has no actor within object') @@ -632,53 +656,66 @@ def receiveUndoFollow(session,baseDir: str,httpPrefix: str, \ '/channel/' not in messageJson['object']['actor'] and \ '/profile/' not in messageJson['object']['actor']: if debug: - print('DEBUG: "users" or "profile" missing from actor within object') + print('DEBUG: "users" or "profile" missing ' + + 'from actor within object') return False if messageJson['object']['actor'] != messageJson['actor']: if debug: print('DEBUG: actors do not match') return False - nicknameFollower=getNicknameFromActor(messageJson['object']['actor']) + nicknameFollower = \ + getNicknameFromActor(messageJson['object']['actor']) if not nicknameFollower: - print('WARN: unable to find nickname in '+messageJson['object']['actor']) + print('WARN: unable to find nickname in ' + + messageJson['object']['actor']) return False - domainFollower,portFollower=getDomainFromActor(messageJson['object']['actor']) - domainFollowerFull=domainFollower + domainFollower, portFollower = \ + getDomainFromActor(messageJson['object']['actor']) + domainFollowerFull = domainFollower if portFollower: - if portFollower!=80 and portFollower!=443: + if portFollower != 80 and portFollower != 443: if ':' not in domainFollower: - domainFollowerFull=domainFollower+':'+str(portFollower) + domainFollowerFull = domainFollower + ':' + str(portFollower) - nicknameFollowing=getNicknameFromActor(messageJson['object']['object']) + nicknameFollowing = \ + getNicknameFromActor(messageJson['object']['object']) if not nicknameFollowing: - print('WARN: unable to find nickname in '+messageJson['object']['object']) + print('WARN: unable to find nickname in ' + + messageJson['object']['object']) return False - domainFollowing,portFollowing=getDomainFromActor(messageJson['object']['object']) - domainFollowingFull=domainFollowing + domainFollowing, portFollowing = \ + getDomainFromActor(messageJson['object']['object']) + domainFollowingFull = domainFollowing if portFollowing: - if portFollowing!=80 and portFollowing!=443: + if portFollowing != 80 and portFollowing != 443: if ':' not in domainFollowing: - domainFollowingFull=domainFollowing+':'+str(portFollowing) + domainFollowingFull = \ + domainFollowing + ':' + str(portFollowing) - if unfollowerOfPerson(baseDir, \ - nicknameFollowing,domainFollowingFull, \ - nicknameFollower,domainFollowerFull, \ + if unfollowerOfPerson(baseDir, + nicknameFollowing, domainFollowingFull, + nicknameFollower, domainFollowerFull, debug): if debug: - print('DEBUG: Follower '+nicknameFollower+'@'+domainFollowerFull+' was removed') + print('DEBUG: Follower ' + + nicknameFollower + '@' + domainFollowerFull + + ' was removed') return True if debug: - print('DEBUG: Follower '+nicknameFollower+'@'+domainFollowerFull+' was not removed') + print('DEBUG: Follower ' + + nicknameFollower + '@' + domainFollowerFull + + ' was not removed') return False -def receiveUndo(session,baseDir: str,httpPrefix: str, \ - port: int,sendThreads: [],postLog: [], \ - cachedWebfingers: {},personCache: {}, \ - messageJson: {},federationList: [], \ - debug : bool, \ - acceptedCaps=["inbox:write","objects:read"]) -> bool: + +def receiveUndo(session, baseDir: str, httpPrefix: str, + port: int, sendThreads: [], postLog: [], + cachedWebfingers: {}, personCache: {}, + messageJson: {}, federationList: [], + debug: bool, + acceptedCaps=["inbox:write", "objects:read"]) -> bool: """Receives an undo request within the POST section of HTTPServer """ if not messageJson['type'].startswith('Undo'): @@ -697,61 +734,65 @@ def receiveUndo(session,baseDir: str,httpPrefix: str, \ return False if not messageJson.get('object'): if debug: - print('DEBUG: '+messageJson['type']+' has no object') + print('DEBUG: ' + messageJson['type'] + ' has no object') return False if not isinstance(messageJson['object'], dict): if debug: - print('DEBUG: '+messageJson['type']+' object is not a dict') + print('DEBUG: ' + messageJson['type'] + ' object is not a dict') return False if not messageJson['object'].get('type'): if debug: - print('DEBUG: '+messageJson['type']+' has no object type') + print('DEBUG: ' + messageJson['type'] + ' has no object type') return False if not messageJson['object'].get('object'): if debug: - print('DEBUG: '+messageJson['type']+' has no object within object') + print('DEBUG: ' + messageJson['type'] + + ' has no object within object') return False if not isinstance(messageJson['object']['object'], str): if debug: - print('DEBUG: '+messageJson['type']+' object within object is not a string') + print('DEBUG: ' + messageJson['type'] + + ' object within object is not a string') return False - if messageJson['object']['type']=='Follow': - return receiveUndoFollow(session,baseDir,httpPrefix, \ - port,messageJson, \ - federationList, \ - debug) + if messageJson['object']['type'] == 'Follow': + return receiveUndoFollow(session, baseDir, httpPrefix, + port, messageJson, + federationList, debug) return False -def personReceiveUpdate(baseDir: str, \ - domain: str,port: int, \ - updateNickname: str,updateDomain: str,updatePort: int, \ - personJson: {},personCache: {},debug: bool) -> bool: + +def personReceiveUpdate(baseDir: str, + domain: str, port: int, + updateNickname: str, updateDomain: str, + updatePort: int, + personJson: {}, personCache: {}, debug: bool) -> bool: """Changes an actor. eg: avatar or display name change """ if debug: print('DEBUG: receiving actor update for '+personJson['url']) - domainFull=domain + domainFull = domain if port: - if port!=80 and port!=443: - domainFull=domain+':'+str(port) - updateDomainFull=updateDomain + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + updateDomainFull = updateDomain if updatePort: - if updatePort!=80 and updatePort!=443: - updateDomainFull=updateDomain+':'+str(updatePort) - actor=updateDomainFull+'/users/'+updateNickname + if updatePort != 80 and updatePort != 443: + updateDomainFull = updateDomain + ':' + str(updatePort) + actor = updateDomainFull + '/users/' + updateNickname if actor not in personJson['id']: - actor=updateDomainFull+'/profile/'+updateNickname + actor = updateDomainFull + '/profile/' + updateNickname if actor not in personJson['id']: - actor=updateDomainFull+'/channel/'+updateNickname + actor = updateDomainFull + '/channel/' + updateNickname if actor not in personJson['id']: if debug: - print('actor: '+actor) - print('id: '+personJson['id']) + print('actor: ' + actor) + print('id: ' + personJson['id']) print('DEBUG: Actor does not match id') return False - if updateDomainFull==domainFull: + if updateDomainFull == domainFull: if debug: - print('DEBUG: You can only receive actor updates for domains other than your own') + print('DEBUG: You can only receive actor updates ' + + 'for domains other than your own') return False if not personJson.get('publicKey'): if debug: @@ -761,36 +802,42 @@ def personReceiveUpdate(baseDir: str, \ if debug: print('DEBUG: actor update does not contain a public key Pem') return False - actorFilename=baseDir+'/cache/actors/'+personJson['id'].replace('/','#')+'.json' + actorFilename = baseDir + '/cache/actors/' + \ + personJson['id'].replace('/', '#') + '.json' # check that the public keys match. # If they don't then this may be a nefarious attempt to hack an account - if personCache.get(personJson['id']): - if personCache[personJson['id']]['actor']['publicKey']['publicKeyPem']!=personJson['publicKey']['publicKeyPem']: + idx = personJson['id'] + if personCache.get(idx): + if personCache[idx]['actor']['publicKey']['publicKeyPem'] != \ + personJson['publicKey']['publicKeyPem']: if debug: print('WARN: Public key does not match when updating actor') return False else: if os.path.isfile(actorFilename): - existingPersonJson=loadJson(actorFilename) + existingPersonJson = loadJson(actorFilename) if existingPersonJson: - if existingPersonJson['publicKey']['publicKeyPem']!=personJson['publicKey']['publicKeyPem']: + if existingPersonJson['publicKey']['publicKeyPem'] != \ + personJson['publicKey']['publicKeyPem']: if debug: - print('WARN: Public key does not match cached actor when updating') + print('WARN: Public key does not match ' + + 'cached actor when updating') return False # save to cache in memory - storePersonInCache(baseDir,personJson['id'],personJson,personCache) + storePersonInCache(baseDir, personJson['id'], personJson, personCache) # save to cache on file - if saveJson(personJson,actorFilename): - print('actor updated for '+personJson['id']) + if saveJson(personJson, actorFilename): + print('actor updated for ' + personJson['id']) # remove avatar if it exists so that it will be refreshed later # when a timeline is constructed - actorStr=personJson['id'].replace('/','-') - removeAvatarFromCache(baseDir,actorStr) + actorStr = personJson['id'].replace('/', '-') + removeAvatarFromCache(baseDir, actorStr) return True -def receiveUpdateToQuestion(recentPostsCache: {},messageJson: {}, \ - baseDir: str,nickname: str,domain: str) -> None: + +def receiveUpdateToQuestion(recentPostsCache: {}, messageJson: {}, + baseDir: str, nickname: str, domain: str) -> None: """Updating a question as new votes arrive """ # message url of the question @@ -798,161 +845,177 @@ def receiveUpdateToQuestion(recentPostsCache: {},messageJson: {}, \ return if not messageJson.get('actor'): return - messageId=messageJson['id'].replace('/activity','') + messageId = messageJson['id'].replace('/activity', '') if '#' in messageId: - messageId=messageId.split('#',1)[0] + messageId = messageId.split('#', 1)[0] # find the question post - postFilename=locatePost(baseDir,nickname,domain,messageId) + postFilename = locatePost(baseDir, nickname, domain, messageId) if not postFilename: return # load the json for the question - postJsonObject=loadJson(postFilename,1) + postJsonObject = loadJson(postFilename, 1) if not postJsonObject: return if not postJsonObject.get('actor'): return # does the actor match? - if postJsonObject['actor']!=messageJson['actor']: + if postJsonObject['actor'] != messageJson['actor']: return - saveJson(messageJson,postFilename) + saveJson(messageJson, postFilename) # ensure that the cached post is removed if it exists, so # that it then will be recreated - cachedPostFilename= \ - getCachedPostFilename(baseDir,nickname,domain,messageJson) + cachedPostFilename = \ + getCachedPostFilename(baseDir, nickname, domain, messageJson) if cachedPostFilename: if os.path.isfile(cachedPostFilename): os.remove(cachedPostFilename) # remove from memory cache - removePostFromCache(messageJson,recentPostsCache) + removePostFromCache(messageJson, recentPostsCache) -def receiveUpdate(recentPostsCache: {},session,baseDir: str, \ - httpPrefix: str,domain :str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - nickname: str,debug : bool) -> bool: + +def receiveUpdate(recentPostsCache: {}, session, baseDir: str, + httpPrefix: str, domain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + nickname: str, debug: bool) -> bool: """Receives an Update activity within the POST section of HTTPServer """ - if messageJson['type']!='Update': + if messageJson['type'] != 'Update': return False if not messageJson.get('actor'): if debug: - print('DEBUG: '+messageJson['type']+' has no actor') + print('DEBUG: ' + messageJson['type'] + ' has no actor') return False if not messageJson.get('object'): if debug: - print('DEBUG: '+messageJson['type']+' has no object') + print('DEBUG: ' + messageJson['type'] + ' has no object') return False if not isinstance(messageJson['object'], dict): if debug: - print('DEBUG: '+messageJson['type']+' object is not a dict') + print('DEBUG: ' + messageJson['type'] + ' object is not a dict') return False if not messageJson['object'].get('type'): if debug: - print('DEBUG: '+messageJson['type']+' object has no type') + print('DEBUG: ' + messageJson['type'] + ' object has no type') return False if '/users/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \ '/profile/' not in messageJson['actor']: if debug: - print('DEBUG: "users" or "profile" missing from actor in '+messageJson['type']) + print('DEBUG: "users" or "profile" missing from actor in ' + + messageJson['type']) return False - if messageJson['object']['type']=='Question': - receiveUpdateToQuestion(recentPostsCache,messageJson, \ - baseDir,nickname,domain) + if messageJson['object']['type'] == 'Question': + receiveUpdateToQuestion(recentPostsCache, messageJson, + baseDir, nickname, domain) if debug: print('DEBUG: Question update was received') return True - if messageJson['type']=='Person': + if messageJson['type'] == 'Person': if messageJson.get('url') and messageJson.get('id'): - print('Request to update unwrapped actor: '+messageJson['id']) - updateNickname=getNicknameFromActor(messageJson['id']) + print('Request to update unwrapped actor: ' + messageJson['id']) + updateNickname = getNicknameFromActor(messageJson['id']) if updateNickname: - updateDomain,updatePort=getDomainFromActor(messageJson['id']) - if personReceiveUpdate(baseDir, \ - domain,port, \ - updateNickname,updateDomain,updatePort, \ - messageJson, \ - personCache,debug): + updateDomain, updatePort = \ + getDomainFromActor(messageJson['id']) + if personReceiveUpdate(baseDir, domain, port, + updateNickname, updateDomain, + updatePort, messageJson, + personCache, debug): if debug: - print('DEBUG: Unwrapped profile update was received for '+messageJson['url']) + print('DEBUG: ' + + 'Unwrapped profile update was received for ' + + messageJson['url']) return True - if messageJson['object']['type']=='Person' or \ - messageJson['object']['type']=='Application' or \ - messageJson['object']['type']=='Group' or \ - messageJson['object']['type']=='Service': - if messageJson['object'].get('url') and messageJson['object'].get('id'): - print('Request to update actor: '+messageJson['actor']) - updateNickname=getNicknameFromActor(messageJson['actor']) + if messageJson['object']['type'] == 'Person' or \ + messageJson['object']['type'] == 'Application' or \ + messageJson['object']['type'] == 'Group' or \ + messageJson['object']['type'] == 'Service': + if messageJson['object'].get('url') and \ + messageJson['object'].get('id'): + print('Request to update actor: ' + messageJson['actor']) + updateNickname = getNicknameFromActor(messageJson['actor']) if updateNickname: - updateDomain,updatePort=getDomainFromActor(messageJson['actor']) - if personReceiveUpdate(baseDir, \ - domain,port, \ - updateNickname,updateDomain,updatePort, \ - messageJson['object'], \ - personCache,debug): + updateDomain, updatePort = \ + getDomainFromActor(messageJson['actor']) + if personReceiveUpdate(baseDir, + domain, port, + updateNickname, updateDomain, + updatePort, + messageJson['object'], + personCache, debug): if debug: - print('DEBUG: Profile update was received for '+messageJson['object']['url']) + print('DEBUG: Profile update was received for ' + + messageJson['object']['url']) return True - if messageJson['object'].get('capability') and messageJson['object'].get('scope'): - nickname=getNicknameFromActor(messageJson['object']['scope']) + if messageJson['object'].get('capability') and \ + messageJson['object'].get('scope'): + nickname = getNicknameFromActor(messageJson['object']['scope']) if nickname: - domain,tempPort=getDomainFromActor(messageJson['object']['scope']) + domain, tempPort = \ + getDomainFromActor(messageJson['object']['scope']) - if messageJson['object']['type']=='Capability': - if capabilitiesReceiveUpdate(baseDir,nickname,domain,port, - messageJson['actor'], \ - messageJson['object']['id'], \ - messageJson['object']['capability'], \ + if messageJson['object']['type'] == 'Capability': + capability = messageJson['object']['capability'] + if capabilitiesReceiveUpdate(baseDir, nickname, domain, port, + messageJson['actor'], + messageJson['object']['id'], + capability, debug): if debug: print('DEBUG: An update was received') return True return False -def receiveLike(recentPostsCache: {}, \ - session,handle: str,isGroup: bool,baseDir: str, \ - httpPrefix: str,domain :str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - debug : bool) -> bool: + +def receiveLike(recentPostsCache: {}, + session, handle: str, isGroup: bool, baseDir: str, + httpPrefix: str, domain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + debug: bool) -> bool: """Receives a Like activity within the POST section of HTTPServer """ - if messageJson['type']!='Like': + if messageJson['type'] != 'Like': return False if not messageJson.get('actor'): if debug: - print('DEBUG: '+messageJson['type']+' has no actor') + print('DEBUG: ' + messageJson['type'] + ' has no actor') return False if not messageJson.get('object'): if debug: - print('DEBUG: '+messageJson['type']+' has no object') + print('DEBUG: ' + messageJson['type'] + ' has no object') return False if not isinstance(messageJson['object'], str): if debug: - print('DEBUG: '+messageJson['type']+' object is not a string') + print('DEBUG: ' + messageJson['type'] + ' object is not a string') return False if not messageJson.get('to'): if debug: - print('DEBUG: '+messageJson['type']+' has no "to" list') + print('DEBUG: ' + messageJson['type'] + ' has no "to" list') return False if '/users/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \ '/profile/' not in messageJson['actor']: if debug: - print('DEBUG: "users" or "profile" missing from actor in '+messageJson['type']) + print('DEBUG: "users" or "profile" missing from actor in ' + + messageJson['type']) return False if '/statuses/' not in messageJson['object']: if debug: - print('DEBUG: "statuses" missing from object in '+messageJson['type']) + print('DEBUG: "statuses" missing from object in ' + + messageJson['type']) return False - if not os.path.isdir(baseDir+'/accounts/'+handle): - print('DEBUG: unknown recipient of like - '+handle) + if not os.path.isdir(baseDir + '/accounts/' + handle): + print('DEBUG: unknown recipient of like - ' + handle) # if this post in the outbox of the person? - postFilename=locatePost(baseDir,handle.split('@')[0],handle.split('@')[1],messageJson['object']) + postFilename = locatePost(baseDir, handle.split('@')[0], + handle.split('@')[1], + messageJson['object']) if not postFilename: if debug: print('DEBUG: post not found in inbox or outbox') @@ -961,20 +1024,21 @@ def receiveLike(recentPostsCache: {}, \ if debug: print('DEBUG: liked post found in inbox') - updateLikesCollection(recentPostsCache,baseDir,postFilename, \ - messageJson['object'], \ - messageJson['actor'],domain,debug) + updateLikesCollection(recentPostsCache, baseDir, postFilename, + messageJson['object'], + messageJson['actor'], domain, debug) return True -def receiveUndoLike(recentPostsCache: {}, \ - session,handle: str,isGroup: bool,baseDir: str, \ - httpPrefix: str,domain :str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - debug : bool) -> bool: + +def receiveUndoLike(recentPostsCache: {}, + session, handle: str, isGroup: bool, baseDir: str, + httpPrefix: str, domain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + debug: bool) -> bool: """Receives an undo like activity within the POST section of HTTPServer """ - if messageJson['type']!='Undo': + if messageJson['type'] != 'Undo': return False if not messageJson.get('actor'): return False @@ -984,30 +1048,35 @@ def receiveUndoLike(recentPostsCache: {}, \ return False if not messageJson['object'].get('type'): return False - if messageJson['object']['type']!='Like': + if messageJson['object']['type'] != 'Like': return False if not messageJson['object'].get('object'): if debug: - print('DEBUG: '+messageJson['type']+' like has no object') + print('DEBUG: ' + messageJson['type'] + ' like has no object') return False if not isinstance(messageJson['object']['object'], str): if debug: - print('DEBUG: '+messageJson['type']+' like object is not a string') + print('DEBUG: ' + messageJson['type'] + + ' like object is not a string') return False if '/users/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \ '/profile/' not in messageJson['actor']: if debug: - print('DEBUG: "users" or "profile" missing from actor in '+messageJson['type']+' like') + print('DEBUG: "users" or "profile" missing from actor in ' + + messageJson['type'] + ' like') return False if '/statuses/' not in messageJson['object']['object']: if debug: - print('DEBUG: "statuses" missing from like object in '+messageJson['type']) + print('DEBUG: "statuses" missing from like object in ' + + messageJson['type']) return False - if not os.path.isdir(baseDir+'/accounts/'+handle): - print('DEBUG: unknown recipient of undo like - '+handle) + if not os.path.isdir(baseDir + '/accounts/' + handle): + print('DEBUG: unknown recipient of undo like - ' + handle) # if this post in the outbox of the person? - postFilename=locatePost(baseDir,handle.split('@')[0],handle.split('@')[1],messageJson['object']['object']) + postFilename = \ + locatePost(baseDir, handle.split('@')[0], handle.split('@')[1], + messageJson['object']['object']) if not postFilename: if debug: print('DEBUG: unliked post not found in inbox or outbox') @@ -1015,61 +1084,67 @@ def receiveUndoLike(recentPostsCache: {}, \ return True if debug: print('DEBUG: liked post found in inbox. Now undoing.') - undoLikesCollectionEntry(recentPostsCache,baseDir,postFilename, \ - messageJson['object'],messageJson['actor'],domain,debug) + undoLikesCollectionEntry(recentPostsCache, baseDir, postFilename, + messageJson['object'], + messageJson['actor'], domain, debug) return True -def receiveBookmark(recentPostsCache: {}, \ - session,handle: str,isGroup: bool,baseDir: str, \ - httpPrefix: str,domain :str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - debug : bool) -> bool: + +def receiveBookmark(recentPostsCache: {}, + session, handle: str, isGroup: bool, baseDir: str, + httpPrefix: str, domain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + debug: bool) -> bool: """Receives a bookmark activity within the POST section of HTTPServer """ - if messageJson['type']!='Bookmark': + if messageJson['type'] != 'Bookmark': return False if not messageJson.get('actor'): if debug: - print('DEBUG: '+messageJson['type']+' has no actor') + print('DEBUG: ' + messageJson['type'] + ' has no actor') return False if not messageJson.get('object'): if debug: - print('DEBUG: '+messageJson['type']+' has no object') + print('DEBUG: ' + messageJson['type'] + ' has no object') return False if not isinstance(messageJson['object'], str): if debug: - print('DEBUG: '+messageJson['type']+' object is not a string') + print('DEBUG: ' + messageJson['type'] + ' object is not a string') return False if not messageJson.get('to'): if debug: - print('DEBUG: '+messageJson['type']+' has no "to" list') + print('DEBUG: ' + messageJson['type'] + ' has no "to" list') return False if '/users/' not in messageJson['actor']: if debug: - print('DEBUG: "users" missing from actor in '+messageJson['type']) + print('DEBUG: "users" missing from actor in ' + + messageJson['type']) return False if '/statuses/' not in messageJson['object']: if debug: - print('DEBUG: "statuses" missing from object in '+messageJson['type']) + print('DEBUG: "statuses" missing from object in ' + + messageJson['type']) return False if domain not in handle.split('@')[1]: if debug: - print('DEBUG: unrecognized domain '+handle) + print('DEBUG: unrecognized domain ' + handle) return False - domainFull=domain + domainFull = domain if port: - if port!=80 and port!=443: - domainFull=domain+':'+str(port) - nickname=handle.split('@')[0] - if not messageJson['actor'].endswith(domainFull+'/users/'+nickname): + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + nickname = handle.split('@')[0] + if not messageJson['actor'].endswith(domainFull + '/users/' + nickname): if debug: - print('DEBUG: bookmark actor should be the same as the handle sent to '+handle+' != '+messageJson['actor']) + print('DEBUG: ' + + 'bookmark actor should be the same as the handle sent to ' + + handle + ' != ' + messageJson['actor']) return False - if not os.path.isdir(baseDir+'/accounts/'+handle): - print('DEBUG: unknown recipient of bookmark - '+handle) + if not os.path.isdir(baseDir + '/accounts/' + handle): + print('DEBUG: unknown recipient of bookmark - ' + handle) # if this post in the outbox of the person? - postFilename=locatePost(baseDir,nickname,domain,messageJson['object']) + postFilename = locatePost(baseDir, nickname, domain, messageJson['object']) if not postFilename: if debug: print('DEBUG: post not found in inbox or outbox') @@ -1078,19 +1153,21 @@ def receiveBookmark(recentPostsCache: {}, \ if debug: print('DEBUG: bookmarked post was found') - updateBookmarksCollection(recentPostsCache,baseDir,postFilename, \ - messageJson['object'],messageJson['actor'],domain,debug) + updateBookmarksCollection(recentPostsCache, baseDir, postFilename, + messageJson['object'], + messageJson['actor'], domain, debug) return True -def receiveUndoBookmark(recentPostsCache: {}, \ - session,handle: str,isGroup: bool,baseDir: str, \ - httpPrefix: str,domain :str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - debug : bool) -> bool: + +def receiveUndoBookmark(recentPostsCache: {}, + session, handle: str, isGroup: bool, baseDir: str, + httpPrefix: str, domain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + debug: bool) -> bool: """Receives an undo bookmark activity within the POST section of HTTPServer """ - if messageJson['type']!='Undo': + if messageJson['type'] != 'Undo': return False if not messageJson.get('actor'): return False @@ -1100,41 +1177,47 @@ def receiveUndoBookmark(recentPostsCache: {}, \ return False if not messageJson['object'].get('type'): return False - if messageJson['object']['type']!='Bookmark': + if messageJson['object']['type'] != 'Bookmark': return False if not messageJson['object'].get('object'): if debug: - print('DEBUG: '+messageJson['type']+' like has no object') + print('DEBUG: ' + messageJson['type'] + ' like has no object') return False if not isinstance(messageJson['object']['object'], str): if debug: - print('DEBUG: '+messageJson['type']+' like object is not a string') + print('DEBUG: ' + messageJson['type'] + + ' like object is not a string') return False if '/users/' not in messageJson['actor']: if debug: - print('DEBUG: "users" missing from actor in '+messageJson['type']+' like') + print('DEBUG: "users" missing from actor in ' + + messageJson['type'] + ' like') return False if '/statuses/' not in messageJson['object']['object']: if debug: - print('DEBUG: "statuses" missing from like object in '+messageJson['type']) + print('DEBUG: "statuses" missing from like object in ' + + messageJson['type']) return False - domainFull=domain + domainFull = domain if port: - if port!=80 and port!=443: - domainFull=domain+':'+str(port) - nickname=handle.split('@')[0] + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + nickname = handle.split('@')[0] if domain not in handle.split('@')[1]: if debug: - print('DEBUG: unrecognized bookmark domain '+handle) + print('DEBUG: unrecognized bookmark domain ' + handle) return False - if not messageJson['actor'].endswith(domainFull+'/users/'+nickname): + if not messageJson['actor'].endswith(domainFull + '/users/' + nickname): if debug: - print('DEBUG: bookmark actor should be the same as the handle sent to '+handle+' != '+messageJson['actor']) + print('DEBUG: ' + + 'bookmark actor should be the same as the handle sent to ' + + handle + ' != ' + messageJson['actor']) return False - if not os.path.isdir(baseDir+'/accounts/'+handle): - print('DEBUG: unknown recipient of bookmark undo - '+handle) + if not os.path.isdir(baseDir + '/accounts/' + handle): + print('DEBUG: unknown recipient of bookmark undo - ' + handle) # if this post in the outbox of the person? - postFilename=locatePost(baseDir,nickname,domain,messageJson['object']['object']) + postFilename = locatePost(baseDir, nickname, domain, + messageJson['object']['object']) if not postFilename: if debug: print('DEBUG: unbookmarked post not found in inbox or outbox') @@ -1142,194 +1225,219 @@ def receiveUndoBookmark(recentPostsCache: {}, \ return True if debug: print('DEBUG: bookmarked post found. Now undoing.') - undoBookmarksCollectionEntry(recentPostsCache,baseDir,postFilename, \ - messageJson['object'],messageJson['actor'],domain,debug) + undoBookmarksCollectionEntry(recentPostsCache, baseDir, postFilename, + messageJson['object'], + messageJson['actor'], domain, debug) return True -def receiveDelete(session,handle: str,isGroup: bool,baseDir: str, \ - httpPrefix: str,domain :str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - debug : bool,allowDeletion: bool) -> bool: + +def receiveDelete(session, handle: str, isGroup: bool, baseDir: str, + httpPrefix: str, domain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + debug: bool, allowDeletion: bool) -> bool: """Receives a Delete activity within the POST section of HTTPServer """ - if messageJson['type']!='Delete': + if messageJson['type'] != 'Delete': return False if not messageJson.get('actor'): if debug: - print('DEBUG: '+messageJson['type']+' has no actor') + print('DEBUG: ' + messageJson['type'] + ' has no actor') return False if debug: print('DEBUG: Delete activity arrived') if not messageJson.get('object'): if debug: - print('DEBUG: '+messageJson['type']+' has no object') + print('DEBUG: ' + messageJson['type'] + ' has no object') return False if not isinstance(messageJson['object'], str): if debug: - print('DEBUG: '+messageJson['type']+' object is not a string') + print('DEBUG: ' + messageJson['type'] + ' object is not a string') return False - domainFull=domain + domainFull = domain if port: - if port!=80 and port!=443: + if port != 80 and port != 443: if ':' not in domain: - domainFull=domain+':'+str(port) - deletePrefix=httpPrefix+'://'+domainFull+'/' - if not allowDeletion and \ - (not messageJson['object'].startswith(deletePrefix) or \ - not messageJson['actor'].startswith(deletePrefix)): + domainFull = domain + ':' + str(port) + deletePrefix = httpPrefix + '://' + domainFull + '/' + if (not allowDeletion and + (not messageJson['object'].startswith(deletePrefix) or + not messageJson['actor'].startswith(deletePrefix))): if debug: print('DEBUG: delete not permitted from other instances') return False if not messageJson.get('to'): if debug: - print('DEBUG: '+messageJson['type']+' has no "to" list') + print('DEBUG: ' + messageJson['type'] + ' has no "to" list') return False if '/users/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \ '/profile/' not in messageJson['actor']: if debug: - print('DEBUG: "users" or "profile" missing from actor in '+messageJson['type']) + print('DEBUG: ' + + '"users" or "profile" missing from actor in ' + + messageJson['type']) return False if '/statuses/' not in messageJson['object']: if debug: - print('DEBUG: "statuses" missing from object in '+messageJson['type']) + print('DEBUG: "statuses" missing from object in ' + + messageJson['type']) return False if messageJson['actor'] not in messageJson['object']: if debug: print('DEBUG: actor is not the owner of the post to be deleted') - if not os.path.isdir(baseDir+'/accounts/'+handle): - print('DEBUG: unknown recipient of like - '+handle) + if not os.path.isdir(baseDir + '/accounts/' + handle): + print('DEBUG: unknown recipient of like - ' + handle) # if this post in the outbox of the person? - messageId=messageJson['object'].replace('/activity','').replace('/undo','') - removeModerationPostFromIndex(baseDir,messageId,debug) - postFilename=locatePost(baseDir,handle.split('@')[0],handle.split('@')[1],messageId) + messageId = messageJson['object'].replace('/activity', '') + messageId = messageId.replace('/undo', '') + removeModerationPostFromIndex(baseDir, messageId, debug) + postFilename = locatePost(baseDir, handle.split('@')[0], + handle.split('@')[1], messageId) if not postFilename: if debug: print('DEBUG: delete post not found in inbox or outbox') print(messageId) return True - deletePost(baseDir,httpPrefix,handle.split('@')[0],handle.split('@')[1],postFilename,debug) + deletePost(baseDir, httpPrefix, handle.split('@')[0], + handle.split('@')[1], postFilename, debug) if debug: - print('DEBUG: post deleted - '+postFilename) + print('DEBUG: post deleted - ' + postFilename) return True -def receiveAnnounce(recentPostsCache: {}, \ - session,handle: str,isGroup: bool,baseDir: str, \ - httpPrefix: str,domain :str,onionDomain: str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - debug : bool) -> bool: + +def receiveAnnounce(recentPostsCache: {}, + session, handle: str, isGroup: bool, baseDir: str, + httpPrefix: str, domain: str, onionDomain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + debug: bool) -> bool: """Receives an announce activity within the POST section of HTTPServer """ - if messageJson['type']!='Announce': + if messageJson['type'] != 'Announce': return False if '@' not in handle: if debug: - print('DEBUG: bad handle '+handle) + print('DEBUG: bad handle ' + handle) return False if not messageJson.get('actor'): if debug: - print('DEBUG: '+messageJson['type']+' has no actor') + print('DEBUG: ' + messageJson['type'] + ' has no actor') return False if debug: - print('DEBUG: receiving announce on '+handle) + print('DEBUG: receiving announce on ' + handle) if not messageJson.get('object'): if debug: - print('DEBUG: '+messageJson['type']+' has no object') + print('DEBUG: ' + messageJson['type'] + ' has no object') return False if not isinstance(messageJson['object'], str): if debug: - print('DEBUG: '+messageJson['type']+' object is not a string') + print('DEBUG: ' + messageJson['type'] + ' object is not a string') return False if not messageJson.get('to'): if debug: - print('DEBUG: '+messageJson['type']+' has no "to" list') + print('DEBUG: ' + messageJson['type'] + ' has no "to" list') return False if '/users/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \ '/profile/' not in messageJson['actor']: if debug: - print('DEBUG: "users" or "profile" missing from actor in '+messageJson['type']) + print('DEBUG: ' + + '"users" or "profile" missing from actor in ' + + messageJson['type']) return False if '/users/' not in messageJson['object'] and \ '/channel/' not in messageJson['object'] and \ '/profile/' not in messageJson['object']: if debug: - print('DEBUG: "users", "channel" or "profile" missing in '+messageJson['type']) + print('DEBUG: ' + + '"users", "channel" or "profile" missing in ' + + messageJson['type']) return False - objectDomain=messageJson['object'].replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','') + objectDomain = \ + messageJson['object'].replace('https://', '') + objectDomain = objectDomain.replace('http://', '') + objectDomain = objectDomain.replace('i2p://', '') + objectDomain = objectDomain.replace('dat://', '') if '/' in objectDomain: - objectDomain=objectDomain.split('/')[0] - if isBlockedDomain(baseDir,objectDomain): + objectDomain = objectDomain.split('/')[0] + if isBlockedDomain(baseDir, objectDomain): if debug: print('DEBUG: announced domain is blocked') return False - if not os.path.isdir(baseDir+'/accounts/'+handle): - print('DEBUG: unknown recipient of announce - '+handle) + if not os.path.isdir(baseDir + '/accounts/' + handle): + print('DEBUG: unknown recipient of announce - ' + handle) # is this post in the outbox of the person? - nickname=handle.split('@')[0] - postFilename=locatePost(baseDir,nickname,handle.split('@')[1],messageJson['object']) + nickname = handle.split('@')[0] + postFilename = locatePost(baseDir, nickname, handle.split('@')[1], + messageJson['object']) if not postFilename: if debug: print('DEBUG: announce post not found in inbox or outbox') print(messageJson['object']) return True - updateAnnounceCollection(recentPostsCache,baseDir,postFilename, \ - messageJson['actor'],domain,debug) + updateAnnounceCollection(recentPostsCache, baseDir, postFilename, + messageJson['actor'], domain, debug) if debug: - print('DEBUG: Downloading announce post '+messageJson['actor']+' -> '+messageJson['object']) - postJsonObject=downloadAnnounce(session,baseDir,httpPrefix,nickname,domain,messageJson,__version__) + print('DEBUG: Downloading announce post ' + messageJson['actor'] + + ' -> ' + messageJson['object']) + postJsonObject = downloadAnnounce(session, baseDir, httpPrefix, + nickname, domain, messageJson, + __version__) if postJsonObject: if debug: - print('DEBUG: Announce post downloaded for '+messageJson['actor']+' -> '+messageJson['object']) - storeHashTags(baseDir,nickname,postJsonObject) + print('DEBUG: Announce post downloaded for ' + + messageJson['actor'] + ' -> ' + messageJson['object']) + storeHashTags(baseDir, nickname, postJsonObject) # Try to obtain the actor for this person # so that their avatar can be shown - lookupActor=None + lookupActor = None if postJsonObject.get('attributedTo'): - lookupActor=postJsonObject['attributedTo'] + lookupActor = postJsonObject['attributedTo'] else: if postJsonObject.get('object'): if isinstance(postJsonObject['object'], dict): if postJsonObject['object'].get('attributedTo'): - lookupActor=postJsonObject['object']['attributedTo'] + lookupActor = postJsonObject['object']['attributedTo'] if lookupActor: if '/users/' in lookupActor or \ '/channel/' in lookupActor or \ '/profile/' in lookupActor: if '/statuses/' in lookupActor: - lookupActor=lookupActor.split('/statuses/')[0] + lookupActor = lookupActor.split('/statuses/')[0] if debug: - print('DEBUG: Obtaining actor for announce post '+lookupActor) + print('DEBUG: Obtaining actor for announce post ' + + lookupActor) for tries in range(6): - pubKey= \ - getPersonPubKey(baseDir,session,lookupActor, \ - personCache,debug, \ - __version__,httpPrefix, \ - domain,onionDomain) + pubKey = \ + getPersonPubKey(baseDir, session, lookupActor, + personCache, debug, + __version__, httpPrefix, + domain, onionDomain) if pubKey: - print('DEBUG: public key obtained for announce: '+lookupActor) + print('DEBUG: public key obtained for announce: ' + + lookupActor) break if debug: - print('DEBUG: Retry '+str(tries+1)+ \ - ' obtaining actor for '+lookupActor) + print('DEBUG: Retry ' + str(tries + 1) + + ' obtaining actor for ' + lookupActor) time.sleep(5) if debug: print('DEBUG: announced/repeated post arrived in inbox') return True -def receiveUndoAnnounce(recentPostsCache: {}, \ - session,handle: str,isGroup: bool,baseDir: str, \ - httpPrefix: str,domain :str,port: int, \ - sendThreads: [],postLog: [],cachedWebfingers: {}, \ - personCache: {},messageJson: {},federationList: [], \ - debug : bool) -> bool: + +def receiveUndoAnnounce(recentPostsCache: {}, + session, handle: str, isGroup: bool, baseDir: str, + httpPrefix: str, domain: str, port: int, + sendThreads: [], postLog: [], cachedWebfingers: {}, + personCache: {}, messageJson: {}, federationList: [], + debug: bool) -> bool: """Receives an undo announce activity within the POST section of HTTPServer """ - if messageJson['type']!='Undo': + if messageJson['type'] != 'Undo': return False if not messageJson.get('actor'): return False @@ -1341,18 +1449,21 @@ def receiveUndoAnnounce(recentPostsCache: {}, \ return False if not isinstance(messageJson['object']['object'], str): return False - if messageJson['object']['type']!='Announce': + if messageJson['object']['type'] != 'Announce': return False if '/users/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \ '/profile/' not in messageJson['actor']: if debug: - print('DEBUG: "users" or "profile" missing from actor in '+messageJson['type']+' announce') + print('DEBUG: "users" or "profile" missing from actor in ' + + messageJson['type'] + ' announce') return False - if not os.path.isdir(baseDir+'/accounts/'+handle): - print('DEBUG: unknown recipient of undo announce - '+handle) + if not os.path.isdir(baseDir + '/accounts/' + handle): + print('DEBUG: unknown recipient of undo announce - ' + handle) # if this post in the outbox of the person? - postFilename=locatePost(baseDir,handle.split('@')[0],handle.split('@')[1],messageJson['object']['object']) + postFilename = locatePost(baseDir, handle.split('@')[0], + handle.split('@')[1], + messageJson['object']['object']) if not postFilename: if debug: print('DEBUG: undo announce post not found in inbox or outbox') @@ -1361,21 +1472,23 @@ def receiveUndoAnnounce(recentPostsCache: {}, \ if debug: print('DEBUG: announced/repeated post to be undone found in inbox') - postJsonObject=loadJson(postFilename) + postJsonObject = loadJson(postFilename) if postJsonObject: if not postJsonObject.get('type'): - if postJsonObject['type']!='Announce': + if postJsonObject['type'] != 'Announce': if debug: - print("DEBUG: Attempt to undo something which isn't an announcement") + print("DEBUG: Attempt to undo something " + + "which isn't an announcement") return False - undoAnnounceCollectionEntry(recentPostsCache,baseDir,postFilename, \ - messageJson['actor'],domain,debug) + undoAnnounceCollectionEntry(recentPostsCache, baseDir, postFilename, + messageJson['actor'], domain, debug) if os.path.isfile(postFilename): os.remove(postFilename) return True -def populateReplies(baseDir :str,httpPrefix :str,domain :str, \ - messageJson :{},maxReplies: int,debug :bool) -> bool: + +def populateReplies(baseDir: str, httpPrefix: str, domain: str, + messageJson: {}, maxReplies: int, debug: bool) -> bool: """Updates the list of replies for a post on this domain if a reply to it arrives """ @@ -1389,59 +1502,64 @@ def populateReplies(baseDir :str,httpPrefix :str,domain :str, \ return False if not messageJson['object'].get('to'): return False - replyTo=messageJson['object']['inReplyTo'] + replyTo = messageJson['object']['inReplyTo'] if debug: print('DEBUG: post contains a reply') # is this a reply to a post on this domain? - if not replyTo.startswith(httpPrefix+'://'+domain+'/'): + if not replyTo.startswith(httpPrefix + '://' + domain + '/'): if debug: print('DEBUG: post is a reply to another not on this domain') print(replyTo) - print('Expected: '+httpPrefix+'://'+domain+'/') + print('Expected: ' + httpPrefix + '://' + domain + '/') return False - replyToNickname=getNicknameFromActor(replyTo) + replyToNickname = getNicknameFromActor(replyTo) if not replyToNickname: - print('DEBUG: no nickname found for '+replyTo) + print('DEBUG: no nickname found for ' + replyTo) return False - replyToDomain,replyToPort=getDomainFromActor(replyTo) + replyToDomain, replyToPort = getDomainFromActor(replyTo) if not replyToDomain: if debug: - print('DEBUG: no domain found for '+replyTo) + print('DEBUG: no domain found for ' + replyTo) return False - postFilename=locatePost(baseDir,replyToNickname,replyToDomain,replyTo) + postFilename = locatePost(baseDir, replyToNickname, + replyToDomain, replyTo) if not postFilename: if debug: - print('DEBUG: post may have expired - '+replyTo) + print('DEBUG: post may have expired - ' + replyTo) return False # populate a text file containing the ids of replies - postRepliesFilename=postFilename.replace('.json','.replies') - messageId=messageJson['id'].replace('/activity','').replace('/undo','') + postRepliesFilename = postFilename.replace('.json', '.replies') + messageId = messageJson['id'].replace('/activity', '') + messageId = messageId.replace('/undo', '') if os.path.isfile(postRepliesFilename): - numLines=sum(1 for line in open(postRepliesFilename)) - if numLines>maxReplies: + numLines = sum(1 for line in open(postRepliesFilename)) + if numLines > maxReplies: return False if messageId not in open(postRepliesFilename).read(): - repliesFile=open(postRepliesFilename, "a") - repliesFile.write(messageId+'\n') + repliesFile = open(postRepliesFilename, "a") + repliesFile.write(messageId + '\n') repliesFile.close() else: - repliesFile=open(postRepliesFilename, "w") - repliesFile.write(messageId+'\n') + repliesFile = open(postRepliesFilename, "w") + repliesFile.write(messageId + '\n') repliesFile.close() return True + def estimateNumberOfMentions(content: str) -> int: """Returns a rough estimate of the number of mentions """ - return int(content.count('@')/2) + return int(content.count('@') / 2) + def estimateNumberOfEmoji(content: str) -> int: """Returns a rough estimate of the number of emoji """ - return int(content.count(':')/2) + return int(content.count(':') / 2) -def validPostContent(baseDir: str,nickname: str,domain: str, \ - messageJson: {},maxMentions: int,maxEmoji: int) -> bool: + +def validPostContent(baseDir: str, nickname: str, domain: str, + messageJson: {}, maxMentions: int, maxEmoji: int) -> bool: """Is the content of a received post valid? Check for bad html Check for hellthreads @@ -1461,44 +1579,52 @@ def validPostContent(baseDir: str,nickname: str,domain: str, \ if 'Z' not in messageJson['object']['published']: return False # check for bad html - invalidStrings=['