diff --git a/availability.py b/availability.py new file mode 100644 index 000000000..8b1ee1959 --- /dev/null +++ b/availability.py @@ -0,0 +1,137 @@ +__filename__ = "availability.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "0.0.1" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import json +import commentjson +import os +from webfinger import webfingerHandle +from auth import createBasicAuthHeader +from posts import getPersonBox +from session import postJson +from utils import getNicknameFromActor +from utils import getDomainFromActor + +def setAvailability(baseDir: str,nickname: str,domain: str, \ + status: str) -> bool: + """Set an availability status + """ + # avoid giant strings + if len(status)>128: + return False + actorFilename=baseDir+'/accounts/'+nickname+'@'+domain+'.json' + if not os.path.isfile(actorFilename): + return False + with open(actorFilename, 'r') as fp: + actorJson=commentjson.load(fp) + actorJson['availability']=status + with open(actorFilename, 'w') as fp: + commentjson.dump(actorJson, fp, indent=4, sort_keys=False) + return True + +def getAvailability(baseDir: str,nickname: str,domain: str) -> str: + """Returns the availability for a given person + """ + actorFilename=baseDir+'/accounts/'+nickname+'@'+domain+'.json' + if not os.path.isfile(actorFilename): + return False + with open(actorFilename, 'r') as fp: + actorJson=commentjson.load(fp) + if not actorJson.get('availability'): + return None + return actorJson['availability'] + return None + +def outboxAvailability(baseDir: str,nickname: str,messageJson: {},debug: bool) -> bool: + """Handles receiving an availability update + """ + if not messageJson.get('type'): + return False + if not messageJson['type']=='Availability': + return False + if not messageJson.get('actor'): + return False + if not messageJson.get('object'): + return False + if not isinstance(messageJson['object'], str): + return False + + actorNickname=getNicknameFromActor(messageJson['actor']) + if actorNickname!=nickname: + return False + domain,port=getDomainFromActor(messageJson['actor']) + status=messageJson['object'].replace('"','') + + return setAvailability(baseDir,nickname,domain,status) + +def sendAvailabilityViaServer(session,nickname: str,password: str, + domain: str,port: int, \ + httpPrefix: str, \ + status: str, \ + cachedWebfingers: {},personCache: {}, \ + debug: bool) -> {}: + """Sets the availability for a person via c2s + """ + if not session: + print('WARN: No session for sendAvailabilityViaServer') + return 6 + + domainFull=domain + if port!=80 and port!=443: + domainFull=domain+':'+str(port) + + toUrl = httpPrefix+'://'+domainFull+'/users/'+nickname + ccUrl = httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers' + + newAvailabilityJson = { + 'type': 'Availability', + 'actor': httpPrefix+'://'+domainFull+'/users/'+nickname, + 'object': '"'+status+'"', + 'to': [toUrl], + 'cc': [ccUrl] + } + + handle=httpPrefix+'://'+domainFull+'/@'+nickname + + # lookup the inbox for the To handle + wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers) + if not wfRequest: + if debug: + print('DEBUG: announce webfinger failed for '+handle) + return 1 + + postToBox='outbox' + + # get the actor inbox for the To handle + inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition = \ + getPersonBox(session,wfRequest,personCache,postToBox) + + if not inboxUrl: + if debug: + print('DEBUG: No '+postToBox+' was found for '+handle) + return 3 + if not fromPersonId: + if debug: + print('DEBUG: No actor was found for '+handle) + return 4 + + authHeader=createBasicAuthHeader(Nickname,password) + + headers = {'host': domain, \ + 'Content-type': 'application/json', \ + 'Authorization': authHeader} + postResult = \ + postJson(session,newAvailabilityJson,[],inboxUrl,headers,"inbox:write") + #if not postResult: + # if debug: + # print('DEBUG: POST announce failed for c2s to '+inboxUrl) + # return 5 + + if debug: + print('DEBUG: c2s POST availability success') + + return newAvailabilityJson diff --git a/daemon.py b/daemon.py index 4b7e41aac..762e0d9b0 100644 --- a/daemon.py +++ b/daemon.py @@ -43,6 +43,7 @@ from blocking import outboxUndoBlock from config import setConfigParam from roles import outboxDelegate from skills import outboxSkills +from availability import outboxAvailability import os import sys @@ -225,8 +226,11 @@ class PubServer(BaseHTTPRequestHandler): print('DEBUG: handle delegation requests') outboxDelegate(self.server.baseDir,self.postToNickname,messageJson,self.server.debug) if self.server.debug: - print('DEBUG: handle skills changes requestsw') + print('DEBUG: handle skills changes requests') outboxSkills(self.server.baseDir,self.postToNickname,messageJson,self.server.debug) + if self.server.debug: + print('DEBUG: handle availability changes requests') + outboxAvailability(self.server.baseDir,self.postToNickname,messageJson,self.server.debug) if self.server.debug: print('DEBUG: handle any like requests') outboxLike(self.server.baseDir,self.server.httpPrefix, \ diff --git a/epicyon.py b/epicyon.py index f1cbdc9d0..0fa99c532 100644 --- a/epicyon.py +++ b/epicyon.py @@ -6,7 +6,6 @@ __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" - from person import createPerson from person import createSharedInbox from person import createCapabilitiesInbox @@ -16,7 +15,6 @@ from person import validNickname from person import setProfileImage from skills import setSkillLevel from roles import setRole -from person import setAvailability from person import setOrganizationScheme from webfinger import webfingerHandle from posts import getPosts @@ -69,6 +67,8 @@ from blocking import sendBlockViaServer from blocking import sendUndoBlockViaServer from roles import sendRoleViaServer from skills import sendSkillViaServer +from availability import setAvailability +from availability import sendAvailabilityViaServer import argparse def str2bool(v): @@ -727,14 +727,6 @@ if args.backgroundImage: print('Background image was not added for '+args.nickname) sys.exit() -if args.availability: - if not nickname: - print('No nickname given') - sys.exit() - if setAvailability(baseDir,nickname,domain,args.availability): - print('Availablity set to '+args.availability) - sys.exit() - if args.project: if not args.delegate and not args.undelegate: if not nickname: @@ -786,6 +778,31 @@ if args.skill: time.sleep(1) sys.exit() +if args.availability: + if not nickname: + print('Specify a nickname with the --nickname option') + sys.exit() + + if not args.password: + print('Specify a password with the --password option') + sys.exit() + + session = createSession(domain,port,useTor) + personCache={} + cachedWebfingers={} + print('Sending availability status of '+nickname+' as '+args.availability) + + sendAvailabilityViaServer(session,nickname,args.password, + domain,port, \ + httpPrefix, \ + args.availability, \ + cachedWebfingers,personCache, \ + True) + for i in range(10): + # TODO detect send success/fail + time.sleep(1) + sys.exit() + if federationList: print('Federating with: '+str(federationList)) diff --git a/person.py b/person.py index 5419054a3..8c94a2379 100644 --- a/person.py +++ b/person.py @@ -106,23 +106,6 @@ def setOrganizationScheme(baseDir: str,nickname: str,domain: str, \ commentjson.dump(actorJson, fp, indent=4, sort_keys=False) return True -def setAvailability(baseDir: str,nickname: str,domain: str, \ - status: str) -> bool: - """Set an availability status - """ - # avoid giant strings - if len(status)>128: - return False - actorFilename=baseDir+'/accounts/'+nickname+'@'+domain+'.json' - if not os.path.isfile(actorFilename): - return False - with open(actorFilename, 'r') as fp: - actorJson=commentjson.load(fp) - actorJson['availability']=status - with open(actorFilename, 'w') as fp: - commentjson.dump(actorJson, fp, indent=4, sort_keys=False) - return True - def createPersonBase(baseDir: str,nickname: str,domain: str,port: int, \ httpPrefix: str, saveToFile: bool,password=None) -> (str,str,{},{}): """Returns the private key, public key, actor and webfinger endpoint diff --git a/skills.py b/skills.py index 43177ae17..4c3bb9a82 100644 --- a/skills.py +++ b/skills.py @@ -69,8 +69,8 @@ def outboxSkills(baseDir: str,nickname: str,messageJson: {},debug: bool) -> bool if actorNickname!=nickname: return False domain,port=getDomainFromActor(messageJson['actor']) - skill=messageJson['object'].split(';')[0].strip() - skillLevelPercent=messageJson['object'].split(';')[1].strip() + skill=messageJson['object'].replace('"','').split(';')[0].strip() + skillLevelPercent=int(messageJson['object'].replace('"','').split(';')[1].strip()) return setSkillLevel(baseDir,nickname,domain, \ skill,skillLevelPercent) @@ -92,7 +92,7 @@ def sendSkillViaServer(session,nickname: str,password: str, domainFull=domain+':'+str(port) toUrl = httpPrefix+'://'+domainFull+'/users/'+nickname - ccUrl = httpPrefix+'://'+domainFull+'/users/'+Nickname+'/followers' + ccUrl = httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers' if skillLevelPercent: skillStr=skill+';'+str(skillLevelPercent) @@ -101,7 +101,7 @@ def sendSkillViaServer(session,nickname: str,password: str, newSkillJson = { 'type': 'Skill', 'actor': httpPrefix+'://'+domainFull+'/users/'+nickname, - 'object': skill+';'+str(skillLevelPercent), + 'object': '"'+skillStr+'"', 'to': [toUrl], 'cc': [ccUrl] }