diff --git a/delete.py b/delete.py new file mode 100644 index 00000000..ce003af3 --- /dev/null +++ b/delete.py @@ -0,0 +1,122 @@ +__filename__ = "delete.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "0.0.1" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +import json +import commentjson +from utils import getStatusNumber +from utils import createOutboxDir +from utils import urlPermitted +from utils import getNicknameFromActor +from utils import getDomainFromActor +from posts import sendSignedJson + +def createDelete(session,baseDir: str,federationList: [], \ + nickname: str, domain: str, port: int, \ + toUrl: str, ccUrl: str, httpPrefix: str, \ + objectUrl: str,clientToServer: bool, \ + sendThreads: [],postLog: [], \ + personCache: {},cachedWebfingers: {}, \ + debug: bool) -> {}: + """Creates an delete message + Typically toUrl will be https://www.w3.org/ns/activitystreams#Public + and ccUrl might be a specific person whose post is to be deleted + objectUrl is typically the url of the message, corresponding to url + or atomUri in createPostBase + """ + if not urlPermitted(objectUrl,federationList,"inbox:write"): + return None + + if ':' in domain: + domain=domain.split(':')[0] + fullDomain=domain + if port!=80 and port!=443: + fullDomain=domain+':'+str(port) + + statusNumber,published = getStatusNumber() + newDeleteId= \ + httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber + newDelete = { + 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, + 'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber, + 'cc': [], + 'id': newDeleteId+'/activity', + 'object': objectUrl, + 'published': published, + 'to': [toUrl], + 'type': 'Delete' + } + if ccUrl: + if len(ccUrl)>0: + newDelete['cc']=[ccUrl] + + deleteNickname=None + deleteDomain=None + deletePort=None + if '/users/' in objectUrl: + deleteNickname=getNicknameFromActor(objectUrl) + deleteDomain,deletePort=getDomainFromActor(objectUrl) + + if deleteNickname and deleteDomain: + sendSignedJson(newDelete,session,baseDir, \ + nickname,domain,port, \ + deleteNickname,deleteDomain,deletePort, \ + 'https://www.w3.org/ns/activitystreams#Public', \ + httpPrefix,True,clientToServer,federationList, \ + sendThreads,postLog,cachedWebfingers,personCache,debug) + + return newDelete + +def deletePublic(session,baseDir: str,federationList: [], \ + nickname: str, domain: str, port: int, httpPrefix: str, \ + objectUrl: str,clientToServer: bool, \ + sendThreads: [],postLog: [], \ + personCache: {},cachedWebfingers: {}, \ + debug: bool) -> {}: + """Makes a public delete activity + """ + fromDomain=domain + if port!=80 and port!=443: + if ':' not in domain: + fromDomain=domain+':'+str(port) + + toUrl = 'https://www.w3.org/ns/activitystreams#Public' + ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers' + return createDelete(session,baseDir,federationList, \ + nickname,domain,port, \ + toUrl,ccUrl,httpPrefix, \ + objectUrl,clientToServer, \ + sendThreads,postLog, \ + personCache,cachedWebfingers, \ + debug) + +def deletePost(session,baseDir: str,federationList: [], \ + nickname: str, domain: str, port: int, httpPrefix: str, \ + deleteNickname: str, deleteDomain: str, \ + deletePort: int, deleteHttpsPrefix: str, \ + deleteStatusNumber: int,clientToServer: bool, \ + sendThreads: [],postLog: [], \ + personCache: {},cachedWebfingers: {}, \ + debug: bool) -> {}: + """Deletes a given status post + """ + deletedDomain=deleteDomain + if deletePort!=80 and deletePort!=443: + if ':' not in deletedDomain: + deletedDomain=deletedDomain+':'+str(deletePort) + + objectUrl = deleteHttpsPrefix + '://'+deletedDomain+'/users/'+ \ + deleteNickname+'/statuses/'+str(deleteStatusNumber) + + return deletePublic(session,baseDir,federationList, \ + nickname,domain,port,httpPrefix, \ + objectUrl,clientToServer, \ + sendThreads,postLog, \ + personCache,cachedWebfingers, \ + debug) + diff --git a/inbox.py b/inbox.py index a9998440..338fbb10 100644 --- a/inbox.py +++ b/inbox.py @@ -90,6 +90,7 @@ def inboxPermittedMessage(domain: str,messageJson: {},federationList: []) -> boo if messageJson['type']!='Follow' and \ messageJson['type']!='Like' and \ + messageJson['type']!='Delete' and \ messageJson['type']!='Announce': if messageJson.get('object'): if messageJson['object'].get('inReplyTo'): @@ -438,6 +439,53 @@ def receiveLike(session,handle: str,baseDir: str, \ updateLikesCollection(postFilename,messageJson['object'],messageJson['actor'],debug) return True +def receiveDelete(session,handle: str,baseDir: str, \ + httpPrefix: str,domain :str,port: int, \ + sendThreads: [],postLog: [],cachedWebfingers: {}, \ + personCache: {},messageJson: {},federationList: [], \ + debug : bool) -> bool: + """Receives a Delete activity within the POST section of HTTPServer + """ + if messageJson['type']!='Delete': + return False + if not messageJson.get('actor'): + if debug: + print('DEBUG: '+messageJson['type']+' has no actor') + return False + if not messageJson.get('object'): + if debug: + 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') + return False + if not messageJson.get('to'): + if debug: + 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']) + return False + if '/statuses/' not in messageJson['object']: + if debug: + 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 this post in the outbox of the person? + postFilename=locatePost(baseDir,handle.split('@')[0],handle.split('@')[1],messageJson['object']) + if not postFilename: + if debug: + print('DEBUG: delete post not found in inbox or outbox') + print(messageJson['object']) + return True + os.remove(postFilename) + if debug: + print('DEBUG: post deleted - '+postFilename) + return True + def receiveAnnounce(session,handle: str,baseDir: str, \ httpPrefix: str,domain :str,port: int, \ sendThreads: [],postLog: [],cachedWebfingers: {}, \ @@ -518,6 +566,18 @@ def inboxAfterCapabilities(session,keyId: str,handle: str,messageJson: {}, \ if debug: print('DEBUG: Announce accepted from '+keyId) + if receiveDelete(session,handle, \ + baseDir,httpPrefix, \ + domain,port, \ + sendThreads,postLog, \ + cachedWebfingers, \ + personCache, \ + messageJson, \ + federationList, \ + debug): + if debug: + print('DEBUG: Announce accepted from '+keyId) + if debug: print('DEBUG: object capabilities passed') print('copy from '+queueFilename+' to '+destinationFilename) @@ -525,7 +585,11 @@ def inboxAfterCapabilities(session,keyId: str,handle: str,messageJson: {}, \ copyfile(queueFilename,destinationFilename) return True -def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [],cachedWebfingers: {},personCache: {},queue: [],domain: str,port: int,useTor: bool,federationList: [],ocapAlways: bool,debug: bool,acceptedCaps=["inbox:write","objects:read"]) -> None: +def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [], \ + cachedWebfingers: {},personCache: {},queue: [], \ + domain: str,port: int,useTor: bool,federationList: [], \ + ocapAlways: bool,debug: bool, \ + acceptedCaps=["inbox:write","objects:read"]) -> None: """Processes received items and moves them to the appropriate directories """