def postImage(session, attachImageFilename: str, federationList: [], inboxUrl: str, headers: {}, capability: str) -> str: """Post an image to the inbox of another person or outbox via c2s Supplying a capability, such as "inbox:write" """ # always allow capability requests if not capability.startswith('cap'): # check that we are posting to a permitted domain if not urlPermitted(inboxUrl, federationList, capability): print('postJson: ' + inboxUrl + ' not permitted') return None if not (attachImageFilename.endswith('.jpg') or \ attachImageFilename.endswith('.jpeg') or \ attachImageFilename.endswith('.png') or \ attachImageFilename.endswith('.gif')): print('Image must be png, jpg, or gif') return None if not os.path.isfile(attachImageFilename): print('Image not found: ' + attachImageFilename) return None contentType = 'image/jpeg' if attachImageFilename.endswith('.png'): contentType = 'image/png' if attachImageFilename.endswith('.gif'): contentType = 'image/gif' headers['Content-type'] = contentType with open(attachImageFilename, 'rb') as avFile: mediaBinary = avFile.read() postResult = session.post(url=inboxUrl, data=mediaBinary, headers=headers) return postResult.text return None
def postJsonString(session,postJsonStr: str, \ federationList: [], \ inboxUrl: str, \ headers: {}, \ capability: str, \ debug: bool) -> bool: """Post a json message string to the inbox of another person Supplying a capability, such as "inbox:write" NOTE: Here we post a string rather than the original json so that conversions between string and json format don't invalidate the message body digest of http signatures """ # always allow capability requests if not capability.startswith('cap'): # check that we are posting to a permitted domain if not urlPermitted(inboxUrl, federationList, capability): print('postJson: ' + inboxUrl + ' not permitted by capabilities') return None postResult = session.post(url=inboxUrl, data=postJsonStr, headers=headers) if postResult.status_code < 200 or postResult.status_code > 202: if postResult.status_code == 401: print('WARN: >>> Post to ' + inboxUrl + ' is unauthorized <<<') else: print('WARN: Failed to post to ' + inboxUrl) print('status code ' + str(postResult.status_code)) return False return True
def undoAnnounce(session,baseDir: str,federationList: [], \ nickname: str, domain: str, port: int, \ toUrl: str, ccUrl: str, httpPrefix: str, \ objectUrl: str, saveToFile: bool, \ clientToServer: bool, \ sendThreads: [],postLog: [], \ personCache: {},cachedWebfingers: {}, \ debug: bool) -> {}: """Undoes an announce message Typically toUrl will be https://www.w3.org/ns/activitystreams#Public and ccUrl might be a specific person whose post was repeated and the objectUrl is typically the url of the message which was repeated, 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: if port!=80 and port!=443: if ':' not in domain: fullDomain=domain+':'+str(port) newUndoAnnounce = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'type': 'Undo', 'cc': [], 'to': [toUrl], 'object': { 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'cc': [], 'object': objectUrl, 'to': [toUrl], 'type': 'Announce' } } if ccUrl: if len(ccUrl)>0: newUndoAnnounce['object']['cc']=[ccUrl] announceNickname=None announceDomain=None announcePort=None if '/users/' in objectUrl or '/profile/' in objectUrl: announceNickname=getNicknameFromActor(objectUrl) announceDomain,announcePort=getDomainFromActor(objectUrl) if announceNickname and announceDomain: sendSignedJson(newUndoAnnounce,session,baseDir, \ nickname,domain,port, \ announceNickname,announceDomain,announcePort, \ 'https://www.w3.org/ns/activitystreams#Public', \ httpPrefix,True,clientToServer,federationList, \ sendThreads,postLog,cachedWebfingers,personCache,debug) return newUndoAnnounce
def postJson(session, postJsonObject: {}, federationList: [], inboxUrl: str, headers: {}, capability: str) -> str: """Post a json message to the inbox of another person Supplying a capability, such as "inbox:write" """ # always allow capability requests if not capability.startswith('cap'): # check that we are posting to a permitted domain if not urlPermitted(inboxUrl, federationList, capability): print('postJson: ' + inboxUrl + ' not permitted') return None postResult = session.post(url=inboxUrl, data=json.dumps(postJsonObject), headers=headers) return postResult.text
def createAcceptReject(baseDir: str,federationList: [], \ nickname: str,domain: str,port: int, \ toUrl: str,ccUrl: str,httpPrefix: str, \ objectJson: {},ocapJson,acceptType: str) -> {}: """Accepts or rejects something (eg. a follow request or offer) Typically toUrl will be https://www.w3.org/ns/activitystreams#Public and ccUrl might be a specific person favorited or repeated and the followers url objectUrl is typically the url of the message, corresponding to url or atomUri in createPostBase """ if not objectJson.get('actor'): return None if not urlPermitted(objectJson['actor'], federationList, "inbox:write"): return None if port: if port != 80 and port != 443: if ':' not in domain: domain = domain + ':' + str(port) newAccept = { "@context": "https://www.w3.org/ns/activitystreams", 'type': acceptType, 'actor': httpPrefix + '://' + domain + '/users/' + nickname, 'to': [toUrl], 'cc': [], 'object': objectJson } if ccUrl: if len(ccUrl) > 0: newAccept['cc'] = [ccUrl] # attach capabilities for follow accept if ocapJson: newAccept['capabilities'] = ocapJson return newAccept
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 a 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: if port != 80 and port != 443: if ':' not in domain: fullDomain = domain + ':' + str(port) statusNumber, published = getStatusNumber() newDeleteId= \ httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber newDelete = { "@context": "https://www.w3.org/ns/activitystreams", '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 or '/profile/' 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 createAnnounce(session,baseDir: str,federationList: [], \ nickname: str, domain: str, port: int, \ toUrl: str, ccUrl: str, httpPrefix: str, \ objectUrl: str, saveToFile: bool, \ clientToServer: bool, \ sendThreads: [],postLog: [], \ personCache: {},cachedWebfingers: {}, \ debug: bool,projectVersion: str) -> {}: """Creates an announce message Typically toUrl will be https://www.w3.org/ns/activitystreams#Public and ccUrl might be a specific person favorited or repeated and the followers url 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: if port!=80 and port!=443: if ':' not in domain: fullDomain=domain+':'+str(port) statusNumber,published = getStatusNumber() newAnnounceId= \ httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber newAnnounce = { "@context": "https://www.w3.org/ns/activitystreams", 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber, 'cc': [], 'id': newAnnounceId+'/activity', 'object': objectUrl, 'published': published, 'to': [toUrl], 'type': 'Announce' } if ccUrl: if len(ccUrl)>0: newAnnounce['cc']=[ccUrl] if saveToFile: outboxDir = createOutboxDir(nickname,domain,baseDir) filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json' with open(filename, 'w') as fp: commentjson.dump(newAnnounce, fp, indent=4, sort_keys=False) announceNickname=None announceDomain=None announcePort=None if '/users/' in objectUrl or '/profile/' in objectUrl: announceNickname=getNicknameFromActor(objectUrl) announceDomain,announcePort=getDomainFromActor(objectUrl) if announceNickname and announceDomain: sendSignedJson(newAnnounce,session,baseDir, \ nickname,domain,port, \ announceNickname,announceDomain,announcePort, \ 'https://www.w3.org/ns/activitystreams#Public', \ httpPrefix,True,clientToServer,federationList, \ sendThreads,postLog,cachedWebfingers,personCache, \ debug,projectVersion) return newAnnounce