Change username to nickname

master
Bob Mottram 2019-07-03 10:40:27 +01:00
parent 983b299792
commit c2cc03c76a
12 changed files with 220 additions and 231 deletions

View File

@ -12,7 +12,7 @@ from utils import getStatusNumber
from utils import createOutboxDir from utils import createOutboxDir
from utils import urlPermitted from utils import urlPermitted
def createAcceptReject(baseDir: str,federationList: [],username: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str,acceptType: str) -> {}: def createAcceptReject(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str,acceptType: str) -> {}:
"""Accepts or rejects something (eg. a follow request) """Accepts or rejects something (eg. a follow request)
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 followers url
@ -30,7 +30,7 @@ def createAcceptReject(baseDir: str,federationList: [],username: str,domain: str
newAccept = { newAccept = {
'type': acceptType, 'type': acceptType,
'actor': prefix+'://'+domain+'/users/'+username, 'actor': prefix+'://'+domain+'/users/'+nickname,
'to': [toUrl], 'to': [toUrl],
'cc': [], 'cc': [],
'object': objectUrl 'object': objectUrl
@ -40,8 +40,8 @@ def createAcceptReject(baseDir: str,federationList: [],username: str,domain: str
newAccept['cc']=ccUrl newAccept['cc']=ccUrl
return newAccept return newAccept
def createAccept(baseDir: str,federationList: [],username: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str) -> {}: def createAccept(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,username,domain,port,toUrl,ccUrl,https,objectUrl,'Accept') return createAcceptReject(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,https,objectUrl,'Accept')
def createReject(baseDir: str,federationList: [],username: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str) -> {}: def createReject(baseDir: str,federationList: [],nickname: str,domain: str,port: int,toUrl: str,ccUrl: str,https: bool,objectUrl: str) -> {}:
return createAcceptReject(baseDir,federationList,username,domain,port,toUrl,ccUrl,https,objectUrl,'Reject') return createAcceptReject(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,https,objectUrl,'Reject')

View File

@ -13,7 +13,7 @@ from utils import createOutboxDir
from utils import urlPermitted from utils import urlPermitted
def createAnnounce(baseDir: str,federationList: [], \ def createAnnounce(baseDir: str,federationList: [], \
username: str, domain: str, port: int, \ nickname: str, domain: str, port: int, \
toUrl: str, ccUrl: str, https: bool, \ toUrl: str, ccUrl: str, https: bool, \
objectUrl: str, saveToFile: bool) -> {}: objectUrl: str, saveToFile: bool) -> {}:
"""Creates an announce message """Creates an announce message
@ -32,10 +32,10 @@ def createAnnounce(baseDir: str,federationList: [], \
domain=domain+':'+str(port) domain=domain+':'+str(port)
statusNumber,published = getStatusNumber() statusNumber,published = getStatusNumber()
newAnnounceId=prefix+'://'+domain+'/users/'+username+'/statuses/'+statusNumber newAnnounceId=prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
newAnnounce = { newAnnounce = {
'actor': prefix+'://'+domain+'/users/'+username, 'actor': prefix+'://'+domain+'/users/'+nickname,
'atomUri': prefix+'://'+domain+'/users/'+username+'/statuses/'+statusNumber, 'atomUri': prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'cc': [], 'cc': [],
'id': newAnnounceId+'/activity', 'id': newAnnounceId+'/activity',
'object': objectUrl, 'object': objectUrl,
@ -49,14 +49,14 @@ def createAnnounce(baseDir: str,federationList: [], \
if saveToFile: if saveToFile:
if ':' in domain: if ':' in domain:
domain=domain.split(':')[0] domain=domain.split(':')[0]
outboxDir = createOutboxDir(username,domain,baseDir) outboxDir = createOutboxDir(nickname,domain,baseDir)
filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json' filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json'
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
commentjson.dump(newAnnounce, fp, indent=4, sort_keys=False) commentjson.dump(newAnnounce, fp, indent=4, sort_keys=False)
return newAnnounce return newAnnounce
def announcePublic(baseDir: str,federationList: [], \ def announcePublic(baseDir: str,federationList: [], \
username: str, domain: str, port: int, https: bool, \ nickname: str, domain: str, port: int, https: bool, \
objectUrl: str, saveToFile: bool) -> {}: objectUrl: str, saveToFile: bool) -> {}:
"""Makes a public announcement """Makes a public announcement
""" """
@ -69,13 +69,13 @@ def announcePublic(baseDir: str,federationList: [], \
fromDomain=fromDomain+':'+str(port) fromDomain=fromDomain+':'+str(port)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl = 'https://www.w3.org/ns/activitystreams#Public'
ccUrl = prefix + '://'+fromDomain+'/users/'+username+'/followers' ccUrl = prefix + '://'+fromDomain+'/users/'+nickname+'/followers'
return createAnnounce(baseDir,username, domain, port, \ return createAnnounce(baseDir,nickname, domain, port, \
toUrl, ccUrl, https, objectUrl, saveToFile) toUrl, ccUrl, https, objectUrl, saveToFile)
def repeatPost(baseDir: str,federationList: [], \ def repeatPost(baseDir: str,federationList: [], \
username: str, domain: str, port: int, https: bool, \ nickname: str, domain: str, port: int, https: bool, \
announceUsername: str, announceDomain: str, \ announceNickname: str, announceDomain: str, \
announcePort: int, announceHttps: bool, \ announcePort: int, announceHttps: bool, \
announceStatusNumber: int, saveToFile: bool) -> {}: announceStatusNumber: int, saveToFile: bool) -> {}:
"""Repeats a given status post """Repeats a given status post
@ -89,7 +89,7 @@ def repeatPost(baseDir: str,federationList: [], \
announcedDomain=announcedDomain+':'+str(announcePort) announcedDomain=announcedDomain+':'+str(announcePort)
objectUrl = prefix + '://'+announcedDomain+'/users/'+ \ objectUrl = prefix + '://'+announcedDomain+'/users/'+ \
announceUsername+'/statuses/'+str(announceStatusNumber) announceNickname+'/statuses/'+str(announceStatusNumber)
return announcePublic(baseDir,username, domain, port, https, objectUrl, saveToFile) return announcePublic(baseDir,nickname, domain, port, https, objectUrl, saveToFile)

View File

@ -43,9 +43,9 @@ def readFollowList(filename: str):
followUsers = open(filename, "r") followUsers = open(filename, "r")
for u in followUsers: for u in followUsers:
if u not in followlist: if u not in followlist:
username,domain = parseHandle(u) nickname,domain = parseHandle(u)
if username: if nickname:
followlist.append(username+'@'+domain) followlist.append(nickname+'@'+domain)
followUsers.close() followUsers.close()
return followlist return followlist

View File

@ -7,7 +7,7 @@ __email__ = "bob@freedombone.net"
__status__ = "Production" __status__ = "Production"
from person import createPerson from person import createPerson
from person import setPreferredUsername from person import setPreferredNickname
from person import setBio from person import setBio
from webfinger import webfingerHandle from webfinger import webfingerHandle
from posts import getPosts from posts import getPosts
@ -73,7 +73,7 @@ if args.tests:
print(args.domain) print(args.domain)
print(str(args.federationList)) print(str(args.federationList))
username='admin' nickname='admin'
domain=args.domain domain=args.domain
port=args.port port=args.port
https=args.https https=args.https
@ -93,24 +93,13 @@ session = createSession(domain,port,useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
#unfollowPerson(username,domain,'squirrel','secret.com') privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,https,True)
#sys.exit() #deleteAllPosts(nickname,domain)
setPreferredNickname(baseDir,nickname,domain,'badger')
#asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} setBio(baseDir,nickname,domain,'Some personal info')
#userFollowing = getJson(session,"https://mastodon.social/users/Gargron/followers?page=true",asHeader,None) #createPublicPost(baseDir,nickname, domain, port,https, "G'day world!", False, True, None, None, 'Not suitable for Vogons')
#userFollowing = getJson(session,"https://mastodon.social/users/Gargron/following",asHeader,None) #archivePosts(nickname,domain,baseDir,4)
#userFollowing = getJson(session,"https://mastodon.social/users/Gargron/following?page=1",asHeader,None) #outboxJson=createOutbox(baseDir,nickname,domain,port,https,2,True,None)
#pprint(userFollowing)
#sys.exit()
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,username,domain,port,https,True)
#deleteAllPosts(username,domain)
setPreferredUsername(baseDir,username,domain,'badger')
setBio(baseDir,username,domain,'Some personal info')
#createPublicPost(baseDir,username, domain, port,https, "G'day world!", False, True, None, None, 'Not suitable for Vogons')
#archivePosts(username,domain,baseDir,4)
#outboxJson=createOutbox(baseDir,username,domain,port,https,2,True,None)
#pprint(outboxJson) #pprint(outboxJson)
#testPostMessageBetweenServers() #testPostMessageBetweenServers()

View File

@ -10,18 +10,18 @@ import json
from pprint import pprint from pprint import pprint
import os import os
import sys import sys
from person import validUsername from person import validNickname
from utils import domainPermitted from utils import domainPermitted
def followPerson(baseDir: str,username: str, domain: str, \ def followPerson(baseDir: str,nickname: str, domain: str, \
followUsername: str, followDomain: str, \ followNickname: str, followDomain: 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=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
handleToFollow=followUsername.lower()+'@'+followDomain.lower() handleToFollow=followNickname.lower()+'@'+followDomain.lower()
if not os.path.isdir(baseDir+'/accounts'): if not os.path.isdir(baseDir+'/accounts'):
os.mkdir(baseDir+'/accounts') os.mkdir(baseDir+'/accounts')
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
@ -37,22 +37,22 @@ def followPerson(baseDir: str,username: str, domain: str, \
followfile.write(handleToFollow+'\n') followfile.write(handleToFollow+'\n')
return True return True
def followerOfPerson(baseDir: str,username: str, domain: str, \ def followerOfPerson(baseDir: str,nickname: str, domain: str, \
followerUsername: str, followerDomain: str, \ followerNickname: str, followerDomain: str, \
federationList: []) -> bool: federationList: []) -> bool:
"""Adds a follower of the given person """Adds a follower of the given person
""" """
return followPerson(baseDir,username, domain, \ return followPerson(baseDir,nickname, domain, \
followerUsername, followerDomain, \ followerNickname, followerDomain, \
federationList, 'followers.txt') federationList, 'followers.txt')
def unfollowPerson(baseDir: str,username: str, domain: str, \ def unfollowPerson(baseDir: str,nickname: str, domain: str, \
followUsername: str, followDomain: str, \ followNickname: str, followDomain: str, \
followFile='following.txt') -> None: followFile='following.txt') -> None:
"""Removes a person to the follow list """Removes a person to the follow list
""" """
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
handleToUnfollow=followUsername.lower()+'@'+followDomain.lower() handleToUnfollow=followNickname.lower()+'@'+followDomain.lower()
if not os.path.isdir(baseDir+'/accounts'): if not os.path.isdir(baseDir+'/accounts'):
os.mkdir(baseDir+'/accounts') os.mkdir(baseDir+'/accounts')
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
@ -68,16 +68,16 @@ def unfollowPerson(baseDir: str,username: str, domain: str, \
if line.strip("\n") != handleToUnfollow: if line.strip("\n") != handleToUnfollow:
f.write(line) f.write(line)
def unfollowerOfPerson(baseDir: str,username: str,domain: str, \ def unfollowerOfPerson(baseDir: str,nickname: str,domain: str, \
followerUsername: str,followerDomain: str) -> None: followerNickname: str,followerDomain: str) -> None:
"""Remove a follower of a person """Remove a follower of a person
""" """
unfollowPerson(baseDir,username,domain,followerUsername,followerDomain,'followers.txt') unfollowPerson(baseDir,nickname,domain,followerNickname,followerDomain,'followers.txt')
def clearFollows(baseDir: str,username: 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=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
if not os.path.isdir(baseDir+'/accounts'): if not os.path.isdir(baseDir+'/accounts'):
os.mkdir(baseDir+'/accounts') os.mkdir(baseDir+'/accounts')
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
@ -86,15 +86,15 @@ def clearFollows(baseDir: str,username: str,domain: str,followFile='following.tx
if os.path.isfile(filename): if os.path.isfile(filename):
os.remove(filename) os.remove(filename)
def clearFollowers(baseDir: str,username: str,domain: str) -> None: def clearFollowers(baseDir: str,nickname: str,domain: str) -> None:
"""Removes all followers """Removes all followers
""" """
clearFollows(baseDir,username, domain,'followers.txt') clearFollows(baseDir,nickname, domain,'followers.txt')
def getNoOfFollows(baseDir: str,username: 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=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
filename=baseDir+'/accounts/'+handle+'/'+followFile filename=baseDir+'/accounts/'+handle+'/'+followFile
if not os.path.isfile(filename): if not os.path.isfile(filename):
return 0 return 0
@ -109,10 +109,10 @@ def getNoOfFollows(baseDir: str,username: str,domain: str,followFile='following.
ctr += 1 ctr += 1
return ctr return ctr
def getNoOfFollowers(baseDir: str,username: str,domain: str) -> int: def getNoOfFollowers(baseDir: str,nickname: str,domain: str) -> int:
"""Returns the number of followers of the given person """Returns the number of followers of the given person
""" """
return getNoOfFollows(baseDir,username,domain,'followers.txt') return getNoOfFollows(baseDir,nickname,domain,'followers.txt')
def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
followsPerPage=12,followFile='following') -> {}: followsPerPage=12,followFile='following') -> {}:
@ -137,14 +137,14 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
if not path.endswith('/'+followFile): if not path.endswith('/'+followFile):
return None return None
username=None nickname=None
if path.startswith('/users/'): if path.startswith('/users/'):
username=path.replace('/users/','',1).replace('/'+followFile,'') nickname=path.replace('/users/','',1).replace('/'+followFile,'')
if path.startswith('/@'): if path.startswith('/@'):
username=path.replace('/@','',1).replace('/'+followFile,'') nickname=path.replace('/@','',1).replace('/'+followFile,'')
if not username: if not nickname:
return None return None
if not validUsername(username): if not validNickname(nickname):
return None return None
prefix='https' prefix='https'
@ -157,9 +157,9 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
if headerOnly: if headerOnly:
following = { following = {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'first': prefix+'://'+domain+'/users/'+username+'/'+followFile+'?page=1', 'first': prefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page=1',
'id': prefix+'://'+domain+'/users/'+username+'/'+followFile, 'id': prefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'totalItems': getNoOfFollows(username,domain), 'totalItems': getNoOfFollows(nickname,domain),
'type': 'OrderedCollection'} 'type': 'OrderedCollection'}
return following return following
@ -169,13 +169,13 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
nextPageNumber=int(pageNumber+1) nextPageNumber=int(pageNumber+1)
following = { following = {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': prefix+'://'+domain+'/users/'+username+'/'+followFile+'?page='+str(pageNumber), 'id': prefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(pageNumber),
'orderedItems': [], 'orderedItems': [],
'partOf': prefix+'://'+domain+'/users/'+username+'/'+followFile, 'partOf': prefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'totalItems': 0, 'totalItems': 0,
'type': 'OrderedCollectionPage'} 'type': 'OrderedCollectionPage'}
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
filename=baseDir+'/accounts/'+handle+'/'+followFile+'.txt' filename=baseDir+'/accounts/'+handle+'/'+followFile+'.txt'
if not os.path.isfile(filename): if not os.path.isfile(filename):
return following return following
@ -206,7 +206,7 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str,https: bool, \
if lastPage<1: if lastPage<1:
lastPage=1 lastPage=1
if nextPageNumber>lastPage: if nextPageNumber>lastPage:
following['next']=prefix+'://'+domain+'/users/'+username+'/'+followFile+'?page='+str(lastPage) following['next']=prefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(lastPage)
return following return following
def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> bool: def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> bool:
@ -219,8 +219,8 @@ def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> boo
domain=messageJson['actor'].split('/users/')[0].replace('https://','').replace('http://','') domain=messageJson['actor'].split('/users/')[0].replace('https://','').replace('http://','')
if not domainPermitted(domain,federationList): if not domainPermitted(domain,federationList):
return False return False
username=messageJson['actor'].split('/users/')[1].replace('@','') nickname=messageJson['actor'].split('/users/')[1].replace('@','')
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
return False return False
if '/users/' not in messageJson['object']: if '/users/' not in messageJson['object']:
@ -228,15 +228,15 @@ def receiveFollowRequest(baseDir: str,messageJson: {},federationList: []) -> boo
domainToFollow=messageJson['object'].split('/users/')[0].replace('https://','').replace('http://','') domainToFollow=messageJson['object'].split('/users/')[0].replace('https://','').replace('http://','')
if not domainPermitted(domainToFollow,federationList): if not domainPermitted(domainToFollow,federationList):
return False return False
usernameToFollow=messageJson['object'].split('/users/')[1].replace('@','') nicknameToFollow=messageJson['object'].split('/users/')[1].replace('@','')
handleToFollow=usernameToFollow.lower()+'@'+domainToFollow.lower() handleToFollow=nicknameToFollow.lower()+'@'+domainToFollow.lower()
if domainToFollow==domain: if domainToFollow==domain:
if not os.path.isdir(baseDir+'/accounts/'+handleToFollow): if not os.path.isdir(baseDir+'/accounts/'+handleToFollow):
return False return False
return followerOfPerson(baseDir,username,domain,usernameToFollow,domainToFollow,federationList) return followerOfPerson(baseDir,nickname,domain,nicknameToFollow,domainToFollow,federationList)
def sendFollowRequest(baseDir: str,username: str,domain: str,port: int,https: bool, \ def sendFollowRequest(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
followUsername: str,followDomain: str,followPort: bool,followHttps: bool, \ followNickname: str,followDomain: str,followPort: bool,followHttps: bool, \
federationList: []) -> {}: federationList: []) -> {}:
"""Gets the json object for sending a follow request """Gets the json object for sending a follow request
""" """
@ -259,8 +259,8 @@ def sendFollowRequest(baseDir: str,username: str,domain: str,port: int,https: bo
newFollow = { newFollow = {
'type': 'Follow', 'type': 'Follow',
'actor': prefix+'://'+domain+'/users/'+username, 'actor': prefix+'://'+domain+'/users/'+nickname,
'object': followPrefix+'://'+followDomain+'/users/'+followUsername, 'object': followPrefix+'://'+followDomain+'/users/'+followNickname,
'to': [toUrl], 'to': [toUrl],
'cc': [] 'cc': []
} }

View File

@ -15,7 +15,7 @@ from requests.auth import AuthBase
import base64 import base64
import json import json
def signPostHeaders(privateKeyPem: str, username: str, domain: str, \ def signPostHeaders(privateKeyPem: str, nickname: str, domain: str, \
port: int,path: str, \ port: int,path: str, \
https: bool, messageBodyJson: {}) -> str: https: bool, messageBodyJson: {}) -> str:
"""Returns a raw signature string that can be plugged into a header and """Returns a raw signature string that can be plugged into a header and
@ -28,7 +28,7 @@ def signPostHeaders(privateKeyPem: str, username: str, domain: str, \
if port!=80 and port!=443: if port!=80 and port!=443:
domain=domain+':'+str(port) domain=domain+':'+str(port)
keyID = prefix+'://'+domain+'/users/'+username+'/main-key' keyID = prefix+'://'+domain+'/users/'+nickname+'/main-key'
if not messageBodyJson: if not messageBodyJson:
headers = {'host': domain} headers = {'host': domain}
else: else:
@ -62,7 +62,7 @@ def signPostHeaders(privateKeyPem: str, username: str, domain: str, \
[f'{k}="{v}"' for k, v in signatureDict.items()]) [f'{k}="{v}"' for k, v in signatureDict.items()])
return signatureHeader return signatureHeader
def createSignedHeader(privateKeyPem: str,username: str,domain: str,port: int, \ def createSignedHeader(privateKeyPem: str,nickname: str,domain: str,port: int, \
path: str,https: bool,withDigest: bool, \ path: str,https: bool,withDigest: bool, \
messageBodyJson: {}) -> {}: messageBodyJson: {}) -> {}:
headerDomain=domain headerDomain=domain
@ -78,7 +78,7 @@ def createSignedHeader(privateKeyPem: str,username: str,domain: str,port: int, \
base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest()) base64.b64encode(SHA256.new(messageBodyJsonStr.encode()).digest())
headers = {'host': headerDomain, 'digest': f'SHA-256={bodyDigest}'} headers = {'host': headerDomain, 'digest': f'SHA-256={bodyDigest}'}
path='/inbox' path='/inbox'
signatureHeader = signPostHeaders(privateKeyPem, username, domain, port, \ signatureHeader = signPostHeaders(privateKeyPem, nickname, domain, port, \
path, https, None) path, https, None)
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
headers['Content-type'] = 'application/json' headers['Content-type'] = 'application/json'

12
like.py
View File

@ -10,7 +10,7 @@ import json
import commentjson import commentjson
from utils import urlPermitted from utils import urlPermitted
def like(baseDir: str,federationList: [],username: str,domain: str,port: int, \ def like(baseDir: str,federationList: [],nickname: str,domain: str,port: int, \
toUrl: str,ccUrl: str,https: bool,objectUrl: str,saveToFile: bool) -> {}: toUrl: str,ccUrl: str,https: bool,objectUrl: str,saveToFile: bool) -> {}:
"""Creates a like """Creates a like
Typically toUrl will be a followers collection Typically toUrl will be a followers collection
@ -29,7 +29,7 @@ def like(baseDir: str,federationList: [],username: str,domain: str,port: int, \
newLike = { newLike = {
'type': 'Like', 'type': 'Like',
'actor': prefix+'://'+domain+'/users/'+username, 'actor': prefix+'://'+domain+'/users/'+nickname,
'object': objectUrl, 'object': objectUrl,
'to': [toUrl], 'to': [toUrl],
'cc': [] 'cc': []
@ -44,8 +44,8 @@ def like(baseDir: str,federationList: [],username: str,domain: str,port: int, \
return newLike return newLike
def likePost(baseDir: str,federationList: [], \ def likePost(baseDir: str,federationList: [], \
username: str, domain: str, port: int, https: bool, \n nickname: str, domain: str, port: int, https: bool, \n
likeUsername: str, likeDomain: str, likePort: int, likeHttps: bool, \n likeNickname: str, likeDomain: str, likePort: int, likeHttps: bool, \n
likeStatusNumber: int,saveToFile: bool) -> {}: likeStatusNumber: int,saveToFile: bool) -> {}:
"""Likes a given status post """Likes a given status post
""" """
@ -57,6 +57,6 @@ def likePost(baseDir: str,federationList: [], \
if likePort!=80 and likePort!=443: if likePort!=80 and likePort!=443:
likeDomain=likeDomain+':'+str(likePort) likeDomain=likeDomain+':'+str(likePort)
objectUrl = prefix + '://'+likeDomain+'/users/'+likeUsername+'/statuses/'+str(likeStatusNumber) objectUrl = prefix + '://'+likeDomain+'/users/'+likeNickname+'/statuses/'+str(likeStatusNumber)
return like(baseDir,federationList,username,domain,port,toUrl,ccUrl,https,objectUrl,saveToFile) return like(baseDir,federationList,nickname,domain,port,toUrl,ccUrl,https,objectUrl,saveToFile)

View File

@ -21,7 +21,7 @@ def generateRSAKey() -> (str,str):
publicKeyPem = key.publickey().exportKey("PEM").decode("utf-8") publicKeyPem = key.publickey().exportKey("PEM").decode("utf-8")
return privateKeyPem,publicKeyPem return privateKeyPem,publicKeyPem
def createPerson(baseDir: str,username: str,domain: str,port: int, \ def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
https: bool, saveToFile: bool) -> (str,str,{},{}): https: bool, saveToFile: bool) -> (str,str,{},{}):
"""Returns the private key, public key, actor and webfinger endpoint """Returns the private key, public key, actor and webfinger endpoint
""" """
@ -31,11 +31,11 @@ def createPerson(baseDir: str,username: str,domain: str,port: int, \
privateKeyPem,publicKeyPem=generateRSAKey() privateKeyPem,publicKeyPem=generateRSAKey()
webfingerEndpoint= \ webfingerEndpoint= \
createWebfingerEndpoint(username,domain,port,https,publicKeyPem) createWebfingerEndpoint(nickname,domain,port,https,publicKeyPem)
if saveToFile: if saveToFile:
storeWebfingerEndpoint(username,domain,baseDir,webfingerEndpoint) storeWebfingerEndpoint(nickname,domain,baseDir,webfingerEndpoint)
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
if port!=80 and port!=443: if port!=80 and port!=443:
domain=domain+':'+str(port) domain=domain+':'+str(port)
@ -55,28 +55,28 @@ def createPerson(baseDir: str,username: str,domain: str,port: int, \
'value': 'schema:value'}], 'value': 'schema:value'}],
'attachment': [], 'attachment': [],
'endpoints': {'sharedInbox': prefix+'://'+domain+'/inbox'}, 'endpoints': {'sharedInbox': prefix+'://'+domain+'/inbox'},
'featured': prefix+'://'+domain+'/users/'+username+'/collections/featured', 'featured': prefix+'://'+domain+'/users/'+nickname+'/collections/featured',
'followers': prefix+'://'+domain+'/users/'+username+'/followers', 'followers': prefix+'://'+domain+'/users/'+nickname+'/followers',
'following': prefix+'://'+domain+'/users/'+username+'/following', 'following': prefix+'://'+domain+'/users/'+nickname+'/following',
'icon': {'mediaType': 'image/png', 'icon': {'mediaType': 'image/png',
'type': 'Image', 'type': 'Image',
'url': prefix+'://'+domain+'/users/'+username+'_icon.png'}, 'url': prefix+'://'+domain+'/users/'+nickname+'_icon.png'},
'id': prefix+'://'+domain+'/users/'+username, 'id': prefix+'://'+domain+'/users/'+nickname,
'image': {'mediaType': 'image/png', 'image': {'mediaType': 'image/png',
'type': 'Image', 'type': 'Image',
'url': prefix+'://'+domain+'/users/'+username+'.png'}, 'url': prefix+'://'+domain+'/users/'+nickname+'.png'},
'inbox': prefix+'://'+domain+'/users/'+username+'/inbox', 'inbox': prefix+'://'+domain+'/users/'+nickname+'/inbox',
'manuallyApprovesFollowers': False, 'manuallyApprovesFollowers': False,
'name': username, 'name': nickname,
'outbox': prefix+'://'+domain+'/users/'+username+'/outbox', 'outbox': prefix+'://'+domain+'/users/'+nickname+'/outbox',
'preferredUsername': ''+username, 'preferredNickname': ''+nickname,
'publicKey': {'id': prefix+'://'+domain+'/users/'+username+'/main-key', 'publicKey': {'id': prefix+'://'+domain+'/users/'+nickname+'/main-key',
'owner': prefix+'://'+domain+'/users/'+username, 'owner': prefix+'://'+domain+'/users/'+nickname,
'publicKeyPem': publicKeyPem, 'publicKeyPem': publicKeyPem,
'summary': '', 'summary': '',
'tag': [], 'tag': [],
'type': 'Person', 'type': 'Person',
'url': prefix+'://'+domain+'/@'+username} 'url': prefix+'://'+domain+'/@'+nickname}
} }
if saveToFile: if saveToFile:
@ -108,26 +108,26 @@ def createPerson(baseDir: str,username: str,domain: str,port: int, \
return privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint return privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint
def validUsername(username: str) -> bool: def validNickname(nickname: str) -> bool:
forbiddenChars=['.',' ','/','?',':',';','@'] forbiddenChars=['.',' ','/','?',':',';','@']
for c in forbiddenChars: for c in forbiddenChars:
if c in username: if c in nickname:
return False return False
return True return True
def personKeyLookup(domain: str,path: str,baseDir: str) -> str: def personKeyLookup(domain: str,path: str,baseDir: str) -> str:
"""Lookup the public key of the person with a given username """Lookup the public key of the person with a given nickname
""" """
if not path.endswith('/main-key'): if not path.endswith('/main-key'):
return None return None
if not path.startswith('/users/'): if not path.startswith('/users/'):
return None return None
username=path.replace('/users/','',1).replace('/main-key','') nickname=path.replace('/users/','',1).replace('/main-key','')
if not validUsername(username): if not validNickname(nickname):
return None return None
if ':' in domain: if ':' in domain:
domain=domain.split(':')[0] domain=domain.split(':')[0]
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
filename=baseDir+'/accounts/'+handle.lower()+'.json' filename=baseDir+'/accounts/'+handle.lower()+'.json'
if not os.path.isfile(filename): if not os.path.isfile(filename):
return None return None
@ -140,7 +140,7 @@ def personKeyLookup(domain: str,path: str,baseDir: str) -> str:
return None return None
def personLookup(domain: str,path: str,baseDir: str) -> {}: def personLookup(domain: str,path: str,baseDir: str) -> {}:
"""Lookup the person for an given username """Lookup the person for an given nickname
""" """
notPersonLookup=['/inbox','/outbox','/outboxarchive', \ notPersonLookup=['/inbox','/outbox','/outboxarchive', \
'/followers','/following','/featured', \ '/followers','/following','/featured', \
@ -149,18 +149,18 @@ def personLookup(domain: str,path: str,baseDir: str) -> {}:
for ending in notPersonLookup: for ending in notPersonLookup:
if path.endswith(ending): if path.endswith(ending):
return None return None
username=None nickname=None
if path.startswith('/users/'): if path.startswith('/users/'):
username=path.replace('/users/','',1) nickname=path.replace('/users/','',1)
if path.startswith('/@'): if path.startswith('/@'):
username=path.replace('/@','',1) nickname=path.replace('/@','',1)
if not username: if not nickname:
return None return None
if not validUsername(username): if not validNickname(nickname):
return None return None
if ':' in domain: if ':' in domain:
domain=domain.split(':')[0] domain=domain.split(':')[0]
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
filename=baseDir+'/accounts/'+handle.lower()+'.json' filename=baseDir+'/accounts/'+handle.lower()+'.json'
if not os.path.isfile(filename): if not os.path.isfile(filename):
return None return None
@ -195,23 +195,23 @@ def personOutboxJson(baseDir: str,domain: str,port: int,path: str, \
if not path.endswith('/outbox'): if not path.endswith('/outbox'):
return None return None
username=None nickname=None
if path.startswith('/users/'): if path.startswith('/users/'):
username=path.replace('/users/','',1).replace('/outbox','') nickname=path.replace('/users/','',1).replace('/outbox','')
if path.startswith('/@'): if path.startswith('/@'):
username=path.replace('/@','',1).replace('/outbox','') nickname=path.replace('/@','',1).replace('/outbox','')
if not username: if not nickname:
return None return None
if not validUsername(username): if not validNickname(nickname):
return None return None
return createOutbox(baseDir,username,domain,port,https, \ return createOutbox(baseDir,nickname,domain,port,https, \
noOfItems,headerOnly,pageNumber) noOfItems,headerOnly,pageNumber)
def setPreferredUsername(baseDir: str,username: str, domain: str, \ def setPreferredNickname(baseDir: str,nickname: str, domain: str, \
preferredName: str) -> bool: preferredName: str) -> bool:
if len(preferredName)>32: if len(preferredName)>32:
return False return False
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
filename=baseDir+'/accounts/'+handle.lower()+'.json' filename=baseDir+'/accounts/'+handle.lower()+'.json'
if not os.path.isfile(filename): if not os.path.isfile(filename):
return False return False
@ -220,15 +220,15 @@ def setPreferredUsername(baseDir: str,username: str, domain: str, \
personJson=commentjson.load(fp) personJson=commentjson.load(fp)
if not personJson: if not personJson:
return False return False
personJson['preferredUsername']=preferredName personJson['preferredNickname']=preferredName
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
commentjson.dump(personJson, fp, indent=4, sort_keys=False) commentjson.dump(personJson, fp, indent=4, sort_keys=False)
return True return True
def setBio(baseDir: str,username: str, domain: str, bio: str) -> bool: def setBio(baseDir: str,nickname: str, domain: str, bio: str) -> bool:
if len(bio)>32: if len(bio)>32:
return False return False
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
filename=baseDir+'/accounts/'+handle.lower()+'.json' filename=baseDir+'/accounts/'+handle.lower()+'.json'
if not os.path.isfile(filename): if not os.path.isfile(filename):
return False return False

View File

@ -34,10 +34,10 @@ try:
except ImportError: except ImportError:
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
def getPersonKey(username: str,domain: str,baseDir: str,keyType='public'): def getPersonKey(nickname: str,domain: str,baseDir: str,keyType='public'):
"""Returns the public or private key of a person """Returns the public or private key of a person
""" """
handle=username+'@'+domain handle=nickname+'@'+domain
keyFilename=baseDir+'/keys/'+keyType+'/'+handle.lower()+'.key' keyFilename=baseDir+'/keys/'+keyType+'/'+handle.lower()+'.key'
if not os.path.isfile(keyFilename): if not os.path.isfile(keyFilename):
return '' return ''
@ -212,10 +212,10 @@ def getPosts(session,outboxUrl: str,maxPosts: int,maxMentions: int, \
break break
return personPosts return personPosts
def createOutboxArchive(username: str,domain: str,baseDir: str) -> str: def createOutboxArchive(nickname: str,domain: str,baseDir: str) -> str:
"""Creates an archive directory for outbox posts """Creates an archive directory for outbox posts
""" """
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
os.mkdir(baseDir+'/accounts/'+handle) os.mkdir(baseDir+'/accounts/'+handle)
outboxArchiveDir=baseDir+'/accounts/'+handle+'/outboxarchive' outboxArchiveDir=baseDir+'/accounts/'+handle+'/outboxarchive'
@ -223,10 +223,10 @@ def createOutboxArchive(username: str,domain: str,baseDir: str) -> str:
os.mkdir(outboxArchiveDir) os.mkdir(outboxArchiveDir)
return outboxArchiveDir return outboxArchiveDir
def deleteAllPosts(username: str, domain: str,baseDir: str) -> None: def deleteAllPosts(nickname: str, domain: str,baseDir: str) -> None:
"""Deletes all posts for a person """Deletes all posts for a person
""" """
outboxDir = createOutboxDir(username,domain,baseDir) outboxDir = createOutboxDir(nickname,domain,baseDir)
for deleteFilename in os.listdir(outboxDir): for deleteFilename in os.listdir(outboxDir):
filePath = os.path.join(outboxDir, deleteFilename) filePath = os.path.join(outboxDir, deleteFilename)
try: try:
@ -236,7 +236,7 @@ def deleteAllPosts(username: str, domain: str,baseDir: str) -> None:
except Exception as e: except Exception as e:
print(e) print(e)
def createPostBase(baseDir: str,username: str, domain: str, port: int, \ def createPostBase(baseDir: str,nickname: str, domain: str, port: int, \
toUrl: str, ccUrl: str, https: bool, content: str, \ toUrl: str, ccUrl: str, https: bool, content: str, \
followersOnly: bool, saveToFile: bool, \ followersOnly: bool, saveToFile: bool, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}: inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
@ -253,11 +253,11 @@ def createPostBase(baseDir: str,username: str, domain: str, port: int, \
conversationDate=published.split('T')[0] conversationDate=published.split('T')[0]
conversationId=statusNumber conversationId=statusNumber
postTo='https://www.w3.org/ns/activitystreams#Public' postTo='https://www.w3.org/ns/activitystreams#Public'
postCC=prefix+'://'+domain+'/users/'+username+'/followers' postCC=prefix+'://'+domain+'/users/'+nickname+'/followers'
if followersOnly: if followersOnly:
postTo=postCC postTo=postCC
postCC='' postCC=''
newPostId=prefix+'://'+domain+'/users/'+username+'/statuses/'+statusNumber newPostId=prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
sensitive=False sensitive=False
summary=None summary=None
if subject: if subject:
@ -266,7 +266,7 @@ def createPostBase(baseDir: str,username: str, domain: str, port: int, \
newPost = { newPost = {
'id': newPostId+'/activity', 'id': newPostId+'/activity',
'type': 'Create', 'type': 'Create',
'actor': prefix+'://'+domain+'/users/'+username, 'actor': prefix+'://'+domain+'/users/'+nickname,
'published': published, 'published': published,
'to': [toUrl], 'to': [toUrl],
'cc': [], 'cc': [],
@ -275,12 +275,12 @@ def createPostBase(baseDir: str,username: str, domain: str, port: int, \
'summary': summary, 'summary': summary,
'inReplyTo': inReplyTo, 'inReplyTo': inReplyTo,
'published': published, 'published': published,
'url': prefix+'://'+domain+'/@'+username+'/'+statusNumber, 'url': prefix+'://'+domain+'/@'+nickname+'/'+statusNumber,
'attributedTo': prefix+'://'+domain+'/users/'+username, 'attributedTo': prefix+'://'+domain+'/users/'+nickname,
'to': [toUrl], 'to': [toUrl],
'cc': [], 'cc': [],
'sensitive': sensitive, 'sensitive': sensitive,
'atomUri': prefix+'://'+domain+'/users/'+username+'/statuses/'+statusNumber, 'atomUri': prefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber,
'inReplyToAtomUri': inReplyToAtomUri, 'inReplyToAtomUri': inReplyToAtomUri,
'conversation': 'tag:'+domain+','+conversationDate+':objectId='+conversationId+':objectType=Conversation', 'conversation': 'tag:'+domain+','+conversationDate+':objectId='+conversationId+':objectType=Conversation',
'content': content, 'content': content,
@ -290,11 +290,11 @@ def createPostBase(baseDir: str,username: str, domain: str, port: int, \
'attachment': [], 'attachment': [],
'tag': [], 'tag': [],
'replies': {} 'replies': {}
# 'id': 'https://'+domain+'/users/'+username+'/statuses/'+statusNumber+'/replies', # 'id': 'https://'+domain+'/users/'+nickname+'/statuses/'+statusNumber+'/replies',
# 'type': 'Collection', # 'type': 'Collection',
# 'first': { # 'first': {
# 'type': 'CollectionPage', # 'type': 'CollectionPage',
# 'partOf': 'https://'+domain+'/users/'+username+'/statuses/'+statusNumber+'/replies', # 'partOf': 'https://'+domain+'/users/'+nickname+'/statuses/'+statusNumber+'/replies',
# 'items': [] # 'items': []
# } # }
#} #}
@ -307,13 +307,13 @@ def createPostBase(baseDir: str,username: str, domain: str, port: int, \
if saveToFile: if saveToFile:
if ':' in domain: if ':' in domain:
domain=domain.split(':')[0] domain=domain.split(':')[0]
outboxDir = createOutboxDir(username,domain,baseDir) outboxDir = createOutboxDir(nickname,domain,baseDir)
filename=outboxDir+'/'+newPostId.replace('/','#')+'.json' filename=outboxDir+'/'+newPostId.replace('/','#')+'.json'
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
commentjson.dump(newPost, fp, indent=4, sort_keys=False) commentjson.dump(newPost, fp, indent=4, sort_keys=False)
return newPost return newPost
def createPublicPost(baseDir: str,username: str, domain: str, port: int,https: bool, \ def createPublicPost(baseDir: str,nickname: str, domain: str, port: int,https: bool, \
content: str, followersOnly: bool, saveToFile: bool, \ content: str, followersOnly: bool, saveToFile: bool, \
inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}: inReplyTo=None, inReplyToAtomUri=None, subject=None) -> {}:
"""Public post to the outbox """Public post to the outbox
@ -321,9 +321,9 @@ def createPublicPost(baseDir: str,username: str, domain: str, port: int,https: b
prefix='https' prefix='https'
if not https: if not https:
prefix='http' prefix='http'
return createPostBase(baseDir,username, domain, port, \ return createPostBase(baseDir,nickname, domain, port, \
'https://www.w3.org/ns/activitystreams#Public', \ 'https://www.w3.org/ns/activitystreams#Public', \
prefix+'://'+domain+'/users/'+username+'/followers', \ prefix+'://'+domain+'/users/'+nickname+'/followers', \
https, content, followersOnly, saveToFile, \ https, content, followersOnly, saveToFile, \
inReplyTo, inReplyToAtomUri, subject) inReplyTo, inReplyToAtomUri, subject)
@ -352,8 +352,8 @@ def threadSendPost(session,postJsonObject: {},federationList: [],inboxUrl: str,
time.sleep(backoffTime) time.sleep(backoffTime)
backoffTime *= 2 backoffTime *= 2
def sendPost(session,baseDir: str,username: str, domain: str, port: int, \ def sendPost(session,baseDir: str,nickname: str, domain: str, port: int, \
toUsername: str, toDomain: str, toPort: int, cc: str, \ toNickname: str, toDomain: str, toPort: int, cc: str, \
https: bool, content: str, followersOnly: bool, \ https: bool, content: str, followersOnly: bool, \
saveToFile: bool, federationList: [], sendThreads: [], \ saveToFile: bool, federationList: [], sendThreads: [], \
postLog: [], cachedWebfingers: {},personCache: {}, \ postLog: [], cachedWebfingers: {},personCache: {}, \
@ -369,7 +369,7 @@ def sendPost(session,baseDir: str,username: str, domain: str, port: int, \
if toPort!=80 and toPort!=443: if toPort!=80 and toPort!=443:
toDomain=toDomain+':'+str(toPort) toDomain=toDomain+':'+str(toPort)
handle=prefix+'://'+toDomain+'/@'+toUsername handle=prefix+'://'+toDomain+'/@'+toNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,https,cachedWebfingers) wfRequest = webfingerHandle(session,handle,https,cachedWebfingers)
@ -386,20 +386,20 @@ def sendPost(session,baseDir: str,username: str, domain: str, port: int, \
if not toPersonId: if not toPersonId:
return 4 return 4
postJsonObject=createPostBase(baseDir,username,domain,port, \ postJsonObject=createPostBase(baseDir,nickname,domain,port, \
toPersonId,cc,https,content, \ toPersonId,cc,https,content, \
followersOnly,saveToFile, \ followersOnly,saveToFile, \
inReplyTo,inReplyToAtomUri, \ inReplyTo,inReplyToAtomUri, \
subject) subject)
# get the senders private key # get the senders private key
privateKeyPem=getPersonKey(username,domain,baseDir,'private') privateKeyPem=getPersonKey(nickname,domain,baseDir,'private')
if len(privateKeyPem)==0: if len(privateKeyPem)==0:
return 5 return 5
# construct the http header # construct the http header
signatureHeaderJson = \ signatureHeaderJson = \
createSignedHeader(privateKeyPem, username, domain, port, \ createSignedHeader(privateKeyPem, nickname, domain, port, \
'/inbox', https, withDigest, postJsonObject) '/inbox', https, withDigest, postJsonObject)
# Keep the number of threads being used small # Keep the number of threads being used small
@ -416,7 +416,7 @@ def sendPost(session,baseDir: str,username: str, domain: str, port: int, \
thr.start() thr.start()
return 0 return 0
def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool, \ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,https: bool, \
itemsPerPage: int,headerOnly: bool,pageNumber=None) -> {}: itemsPerPage: int,headerOnly: bool,pageNumber=None) -> {}:
"""Constructs the outbox feed """Constructs the outbox feed
""" """
@ -424,7 +424,7 @@ def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool, \
if not https: if not https:
prefix='http' prefix='http'
outboxDir = createOutboxDir(username,domain,baseDir) outboxDir = createOutboxDir(nickname,domain,baseDir)
if port!=80 and port!=443: if port!=80 and port!=443:
domain = domain+':'+str(port) domain = domain+':'+str(port)
@ -436,16 +436,16 @@ def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool, \
except: except:
pass pass
outboxHeader = {'@context': 'https://www.w3.org/ns/activitystreams', outboxHeader = {'@context': 'https://www.w3.org/ns/activitystreams',
'first': prefix+'://'+domain+'/users/'+username+'/outbox?page=true', 'first': prefix+'://'+domain+'/users/'+nickname+'/outbox?page=true',
'id': prefix+'://'+domain+'/users/'+username+'/outbox', 'id': prefix+'://'+domain+'/users/'+nickname+'/outbox',
'last': prefix+'://'+domain+'/users/'+username+'/outbox?page=true', 'last': prefix+'://'+domain+'/users/'+nickname+'/outbox?page=true',
'totalItems': 0, 'totalItems': 0,
'type': 'OrderedCollection'} 'type': 'OrderedCollection'}
outboxItems = {'@context': 'https://www.w3.org/ns/activitystreams', outboxItems = {'@context': 'https://www.w3.org/ns/activitystreams',
'id': prefix+'://'+domain+'/users/'+username+'/outbox'+pageStr, 'id': prefix+'://'+domain+'/users/'+nickname+'/outbox'+pageStr,
'orderedItems': [ 'orderedItems': [
], ],
'partOf': prefix+'://'+domain+'/users/'+username+'/outbox', 'partOf': prefix+'://'+domain+'/users/'+nickname+'/outbox',
'type': 'OrderedCollectionPage'} 'type': 'OrderedCollectionPage'}
# counter for posts loop # counter for posts loop
@ -467,7 +467,7 @@ def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool, \
if lastPage<1: if lastPage<1:
lastPage=1 lastPage=1
outboxHeader['last']= \ outboxHeader['last']= \
prefix+'://'+domain+'/users/'+username+'/outbox?page='+str(lastPage) prefix+'://'+domain+'/users/'+nickname+'/outbox?page='+str(lastPage)
# Insert posts # Insert posts
currPage=1 currPage=1
@ -478,7 +478,7 @@ def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool, \
# 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','')
outboxHeader['prev']= \ outboxHeader['prev']= \
prefix+'://'+domain+'/users/'+username+'/outbox?min_id='+postId+'&page=true' prefix+'://'+domain+'/users/'+nickname+'/outbox?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(outboxDir, postFilename) filePath = os.path.join(outboxDir, postFilename)
try: try:
@ -497,7 +497,7 @@ def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool, \
postId = p['id'].split('/statuses/')[1].replace('/activity','') postId = p['id'].split('/statuses/')[1].replace('/activity','')
outboxHeader['next']= \ outboxHeader['next']= \
prefix+'://'+domain+'/users/'+ \ prefix+'://'+domain+'/users/'+ \
username+'/outbox?max_id='+ \ nickname+'/outbox?max_id='+ \
postId+'&page=true' postId+'&page=true'
postsOnPageCtr += 1 postsOnPageCtr += 1
# remember the last post filename for use with prev # remember the last post filename for use with prev
@ -515,13 +515,13 @@ def createOutbox(baseDir: str,username: str,domain: str,port: int,https: bool, \
return outboxHeader return outboxHeader
return outboxItems return outboxItems
def archivePosts(username: str,domain: str,baseDir: str, \ def archivePosts(nickname: str,domain: str,baseDir: str, \
maxPostsInOutbox=256) -> None: maxPostsInOutbox=256) -> None:
"""Retain a maximum number of posts within the outbox """Retain a maximum number of posts within the outbox
Move any others to an archive directory Move any others to an archive directory
""" """
outboxDir = createOutboxDir(username,domain,baseDir) outboxDir = createOutboxDir(nickname,domain,baseDir)
archiveDir = createOutboxArchive(username,domain,baseDir) archiveDir = createOutboxArchive(nickname,domain,baseDir)
postsInOutbox=sorted(os.listdir(outboxDir), reverse=False) postsInOutbox=sorted(os.listdir(outboxDir), reverse=False)
noOfPosts=len(postsInOutbox) noOfPosts=len(postsInOutbox)
if noOfPosts<=maxPostsInOutbox: if noOfPosts<=maxPostsInOutbox:

View File

@ -35,12 +35,12 @@ testServerBobRunning = False
def testHttpsigBase(withDigest): def testHttpsigBase(withDigest):
print('testHttpsig(' + str(withDigest) + ')') print('testHttpsig(' + str(withDigest) + ')')
username='socrates' nickname='socrates'
domain='argumentative.social' domain='argumentative.social'
https=True https=True
port=5576 port=5576
baseDir=os.getcwd() baseDir=os.getcwd()
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,username,domain,port,https,False) privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(baseDir,nickname,domain,port,https,False)
messageBodyJsonStr = '{"a key": "a value", "another key": "A string"}' messageBodyJsonStr = '{"a key": "a value", "another key": "A string"}'
headersDomain=domain headersDomain=domain
@ -54,7 +54,7 @@ def testHttpsigBase(withDigest):
headers = {'host': headersDomain, 'digest': f'SHA-256={bodyDigest}'} headers = {'host': headersDomain, 'digest': f'SHA-256={bodyDigest}'}
path='/inbox' path='/inbox'
signatureHeader = signPostHeaders(privateKeyPem, username, domain, port, path, https, None) signatureHeader = signPostHeaders(privateKeyPem, nickname, domain, port, path, https, None)
headers['signature'] = signatureHeader headers['signature'] = signatureHeader
assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox' ,False, messageBodyJsonStr) assert verifyPostHeaders(https, publicKeyPem, headers, '/inbox' ,False, messageBodyJsonStr)
assert verifyPostHeaders(https, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJsonStr) == False assert verifyPostHeaders(https, publicKeyPem, headers, '/parambulator/inbox', False , messageBodyJsonStr) == False
@ -104,16 +104,16 @@ def createServerAlice(path: str,domain: str,port: int,federationList: []):
shutil.rmtree(path) shutil.rmtree(path)
os.mkdir(path) os.mkdir(path)
os.chdir(path) os.chdir(path)
username='alice' nickname='alice'
https=False https=False
useTor=False useTor=False
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,username,domain,port,https,True) privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,https,True)
deleteAllPosts(username,domain,path) deleteAllPosts(nickname,domain,path)
followPerson(path,username,domain,'bob','127.0.0.100:61936',federationList) followPerson(path,nickname,domain,'bob','127.0.0.100:61936',federationList)
followerOfPerson(path,username,domain,'bob','127.0.0.100:61936',federationList) followerOfPerson(path,nickname,domain,'bob','127.0.0.100:61936',federationList)
createPublicPost(path,username, domain, port,https, "No wise fish would go anywhere without a porpoise", False, True) createPublicPost(path,nickname, domain, port,https, "No wise fish would go anywhere without a porpoise", False, True)
createPublicPost(path,username, domain, port,https, "Curiouser and curiouser!", False, True) createPublicPost(path,nickname, domain, port,https, "Curiouser and curiouser!", False, True)
createPublicPost(path,username, domain, port,https, "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", False, True) createPublicPost(path,nickname, domain, port,https, "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", False, True)
global testServerAliceRunning global testServerAliceRunning
testServerAliceRunning = True testServerAliceRunning = True
print('Server running: Alice') print('Server running: Alice')
@ -125,16 +125,16 @@ def createServerBob(path: str,domain: str,port: int,federationList: []):
shutil.rmtree(path) shutil.rmtree(path)
os.mkdir(path) os.mkdir(path)
os.chdir(path) os.chdir(path)
username='bob' nickname='bob'
https=False https=False
useTor=False useTor=False
privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,username,domain,port,https,True) privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(path,nickname,domain,port,https,True)
deleteAllPosts(username,domain,path) deleteAllPosts(nickname,domain,path)
followPerson(path,username,domain,'alice','127.0.0.50:61935',federationList) followPerson(path,nickname,domain,'alice','127.0.0.50:61935',federationList)
followerOfPerson(path,username,domain,'alice','127.0.0.50:61935',federationList) followerOfPerson(path,nickname,domain,'alice','127.0.0.50:61935',federationList)
createPublicPost(path,username, domain, port,https, "It's your life, live it your way.", False, True) createPublicPost(path,nickname, domain, port,https, "It's your life, live it your way.", False, True)
createPublicPost(path,username, domain, port,https, "One of the things I've realised is that I am very simple", False, True) createPublicPost(path,nickname, domain, port,https, "One of the things I've realised is that I am very simple", False, True)
createPublicPost(path,username, domain, port,https, "Quantum physics is a bit of a passion of mine", False, True) createPublicPost(path,nickname, domain, port,https, "Quantum physics is a bit of a passion of mine", False, True)
global testServerBobRunning global testServerBobRunning
testServerBobRunning = True testServerBobRunning = True
print('Server running: Bob') print('Server running: Bob')
@ -208,7 +208,7 @@ def testPostMessageBetweenServers():
def testFollows(): def testFollows():
currDir=os.getcwd() currDir=os.getcwd()
username='test529' nickname='test529'
domain='testdomain.com' domain='testdomain.com'
port=80 port=80
https=True https=True
@ -218,16 +218,16 @@ def testFollows():
shutil.rmtree(baseDir) shutil.rmtree(baseDir)
os.mkdir(baseDir) os.mkdir(baseDir)
os.chdir(baseDir) os.chdir(baseDir)
createPerson(baseDir,username,domain,port,https,True) createPerson(baseDir,nickname,domain,port,https,True)
clearFollows(baseDir,username,domain) clearFollows(baseDir,nickname,domain)
followPerson(baseDir,username,domain,'badger','wild.com',federationList) followPerson(baseDir,nickname,domain,'badger','wild.com',federationList)
followPerson(baseDir,username,domain,'squirrel','secret.com',federationList) followPerson(baseDir,nickname,domain,'squirrel','secret.com',federationList)
followPerson(baseDir,username,domain,'rodent','drainpipe.com',federationList) followPerson(baseDir,nickname,domain,'rodent','drainpipe.com',federationList)
followPerson(baseDir,username,domain,'batman','mesh.com',federationList) followPerson(baseDir,nickname,domain,'batman','mesh.com',federationList)
followPerson(baseDir,username,domain,'giraffe','trees.com',federationList) followPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList)
f = open(baseDir+'/accounts/'+username+'@'+domain+'/following.txt', "r") f = open(baseDir+'/accounts/'+nickname+'@'+domain+'/following.txt', "r")
domainFound=False domainFound=False
for followingDomain in f: for followingDomain in f:
testDomain=followingDomain.split('@')[1].replace('\n','') testDomain=followingDomain.split('@')[1].replace('\n','')
@ -238,7 +238,7 @@ def testFollows():
assert(False) assert(False)
assert(domainFound) assert(domainFound)
unfollowPerson(baseDir,username,domain,'batman','mesh.com') unfollowPerson(baseDir,nickname,domain,'batman','mesh.com')
domainFound=False domainFound=False
for followingDomain in f: for followingDomain in f:
@ -247,14 +247,14 @@ def testFollows():
domainFound=True domainFound=True
assert(domainFound==False) assert(domainFound==False)
clearFollowers(baseDir,username,domain) clearFollowers(baseDir,nickname,domain)
followerOfPerson(baseDir,username,domain,'badger','wild.com',federationList) followerOfPerson(baseDir,nickname,domain,'badger','wild.com',federationList)
followerOfPerson(baseDir,username,domain,'squirrel','secret.com',federationList) followerOfPerson(baseDir,nickname,domain,'squirrel','secret.com',federationList)
followerOfPerson(baseDir,username,domain,'rodent','drainpipe.com',federationList) followerOfPerson(baseDir,nickname,domain,'rodent','drainpipe.com',federationList)
followerOfPerson(baseDir,username,domain,'batman','mesh.com',federationList) followerOfPerson(baseDir,nickname,domain,'batman','mesh.com',federationList)
followerOfPerson(baseDir,username,domain,'giraffe','trees.com',federationList) followerOfPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList)
f = open(baseDir+'/accounts/'+username+'@'+domain+'/followers.txt', "r") f = open(baseDir+'/accounts/'+nickname+'@'+domain+'/followers.txt', "r")
for followerDomain in f: for followerDomain in f:
testDomain=followerDomain.split('@')[1].replace('\n','') testDomain=followerDomain.split('@')[1].replace('\n','')
if testDomain not in federationList: if testDomain not in federationList:

View File

@ -20,10 +20,10 @@ def getStatusNumber() -> (str,str):
conversationDate=currTime.strftime("%Y-%m-%d") conversationDate=currTime.strftime("%Y-%m-%d")
return statusNumber,published return statusNumber,published
def createOutboxDir(username: str,domain: str,baseDir: str) -> str: def createOutboxDir(nickname: str,domain: str,baseDir: str) -> str:
"""Create an outbox for a person and returns the feed filename and directory """Create an outbox for a person and returns the feed filename and directory
""" """
handle=username.lower()+'@'+domain.lower() handle=nickname.lower()+'@'+domain.lower()
if not os.path.isdir(baseDir+'/accounts/'+handle): if not os.path.isdir(baseDir+'/accounts/'+handle):
os.mkdir(baseDir+'/accounts/'+handle) os.mkdir(baseDir+'/accounts/'+handle)
outboxDir=baseDir+'/accounts/'+handle+'/outbox' outboxDir=baseDir+'/accounts/'+handle+'/outbox'

View File

@ -21,43 +21,43 @@ def parseHandle(handle: str) -> (str,str):
if '.' not in handle: if '.' not in handle:
return None, None return None, None
if '/@' in handle: if '/@' in handle:
domain, username = \ domain, nickname = \
handle.replace('https://','').replace('http://','').split('/@') handle.replace('https://','').replace('http://','').split('/@')
else: else:
if '/users/' in handle: if '/users/' in handle:
domain, username = \ domain, nickname = \
handle.replace('https://','').replace('http://','').split('/users/') handle.replace('https://','').replace('http://','').split('/users/')
else: else:
if '@' in handle: if '@' in handle:
username, domain = handle.split('@') nickname, domain = handle.split('@')
else: else:
return None, None return None, None
return username, domain return nickname, domain
def webfingerHandle(session,handle: str,https: bool,cachedWebfingers: {}) -> {}: def webfingerHandle(session,handle: str,https: bool,cachedWebfingers: {}) -> {}:
username, domain = parseHandle(handle) nickname, domain = parseHandle(handle)
if not username: if not nickname:
return None return None
wfDomain=domain wfDomain=domain
if ':' in wfDomain: if ':' in wfDomain:
wfDomain=wfDomain.split(':')[0] wfDomain=wfDomain.split(':')[0]
print('***********cachedWebfingers '+str(cachedWebfingers)) print('***********cachedWebfingers '+str(cachedWebfingers))
wf=getWebfingerFromCache(username+'@'+wfDomain,cachedWebfingers) wf=getWebfingerFromCache(nickname+'@'+wfDomain,cachedWebfingers)
if wf: if wf:
return wf return wf
prefix='https' prefix='https'
if not https: if not https:
prefix='http' prefix='http'
url = '{}://{}/.well-known/webfinger'.format(prefix,domain) url = '{}://{}/.well-known/webfinger'.format(prefix,domain)
par = {'resource': 'acct:{}'.format(username+'@'+wfDomain)} par = {'resource': 'acct:{}'.format(nickname+'@'+wfDomain)}
hdr = {'Accept': 'application/jrd+json'} hdr = {'Accept': 'application/jrd+json'}
#try: #try:
result = getJson(session, url, hdr, par) result = getJson(session, url, hdr, par)
#except: #except:
# print("Unable to webfinger " + url + ' ' + str(hdr) + ' ' + str(par)) # print("Unable to webfinger " + url + ' ' + str(hdr) + ' ' + str(par))
storeWebfingerInCache(username+'@'+wfDomain,result,cachedWebfingers) storeWebfingerInCache(nickname+'@'+wfDomain,result,cachedWebfingers)
return result return result
def generateMagicKey(publicKeyPem) -> str: def generateMagicKey(publicKeyPem) -> str:
@ -69,11 +69,11 @@ def generateMagicKey(publicKeyPem) -> str:
pubexp = base64.urlsafe_b64encode(number.long_to_bytes(privkey.e)).decode("utf-8") pubexp = base64.urlsafe_b64encode(number.long_to_bytes(privkey.e)).decode("utf-8")
return f"data:application/magic-public-key,RSA.{mod}.{pubexp}" return f"data:application/magic-public-key,RSA.{mod}.{pubexp}"
def storeWebfingerEndpoint(username: str,domain: str,baseDir: str, \ def storeWebfingerEndpoint(nickname: str,domain: str,baseDir: str, \
wfJson: {}) -> bool: wfJson: {}) -> bool:
"""Stores webfinger endpoint for a user to a file """Stores webfinger endpoint for a user to a file
""" """
handle=username+'@'+domain handle=nickname+'@'+domain
wfSubdir='/wfendpoints' wfSubdir='/wfendpoints'
if not os.path.isdir(baseDir+wfSubdir): if not os.path.isdir(baseDir+wfSubdir):
os.mkdir(baseDir+wfSubdir) os.mkdir(baseDir+wfSubdir)
@ -82,7 +82,7 @@ def storeWebfingerEndpoint(username: str,domain: str,baseDir: str, \
commentjson.dump(wfJson, fp, indent=4, sort_keys=False) commentjson.dump(wfJson, fp, indent=4, sort_keys=False)
return True return True
def createWebfingerEndpoint(username: str,domain: str,port: int, \ def createWebfingerEndpoint(nickname: str,domain: str,port: int, \
https: bool,publicKeyPem) -> {}: https: bool,publicKeyPem) -> {}:
"""Creates a webfinger endpoint for a user """Creates a webfinger endpoint for a user
""" """
@ -95,22 +95,22 @@ def createWebfingerEndpoint(username: str,domain: str,port: int, \
account = { account = {
"aliases": [ "aliases": [
prefix+"://"+domain+"/@"+username, prefix+"://"+domain+"/@"+nickname,
prefix+"://"+domain+"/users/"+username prefix+"://"+domain+"/users/"+nickname
], ],
"links": [ "links": [
{ {
"href": prefix+"://"+domain+"/@"+username, "href": prefix+"://"+domain+"/@"+nickname,
"rel": "http://webfinger.net/rel/profile-page", "rel": "http://webfinger.net/rel/profile-page",
"type": "text/html" "type": "text/html"
}, },
{ {
"href": prefix+"://"+domain+"/users/"+username+".atom", "href": prefix+"://"+domain+"/users/"+nickname+".atom",
"rel": "http://schemas.google.com/g/2010#updates-from", "rel": "http://schemas.google.com/g/2010#updates-from",
"type": "application/atom+xml" "type": "application/atom+xml"
}, },
{ {
"href": prefix+"://"+domain+"/users/"+username, "href": prefix+"://"+domain+"/users/"+nickname,
"rel": "self", "rel": "self",
"type": "application/activity+json" "type": "application/activity+json"
}, },
@ -127,7 +127,7 @@ def createWebfingerEndpoint(username: str,domain: str,port: int, \
"template": prefix+"://"+domain+"/authorize_interaction?uri={uri}" "template": prefix+"://"+domain+"/authorize_interaction?uri={uri}"
} }
], ],
"subject": "acct:"+username+"@"+domain "subject": "acct:"+nickname+"@"+domain
} }
return account return account