Reduce some line lengths

master
Bob Mottram 2019-07-06 18:00:22 +01:00
parent e3be2f4328
commit 3a6fe719a9
8 changed files with 192 additions and 95 deletions

View File

@ -8,6 +8,8 @@ Also: https://raw.githubusercontent.com/w3c/activitypub/gh-pages/activitypub-tut
https://blog.dereferenced.org/what-is-ocap-and-why-should-i-care https://blog.dereferenced.org/what-is-ocap-and-why-should-i-care
https://alexcastano.com/what-is-activity-pub
This project is currently *pre alpha* and not recommended for any real world uses. This project is currently *pre alpha* and not recommended for any real world uses.
## Goals ## Goals

View File

@ -15,11 +15,15 @@ from utils import getDomainFromActor
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import domainPermitted from utils import domainPermitted
def createAcceptReject(baseDir: str,federationList: [],capsList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,httpPrefix: str,objectUrl: str,acceptType: str) -> {}: def createAcceptReject(baseDir: str,federationList: [],capsList: [], \
"""Accepts or rejects something (eg. a follow request) nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,httpPrefix: str, \
objectUrl: str,acceptType: str) -> {}:
"""Accepts or rejects something (eg. a follow request or offer)
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public 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 and ccUrl might be a specific person favorited or repeated and
objectUrl is typically the url of the message, corresponding to url or atomUri in createPostBase the followers url objectUrl is typically the url of the message,
corresponding to url or atomUri in createPostBase
""" """
if not urlPermitted(objectUrl,federationList,capsList,"inbox:write"): if not urlPermitted(objectUrl,federationList,capsList,"inbox:write"):
return None return None
@ -39,13 +43,28 @@ def createAcceptReject(baseDir: str,federationList: [],capsList: [],nickname: st
newAccept['cc']=ccUrl newAccept['cc']=ccUrl
return newAccept return newAccept
def createAccept(baseDir: str,federationList: [],capsList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,httpPrefix: str,objectUrl: str) -> {}: def createAccept(baseDir: str,federationList: [],capsList: [], \
return createAcceptReject(baseDir,federationList,capsList,nickname,domain,port,toUrl,ccUrl,httpPrefix,objectUrl,'Accept') nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,httpPrefix: str, \
objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,capsList, \
nickname,domain,port, \
toUrl,ccUrl,httpPrefix, \
objectUrl,'Accept')
def createReject(baseDir: str,federationList: [],capsList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,httpPrefix: str,objectUrl: str) -> {}: def createReject(baseDir: str,federationList: [],capsList: [], \
return createAcceptReject(baseDir,federationList,capsList,nickname,domain,port,toUrl,ccUrl,httpPrefix,objectUrl,'Reject') nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,httpPrefix: str, \
objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,capsList, \
nickname,domain,port, \
toUrl,ccUrl, \
httpPrefix,objectUrl,'Reject')
def receiveAcceptReject(session,baseDir: str,httpPrefix: str,port: int,sendThreads: [],postLog: [],cachedWebfingers: {},personCache: {},messageJson: {},federationList: [],capsList: [],debug : bool) -> bool: def receiveAcceptReject(session,baseDir: str,httpPrefix: str,port: int, \
sendThreads: [],postLog: [],cachedWebfingers: {}, \
personCache: {},messageJson: {},federationList: [], \
capsList: [],debug : bool) -> bool:
"""Receives an Accept or Reject within the POST section of HTTPServer """Receives an Accept or Reject within the POST section of HTTPServer
""" """
if messageJson['type']!='Accept' and messageJson['type']!='Reject': if messageJson['type']!='Accept' and messageJson['type']!='Reject':

View File

@ -18,8 +18,9 @@ def createAnnounce(baseDir: str,federationList: [], capsList: [], \
objectUrl: str, saveToFile: bool) -> {}: objectUrl: str, saveToFile: bool) -> {}:
"""Creates an announce message """Creates an announce message
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public 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 and ccUrl might be a specific person favorited or repeated and the
objectUrl is typically the url of the message, corresponding to url or atomUri in createPostBase followers url objectUrl is typically the url of the message,
corresponding to url or atomUri in createPostBase
""" """
if not urlPermitted(objectUrl,federationList,capsList,"inbox:write"): if not urlPermitted(objectUrl,federationList,capsList,"inbox:write"):
return None return None
@ -28,7 +29,8 @@ def createAnnounce(baseDir: str,federationList: [], capsList: [], \
domain=domain+':'+str(port) domain=domain+':'+str(port)
statusNumber,published = getStatusNumber() statusNumber,published = getStatusNumber()
newAnnounceId=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber newAnnounceId= \
httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
newAnnounce = { newAnnounce = {
'actor': httpPrefix+'://'+domain+'/users/'+nickname, 'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'atomUri': httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber, 'atomUri': httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
@ -79,5 +81,6 @@ def repeatPost(baseDir: str,federationList: [], \
objectUrl = announceHttpsPrefix + '://'+announcedDomain+'/users/'+ \ objectUrl = announceHttpsPrefix + '://'+announcedDomain+'/users/'+ \
announceNickname+'/statuses/'+str(announceStatusNumber) announceNickname+'/statuses/'+str(announceStatusNumber)
return announcePublic(baseDir,nickname, domain, port, httpPrefix, objectUrl, saveToFile) return announcePublic(baseDir,nickname, domain, port, \
httpPrefix, objectUrl, saveToFile)

View File

@ -65,7 +65,9 @@ def authorizeBasic(baseDir: str,path: str,authHeader: str,debug: bool) -> bool:
nickname = plain.split(':')[0] nickname = plain.split(':')[0]
if nickname!=nicknameFromPath: if nickname!=nicknameFromPath:
if debug: if debug:
print('DEBUG: Nickname given in the path ('+nicknameFromPath+') does not match the one in the Authorization header ('+nickname+')') print('DEBUG: Nickname given in the path ('+nicknameFromPath+ \
') does not match the one in the Authorization header ('+ \
nickname+')')
return False return False
passwordFile=baseDir+'/accounts/passwords' passwordFile=baseDir+'/accounts/passwords'
if not os.path.isfile(passwordFile): if not os.path.isfile(passwordFile):

View File

@ -12,7 +12,10 @@ def storePersonInCache(personUrl: str,personJson: {},personCache: {}) -> None:
"""Store an actor in the cache """Store an actor in the cache
""" """
currTime=datetime.datetime.utcnow() currTime=datetime.datetime.utcnow()
personCache[personUrl]={ "actor": personJson, "timestamp": currTime.strftime("%Y-%m-%dT%H:%M:%SZ") } personCache[personUrl]={
"actor": personJson,
"timestamp": currTime.strftime("%Y-%m-%dT%H:%M:%SZ")
}
def storeWebfingerInCache(handle: str,wf,cachedWebfingers: {}) -> None: def storeWebfingerInCache(handle: str,wf,cachedWebfingers: {}) -> None:
"""Store a webfinger endpoint in the cache """Store a webfinger endpoint in the cache
@ -25,7 +28,9 @@ def getPersonFromCache(personUrl: str,personCache: {}) -> {}:
if personCache.get(personUrl): if personCache.get(personUrl):
# how old is the cached data? # how old is the cached data?
currTime=datetime.datetime.utcnow() currTime=datetime.datetime.utcnow()
cacheTime=datetime.datetime.strptime(personCache[personUrl]['timestamp'],"%Y-%m-%dT%H:%M:%SZ") cacheTime= \
datetime.datetime.strptime(personCache[personUrl]['timestamp'], \
"%Y-%m-%dT%H:%M:%SZ")
daysSinceCached=(currTime - cacheTime).days daysSinceCached=(currTime - cacheTime).days
# return cached value if it has not expired # return cached value if it has not expired
if daysSinceCached <= 2: if daysSinceCached <= 2:

View File

@ -132,16 +132,22 @@ class PubServer(BaseHTTPRequestHandler):
return False return False
# https://www.w3.org/TR/activitypub/#create-activity-outbox # https://www.w3.org/TR/activitypub/#create-activity-outbox
messageJson['object']['attributedTo']=messageJson['actor'] messageJson['object']['attributedTo']=messageJson['actor']
permittedOutboxTypes=['Create','Announce','Like','Follow','Undo','Update','Add','Remove','Block','Delete'] permittedOutboxTypes=[
'Create','Announce','Like','Follow','Undo', \
'Update','Add','Remove','Block','Delete'
]
if messageJson['type'] not in permittedOutboxTypes: if messageJson['type'] not in permittedOutboxTypes:
if self.server.debug: if self.server.debug:
print('DEBUG: POST to outbox - '+messageJson['type']+' is not a permitted activity type') print('DEBUG: POST to outbox - '+messageJson['type']+ \
' is not a permitted activity type')
return False return False
if messageJson.get('id'): if messageJson.get('id'):
postId=messageJson['id'] postId=messageJson['id']
else: else:
postId=None postId=None
savePostToBox(self.server.baseDir,postId,self.postToNickname,self.server.domain,messageJson,'outbox') savePostToBox(self.server.baseDir,postId, \
self.postToNickname, \
self.server.domain,messageJson,'outbox')
return True return True
def _updateInboxQueue(self,nickname: str,messageJson: {}) -> bool: def _updateInboxQueue(self,nickname: str,messageJson: {}) -> bool:
@ -158,7 +164,6 @@ class PubServer(BaseHTTPRequestHandler):
'/'+self.path.split('/')[-1], '/'+self.path.split('/')[-1],
self.server.debug) self.server.debug)
if queueFilename: if queueFilename:
print('**************************************')
if queueFilename not in self.server.inboxQueue: if queueFilename not in self.server.inboxQueue:
self.server.inboxQueue.append(queueFilename) self.server.inboxQueue.append(queueFilename)
self.send_response(201) self.send_response(201)
@ -169,7 +174,9 @@ class PubServer(BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
if self.server.debug: if self.server.debug:
print('DEBUG: GET from '+self.server.baseDir+' path: '+self.path+' busy: '+str(self.server.GETbusy)) print('DEBUG: GET from '+self.server.baseDir+ \
' path: '+self.path+' busy: '+ \
str(self.server.GETbusy))
if self.server.GETbusy: if self.server.GETbusy:
currTimeGET=int(time.time()) currTimeGET=int(time.time())
if currTimeGET-self.server.lastGET<10: if currTimeGET-self.server.lastGET<10:
@ -211,7 +218,8 @@ class PubServer(BaseHTTPRequestHandler):
return return
else: else:
if self.server.debug: if self.server.debug:
print('DEBUG: '+nickname+' was not authorized to access '+self.path) print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
if self.server.debug: if self.server.debug:
print('DEBUG: GET access to inbox is unauthorized') print('DEBUG: GET access to inbox is unauthorized')
self.send_response(405) self.send_response(405)
@ -239,7 +247,8 @@ class PubServer(BaseHTTPRequestHandler):
return return
followers=getFollowingFeed(self.server.baseDir,self.server.domain, \ followers=getFollowingFeed(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \ self.server.port,self.path, \
self.server.httpPrefix,followsPerPage,'followers') self.server.httpPrefix, \
followsPerPage,'followers')
if followers: if followers:
self._set_headers('application/json') self._set_headers('application/json')
self.wfile.write(json.dumps(followers).encode('utf-8')) self.wfile.write(json.dumps(followers).encode('utf-8'))
@ -279,7 +288,9 @@ class PubServer(BaseHTTPRequestHandler):
def do_POST(self): def do_POST(self):
if self.server.debug: if self.server.debug:
print('DEBUG: POST to from '+self.server.baseDir+' path: '+self.path+' busy: '+str(self.server.POSTbusy)) print('DEBUG: POST to from '+self.server.baseDir+ \
' path: '+self.path+' busy: '+ \
str(self.server.POSTbusy))
if self.server.POSTbusy: if self.server.POSTbusy:
currTimePOST=int(time.time()) currTimePOST=int(time.time())
if currTimePOST-self.server.lastPOST<10: if currTimePOST-self.server.lastPOST<10:
@ -314,7 +325,9 @@ class PubServer(BaseHTTPRequestHandler):
if self.path.endswith('/outbox'): if self.path.endswith('/outbox'):
if '/users/' in self.path: if '/users/' in self.path:
if self.headers.get('Authorization'): if self.headers.get('Authorization'):
if authorize(self.server.baseDir,self.path,self.headers['Authorization'],self.server.debug): if authorize(self.server.baseDir,self.path, \
self.headers['Authorization'], \
self.server.debug):
self.outboxAuthenticated=True self.outboxAuthenticated=True
pathUsersSection=path.split('/users/')[1] pathUsersSection=path.split('/users/')[1]
self.postToNickname=pathUsersSection.split('/')[0] self.postToNickname=pathUsersSection.split('/')[0]
@ -354,7 +367,8 @@ class PubServer(BaseHTTPRequestHandler):
# https://www.w3.org/TR/activitypub/#object-without-create # https://www.w3.org/TR/activitypub/#object-without-create
if self.outboxAuthenticated: if self.outboxAuthenticated:
if self._postToOutbox(messageJson): if self._postToOutbox(messageJson):
self.send_header('Location',messageJson['object']['atomUri']) self.send_header('Location', \
messageJson['object']['atomUri'])
self.send_response(201) self.send_response(201)
self.end_headers() self.end_headers()
self.server.POSTbusy=False self.server.POSTbusy=False
@ -379,7 +393,10 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy=False self.server.POSTbusy=False
return return
if not inboxPermittedMessage(self.server.domain,messageJson,self.server.federationList,self.server.capsList): if not inboxPermittedMessage(self.server.domain, \
messageJson, \
self.server.federationList, \
self.server.capsList):
if self.server.debug: if self.server.debug:
# https://www.youtube.com/watch?v=K3PrSj9XEu4 # https://www.youtube.com/watch?v=K3PrSj9XEu4
print('DEBUG: Ah Ah Ah') print('DEBUG: Ah Ah Ah')
@ -425,7 +442,8 @@ class PubServer(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.server.POSTbusy=False self.server.POSTbusy=False
def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https',fedList=[],capsList=[],useTor=False,debug=False) -> None: def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https', \
fedList=[],capsList=[],useTor=False,debug=False) -> None:
if len(domain)==0: if len(domain)==0:
domain='localhost' domain='localhost'
if '.' not in domain: if '.' not in domain:
@ -456,6 +474,12 @@ def runDaemon(baseDir: str,domain: str,port=80,httpPrefix='https',fedList=[],cap
httpd.sendThreads=[] httpd.sendThreads=[]
httpd.postLog=[] httpd.postLog=[]
print('Running ActivityPub daemon on ' + domain + ' port ' + str(port)) print('Running ActivityPub daemon on ' + domain + ' port ' + str(port))
httpd.thrInboxQueue=threadWithTrace(target=runInboxQueue,args=(baseDir,httpPrefix,httpd.sendThreads,httpd.postLog,httpd.cachedWebfingers,httpd.personCache,httpd.inboxQueue,domain,port,useTor,httpd.federationList,httpd.capsList,debug),daemon=True) httpd.thrInboxQueue= \
threadWithTrace(target=runInboxQueue, \
args=(baseDir,httpPrefix,httpd.sendThreads, \
httpd.postLog,httpd.cachedWebfingers, \
httpd.personCache,httpd.inboxQueue, \
domain,port,useTor,httpd.federationList, \
httpd.capsList,debug),daemon=True)
httpd.thrInboxQueue.start() httpd.thrInboxQueue.start()
httpd.serve_forever() httpd.serve_forever()

View File

@ -18,7 +18,9 @@ from posts import sendSignedJson
from capabilities import isCapable from capabilities import isCapable
from acceptreject import createAccept from acceptreject import createAccept
def getFollowersOfPerson(baseDir: str,nickname: str,domain: str,followFile='following.txt') -> []: def getFollowersOfPerson(baseDir: str, \
nickname: str,domain: str, \
followFile='following.txt') -> []:
"""Returns a list containing the followers of the given person """Returns a list containing the followers of the given person
Used by the shared inbox to know who to send incoming mail to Used by the shared inbox to know who to send incoming mail to
""" """
@ -46,7 +48,8 @@ def followPerson(baseDir: str,nickname: str, domain: str, \
federationList: [], followFile='following.txt') -> bool: federationList: [], followFile='following.txt') -> bool:
"""Adds a person to the follow list """Adds a person to the follow list
""" """
if not domainPermitted(followDomain.lower().replace('\n',''), federationList): if not domainPermitted(followDomain.lower().replace('\n',''), \
federationList):
return False return False
handle=nickname.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
handleToFollow=followNickname.lower()+'@'+followDomain.lower() handleToFollow=followNickname.lower()+'@'+followDomain.lower()
@ -100,9 +103,11 @@ def unfollowerOfPerson(baseDir: str,nickname: str,domain: str, \
followerNickname: str,followerDomain: str) -> None: followerNickname: str,followerDomain: str) -> None:
"""Remove a follower of a person """Remove a follower of a person
""" """
unfollowPerson(baseDir,nickname,domain,followerNickname,followerDomain,'followers.txt') unfollowPerson(baseDir,nickname,domain, \
followerNickname,followerDomain,'followers.txt')
def clearFollows(baseDir: str,nickname: str,domain: str,followFile='following.txt') -> None: def clearFollows(baseDir: str,nickname: str,domain: str, \
followFile='following.txt') -> None:
"""Removes all follows """Removes all follows
""" """
handle=nickname.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
@ -119,7 +124,8 @@ def clearFollowers(baseDir: str,nickname: str,domain: str) -> None:
""" """
clearFollows(baseDir,nickname, domain,'followers.txt') clearFollows(baseDir,nickname, domain,'followers.txt')
def getNoOfFollows(baseDir: str,nickname: str,domain: str,followFile='following.txt') -> int: def getNoOfFollows(baseDir: str,nickname: str,domain: str, \
followFile='following.txt') -> int:
"""Returns the number of follows or followers """Returns the number of follows or followers
""" """
handle=nickname.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
@ -142,8 +148,9 @@ def getNoOfFollowers(baseDir: str,nickname: str,domain: str) -> int:
""" """
return getNoOfFollows(baseDir,nickname,domain,'followers.txt') return getNoOfFollows(baseDir,nickname,domain,'followers.txt')
def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,httpPrefix: str, \ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str, \
followsPerPage=12,followFile='following') -> {}: httpPrefix: str, followsPerPage=12, \
followFile='following') -> {}:
"""Returns the following and followers feeds from GET requests """Returns the following and followers feeds from GET requests
""" """
if '/'+followFile not in path: if '/'+followFile not in path:
@ -233,7 +240,11 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,httpPrefix: st
following['next']=httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(lastPage) following['next']=httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(lastPage)
return following return following
def receiveFollowRequest(session,baseDir: str,httpPrefix: str,port: int,sendThreads: [],postLog: [],cachedWebfingers: {},personCache: {},messageJson: {},federationList: [],capsList: [],debug : bool) -> bool: def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \
port: int,sendThreads: [],postLog: [], \
cachedWebfingers: {},personCache: {}, \
messageJson: {},federationList: [], \
capsList: [],debug : bool) -> bool:
"""Receives a follow request within the POST section of HTTPServer """Receives a follow request within the POST section of HTTPServer
""" """
if not messageJson['type'].startswith('Follow'): if not messageJson['type'].startswith('Follow'):
@ -278,33 +289,46 @@ def receiveFollowRequest(session,baseDir: str,httpPrefix: str,port: int,sendThre
if domainToFollow==domain: if domainToFollow==domain:
if not os.path.isdir(baseDir+'/accounts/'+handleToFollow): if not os.path.isdir(baseDir+'/accounts/'+handleToFollow):
if debug: if debug:
print('DEBUG: followed account not found - '+baseDir+'/accounts/'+handleToFollow) print('DEBUG: followed account not found - '+ \
baseDir+'/accounts/'+handleToFollow)
return False return False
if not followerOfPerson(baseDir,nickname,domain,nicknameToFollow,domainToFollow,federationList): if not followerOfPerson(baseDir,nickname,domain, \
nicknameToFollow,domainToFollow,federationList):
if debug: if debug:
print('DEBUG: '+nickname+'@'+domain+' is already a follower of '+nicknameToFollow+'@'+domainToFollow) print('DEBUG: '+nickname+'@'+domain+ \
' is already a follower of '+ \
nicknameToFollow+'@'+domainToFollow)
return False return False
# send accept back # send accept back
if debug: if debug:
print('DEBUG: sending Accept for follow request which arrived at '+nicknameToFollow+'@'+domainToFollow+' back to '+nickname+'@'+domain) print('DEBUG: sending Accept for follow request which arrived at '+ \
nicknameToFollow+'@'+domainToFollow+' back to '+nickname+'@'+domain)
personUrl=messageJson['actor'] personUrl=messageJson['actor']
acceptJson=createAccept(baseDir,federationList,capsList,nickname,domain,port, \ acceptJson=createAccept(baseDir,federationList,capsList, \
nickname,domain,port, \
personUrl,'',httpPrefix,messageJson['object']) personUrl,'',httpPrefix,messageJson['object'])
if debug: if debug:
pprint(acceptJson) pprint(acceptJson)
print('DEBUG: sending follow Accept from '+nicknameToFollow+'@'+domainToFollow+' port '+str(port)+' to '+nickname+'@'+domain+' port '+ str(fromPort)) print('DEBUG: sending follow Accept from '+ \
nicknameToFollow+'@'+domainToFollow+ \
' port '+str(port)+' to '+ \
nickname+'@'+domain+' port '+ str(fromPort))
clientToServer=False clientToServer=False
return sendSignedJson(acceptJson,session,baseDir,nicknameToFollow,domainToFollow,port, \ return sendSignedJson(acceptJson,session,baseDir, \
nicknameToFollow,domainToFollow,port, \
nickname,domain,fromPort, '', \ nickname,domain,fromPort, '', \
httpPrefix,True,clientToServer, \ httpPrefix,True,clientToServer, \
federationList, capsList, \ federationList, capsList, \
sendThreads,postLog,cachedWebfingers,personCache,debug) sendThreads,postLog,cachedWebfingers, \
personCache,debug)
def sendFollowRequest(session,baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \ def sendFollowRequest(session,baseDir: str, \
followNickname: str,followDomain: str,followPort: bool,followHttpPrefix: str, \ nickname: str,domain: str,port: int,httpPrefix: str, \
followNickname: str,followDomain: str, \
followPort: bool,followHttpPrefix: str, \
clientToServer: bool,federationList: [],capsList: [], \ clientToServer: bool,federationList: [],capsList: [], \
sendThreads: [],postLog: [],cachedWebfingers: {},personCache: {}, sendThreads: [],postLog: [],cachedWebfingers: {}, \
debug : bool) -> {}: personCache: {},debug : bool) -> {}:
"""Gets the json object for sending a follow request """Gets the json object for sending a follow request
""" """
if not domainPermitted(followDomain,federationList): if not domainPermitted(followDomain,federationList):

View File

@ -36,7 +36,8 @@ try:
except ImportError: except ImportError:
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
def noOfFollowersOnDomain(baseDir: str,handle: str, domain: str, followFile='followers.txt') -> int: def noOfFollowersOnDomain(baseDir: str,handle: str, \
domain: str, followFile='followers.txt') -> int:
"""Returns the number of followers of the given handle from the given domain """Returns the number of followers of the given handle from the given domain
""" """
filename=baseDir+'/accounts/'+handle+'/'+followFile filename=baseDir+'/accounts/'+handle+'/'+followFile
@ -47,12 +48,14 @@ def noOfFollowersOnDomain(baseDir: str,handle: str, domain: str, followFile='fol
with open(filename, "r") as followersFilename: with open(filename, "r") as followersFilename:
for followerHandle in followersFilename: for followerHandle in followersFilename:
if '@' in followerHandle: if '@' in followerHandle:
followerDomain=followerHandle.split('@')[1].replace('\n','') followerDomain= \
followerHandle.split('@')[1].replace('\n','')
if domain==followerDomain: if domain==followerDomain:
ctr+=1 ctr+=1
return ctr return ctr
def getPersonKey(nickname: str,domain: str,baseDir: str,keyType='public',debug=False): def getPersonKey(nickname: str,domain: str,baseDir: str,keyType='public', \
debug=False):
"""Returns the public or private key of a person """Returns the public or private key of a person
""" """
handle=nickname+'@'+domain handle=nickname+'@'+domain
@ -101,7 +104,8 @@ def parseUserFeed(session,feedUrl: str,asHeader: {}) -> None:
for item in parseUserFeed(session,nextUrl,asHeader): for item in parseUserFeed(session,nextUrl,asHeader):
yield item yield item
def getPersonBox(session,wfRequest: {},personCache: {},boxName='inbox') -> (str,str,str,str,str): def getPersonBox(session,wfRequest: {},personCache: {}, \
boxName='inbox') -> (str,str,str,str,str):
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
personUrl = getUserUrl(wfRequest) personUrl = getUserUrl(wfRequest)
if not personUrl: if not personUrl:
@ -140,18 +144,8 @@ def getPersonBox(session,wfRequest: {},personCache: {},boxName='inbox') -> (str,
if personJson['endpoints'].get('sharedInbox'): if personJson['endpoints'].get('sharedInbox'):
sharedInbox=personJson['endpoints']['sharedInbox'] sharedInbox=personJson['endpoints']['sharedInbox']
capabilityAcquisition=None capabilityAcquisition=None
if personJson.get('capabilityAcquisition'):
capabilityAcquisition=personJson['capabilityAcquisition']
else:
if personJson.get('capabilityAcquisitionEndpoint'): if personJson.get('capabilityAcquisitionEndpoint'):
capabilityAcquisition=personJson['capabilityAcquisitionEndpoint'] capabilityAcquisition=personJson['capabilityAcquisitionEndpoint']
else:
if personJson.get('endpoints'):
if personJson['endpoints'].get('capabilityAcquisition'):
capabilityAcquisition=personJson['endpoints']['capabilityAcquisition']
else:
if personJson['endpoints'].get('capabilityAcquisitionEndpoint'):
capabilityAcquisition=personJson['endpoints']['capabilityAcquisitionEndpoint']
storePersonInCache(personUrl,personJson,personCache) storePersonInCache(personUrl,personJson,personCache)
@ -198,7 +192,9 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
if tagItem.get('name') and tagItem.get('icon'): if tagItem.get('name') and tagItem.get('icon'):
if tagItem['icon'].get('url'): if tagItem['icon'].get('url'):
# No emoji from non-permitted domains # No emoji from non-permitted domains
if urlPermitted(tagItem['icon']['url'],federationList,capsList,"objects:read"): if urlPermitted(tagItem['icon']['url'], \
federationList,capsList, \
"objects:read"):
emojiName=tagItem['name'] emojiName=tagItem['name']
emojiIcon=tagItem['icon']['url'] emojiIcon=tagItem['icon']['url']
emoji[emojiName]=emojiIcon emoji[emojiName]=emojiIcon
@ -220,7 +216,9 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
if item['object'].get('inReplyTo'): if item['object'].get('inReplyTo'):
if item['object']['inReplyTo']: if item['object']['inReplyTo']:
# No replies to non-permitted domains # No replies to non-permitted domains
if not urlPermitted(item['object']['inReplyTo'],federationList,capsList,"objects:read"): if not urlPermitted(item['object']['inReplyTo'], \
federationList,capsList, \
"objects:read"):
continue continue
inReplyTo = item['object']['inReplyTo'] inReplyTo = item['object']['inReplyTo']
@ -228,7 +226,8 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
if item['object'].get('conversation'): if item['object'].get('conversation'):
if item['object']['conversation']: if item['object']['conversation']:
# no conversations originated in non-permitted domains # no conversations originated in non-permitted domains
if urlPermitted(item['object']['conversation'],federationList,"objects:read"): if urlPermitted(item['object']['conversation'], \
federationList,"objects:read"):
conversation = item['object']['conversation'] conversation = item['object']['conversation']
attachment = [] attachment = []
@ -237,7 +236,9 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
for attach in item['object']['attachment']: for attach in item['object']['attachment']:
if attach.get('name') and attach.get('url'): if attach.get('name') and attach.get('url'):
# no attachments from non-permitted domains # no attachments from non-permitted domains
if urlPermitted(attach['url'],federationList,capsList,"objects:read"): if urlPermitted(attach['url'], \
federationList,capsList, \
"objects:read"):
attachment.append([attach['name'],attach['url']]) attachment.append([attach['name'],attach['url']])
sensitive = False sensitive = False
@ -264,7 +265,8 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
break break
return personPosts return personPosts
def createBoxArchive(nickname: str,domain: str,baseDir: str,boxname: str) -> str: def createBoxArchive(nickname: str,domain: str,baseDir: str, \
boxname: str) -> str:
"""Creates an archive directory for inbox/outbox posts """Creates an archive directory for inbox/outbox posts
""" """
handle=nickname.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
@ -290,7 +292,9 @@ def deleteAllPosts(baseDir: str,nickname: str, domain: str,boxname: str) -> None
except Exception as e: except Exception as e:
print(e) print(e)
def savePostToBox(baseDir: str,httpPrefix: str,postId: str,nickname: str, domain: str,postJson: {},boxname: str) -> None: def savePostToBox(baseDir: str,httpPrefix: str,postId: str, \
nickname: str, domain: str,postJson: {}, \
boxname: str) -> None:
"""Saves the give json to the give box """Saves the give json to the give box
""" """
if boxname!='inbox' and boxname!='outbox': if boxname!='inbox' and boxname!='outbox':
@ -300,7 +304,8 @@ def savePostToBox(baseDir: str,httpPrefix: str,postId: str,nickname: str, domain
if not postId: if not postId:
statusNumber,published = getStatusNumber() statusNumber,published = getStatusNumber()
postId=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber postId=httpPrefix+'://'+domain+'/users/'+nickname+ \
'/statuses/'+statusNumber
postJson['id']=postId+'/activity' postJson['id']=postId+'/activity'
if postJson.get('object'): if postJson.get('object'):
postJson['object']['id']=postId postJson['object']['id']=postId
@ -410,10 +415,12 @@ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
newPost['cc']=ccUrl newPost['cc']=ccUrl
newPost['object']['cc']=ccUrl newPost['object']['cc']=ccUrl
if saveToFile: if saveToFile:
savePostToBox(baseDir,httpPrefix,newPostId,nickname,domain,newPost,'outbox') savePostToBox(baseDir,httpPrefix,newPostId, \
nickname,domain,newPost,'outbox')
return newPost return newPost
def outboxMessageCreateWrap(httpPrefix: str,nickname: str,domain: str,messageJson: {}) -> {}: def outboxMessageCreateWrap(httpPrefix: str,nickname: str,domain: str, \
messageJson: {}) -> {}:
"""Wraps a received message in a Create """Wraps a received message in a Create
https://www.w3.org/TR/activitypub/#object-without-create https://www.w3.org/TR/activitypub/#object-without-create
""" """
@ -438,8 +445,10 @@ def outboxMessageCreateWrap(httpPrefix: str,nickname: str,domain: str,messageJso
'object': messageJson 'object': messageJson
} }
newPost['object']['id']=newPost['id'] newPost['object']['id']=newPost['id']
newPost['object']['url']=httpPrefix+'://'+domain+'/@'+nickname+'/'+statusNumber newPost['object']['url']= \
newPost['object']['atomUri']=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber httpPrefix+'://'+domain+'/@'+nickname+'/'+statusNumber
newPost['object']['atomUri']= \
httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
return newPost return newPost
def createPublicPost(baseDir: str, def createPublicPost(baseDir: str,
@ -452,8 +461,8 @@ def createPublicPost(baseDir: str,
return createPostBase(baseDir,nickname, domain, port, \ return createPostBase(baseDir,nickname, domain, port, \
'https://www.w3.org/ns/activitystreams#Public', \ 'https://www.w3.org/ns/activitystreams#Public', \
httpPrefix+'://'+domain+'/users/'+nickname+'/followers', \ httpPrefix+'://'+domain+'/users/'+nickname+'/followers', \
httpPrefix, content, followersOnly, saveToFile, clientToServer, \ httpPrefix, content, followersOnly, saveToFile, \
capsList, clientToServer, capsList, \
inReplyTo, inReplyToAtomUri, subject) inReplyTo, inReplyToAtomUri, subject)
def threadSendPost(session,postJsonObject: {},federationList: [],capsList: [],\ def threadSendPost(session,postJsonObject: {},federationList: [],capsList: [],\
@ -483,7 +492,8 @@ def threadSendPost(session,postJsonObject: {},federationList: [],capsList: [],\
# our work here is done # our work here is done
break break
if debug: if debug:
print('DEBUG: json post to '+inboxUrl+' failed. Waiting for '+str(backoffTime)+' seconds.') print('DEBUG: json post to '+inboxUrl+' failed. Waiting for '+ \
str(backoffTime)+' seconds.')
time.sleep(backoffTime) time.sleep(backoffTime)
backoffTime *= 2 backoffTime *= 2
@ -573,12 +583,13 @@ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
thr.start() thr.start()
return 0 return 0
def sendSignedJson(postJsonObject: {},session,baseDir: str,nickname: str, domain: str, port: int, \ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
nickname: str, domain: str, port: int, \
toNickname: str, toDomain: str, toPort: int, cc: str, \ toNickname: str, toDomain: str, toPort: int, cc: str, \
httpPrefix: str, saveToFile: bool, clientToServer: bool, \ httpPrefix: str, saveToFile: bool, clientToServer: bool, \
federationList: [], capsList: [], \ federationList: [], capsList: [], \
sendThreads: [], postLog: [], cachedWebfingers: {},personCache: {}, \ sendThreads: [], postLog: [], cachedWebfingers: {}, \
debug: bool) -> int: personCache: {}, debug: bool) -> int:
"""Sends a signed json object to an inbox/outbox """Sends a signed json object to an inbox/outbox
""" """
withDigest=True withDigest=True
@ -646,7 +657,8 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str,nickname: str, domain
while len(sendThreads)>10: while len(sendThreads)>10:
sendThreads[0].kill() sendThreads[0].kill()
sendThreads.pop(0) sendThreads.pop(0)
thr = threadWithTrace(target=threadSendPost,args=(session, \ thr = threadWithTrace(target=threadSendPost, \
args=(session, \
postJsonObject.copy(), \ postJsonObject.copy(), \
federationList, \ federationList, \
capsList, \ capsList, \
@ -667,7 +679,8 @@ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,httpPrefix: st
return createBoxBase(baseDir,'outbox',nickname,domain,port,httpPrefix, \ return createBoxBase(baseDir,'outbox',nickname,domain,port,httpPrefix, \
itemsPerPage,headerOnly,pageNumber) itemsPerPage,headerOnly,pageNumber)
def createBoxBase(baseDir: str,boxname: str,nickname: str,domain: str,port: int,httpPrefix: str, \ def createBoxBase(baseDir: str,boxname: str, \
nickname: str,domain: str,port: int,httpPrefix: str, \
itemsPerPage: int,headerOnly: bool,pageNumber=None) -> {}: itemsPerPage: int,headerOnly: bool,pageNumber=None) -> {}:
"""Constructs the box feed """Constructs the box feed
""" """
@ -727,7 +740,8 @@ def createBoxBase(baseDir: str,boxname: str,nickname: str,domain: str,port: int,
# update the prev entry for the last message id # update the prev entry for the last message id
postId = prevPostFilename.split('#statuses#')[1].replace('#activity','') postId = prevPostFilename.split('#statuses#')[1].replace('#activity','')
boxHeader['prev']= \ boxHeader['prev']= \
httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?min_id='+postId+'&page=true' httpPrefix+'://'+domain+'/users/'+nickname+'/'+ \
boxname+'?min_id='+postId+'&page=true'
# get the full path of the post file # get the full path of the post file
filePath = os.path.join(boxDir, postFilename) filePath = os.path.join(boxDir, postFilename)
try: try:
@ -788,7 +802,8 @@ def archivePosts(nickname: str,domain: str,baseDir: str, \
if noOfPosts <= maxPostsInBox: if noOfPosts <= maxPostsInBox:
break break
def getPublicPostsOfPerson(nickname: str,domain: str,raw: bool,simple: bool) -> None: def getPublicPostsOfPerson(nickname: str,domain: str, \
raw: bool,simple: bool) -> None:
""" This is really just for test purposes """ This is really just for test purposes
""" """
useTor=True useTor=True
@ -800,7 +815,8 @@ def getPublicPostsOfPerson(nickname: str,domain: str,raw: bool,simple: bool) ->
httpPrefix='https' httpPrefix='https'
handle=httpPrefix+"://"+domain+"/@"+nickname handle=httpPrefix+"://"+domain+"/@"+nickname
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers) wfRequest = \
webfingerHandle(session,handle,httpPrefix,cachedWebfingers)
if not wfRequest: if not wfRequest:
sys.exit() sys.exit()
@ -811,5 +827,7 @@ def getPublicPostsOfPerson(nickname: str,domain: str,raw: bool,simple: bool) ->
maxMentions=10 maxMentions=10
maxEmoji=10 maxEmoji=10
maxAttachments=5 maxAttachments=5
userPosts = getPosts(session,personUrl,30,maxMentions,maxEmoji,maxAttachments,federationList,personCache,raw,simple) userPosts = getPosts(session,personUrl,30,maxMentions,maxEmoji, \
maxAttachments,federationList,personCache, \
raw,simple)
#print(str(userPosts)) #print(str(userPosts))