merge-requests/30/head
Bob Mottram 2020-03-22 20:36:19 +00:00
parent e5e6cad1b4
commit d0884fa04d
46 changed files with 1290 additions and 1126 deletions

View File

@ -1,10 +1,10 @@
__filename__ = "acceptreject.py" __filename__="acceptreject.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import json import json
@ -39,7 +39,7 @@ def createAcceptReject(baseDir: str,federationList: [], \
if ':' not in domain: if ':' not in domain:
domain=domain+':'+str(port) domain=domain+':'+str(port)
newAccept = { newAccept={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': acceptType, 'type': acceptType,
'actor': httpPrefix+'://'+domain+'/users/'+nickname, 'actor': httpPrefix+'://'+domain+'/users/'+nickname,

View File

@ -1,10 +1,10 @@
__filename__ = "announce.py" __filename__="announce.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import time import time
@ -162,7 +162,7 @@ def updateAnnounceCollection(recentPostsCache: {}, \
if debug: if debug:
print('DEBUG: Adding initial shares (announcements) to '+ \ print('DEBUG: Adding initial shares (announcements) to '+ \
postUrl) postUrl)
announcementsJson = { announcementsJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'id': postUrl, 'id': postUrl,
'type': 'Collection', 'type': 'Collection',
@ -236,10 +236,10 @@ def createAnnounce(session,baseDir: str,federationList: [], \
if ':' not in domain: if ':' not in domain:
fullDomain=domain+':'+str(port) fullDomain=domain+':'+str(port)
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
newAnnounceId= \ newAnnounceId= \
httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber
newAnnounce = { newAnnounce={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber, 'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber,
@ -254,7 +254,7 @@ def createAnnounce(session,baseDir: str,federationList: [], \
if len(ccUrl)>0: if len(ccUrl)>0:
newAnnounce['cc']=[ccUrl] newAnnounce['cc']=[ccUrl]
if saveToFile: if saveToFile:
outboxDir = createOutboxDir(nickname,domain,baseDir) outboxDir=createOutboxDir(nickname,domain,baseDir)
filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json' filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json'
saveJson(newAnnounce,filename) saveJson(newAnnounce,filename)
@ -291,8 +291,8 @@ def announcePublic(session,baseDir: str,federationList: [], \
if ':' not in domain: if ':' not in domain:
fromDomain=domain+':'+str(port) fromDomain=domain+':'+str(port)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl='https://www.w3.org/ns/activitystreams#Public'
ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers' ccUrl=httpPrefix+'://'+fromDomain+'/users/'+nickname+'/followers'
return createAnnounce(session,baseDir,federationList, \ return createAnnounce(session,baseDir,federationList, \
nickname,domain,port, \ nickname,domain,port, \
toUrl,ccUrl,httpPrefix, \ toUrl,ccUrl,httpPrefix, \
@ -317,7 +317,7 @@ def repeatPost(session,baseDir: str,federationList: [], \
if ':' not in announcedDomain: if ':' not in announcedDomain:
announcedDomain=announcedDomain+':'+str(announcePort) announcedDomain=announcedDomain+':'+str(announcePort)
objectUrl = announceHttpsPrefix + '://'+announcedDomain+'/users/'+ \ objectUrl=announceHttpsPrefix+'://'+announcedDomain+'/users/'+ \
announceNickname+'/statuses/'+str(announceStatusNumber) announceNickname+'/statuses/'+str(announceStatusNumber)
return announcePublic(session,baseDir,federationList, \ return announcePublic(session,baseDir,federationList, \
@ -352,7 +352,7 @@ def undoAnnounce(session,baseDir: str,federationList: [], \
if ':' not in domain: if ':' not in domain:
fullDomain=domain+':'+str(port) fullDomain=domain+':'+str(port)
newUndoAnnounce = { newUndoAnnounce={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'type': 'Undo', 'type': 'Undo',
@ -403,8 +403,8 @@ def undoAnnouncePublic(session,baseDir: str,federationList: [], \
if ':' not in domain: if ':' not in domain:
fromDomain=domain+':'+str(port) fromDomain=domain+':'+str(port)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl='https://www.w3.org/ns/activitystreams#Public'
ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers' ccUrl=httpPrefix+'://'+fromDomain+'/users/'+nickname+'/followers'
return undoAnnounce(session,baseDir,federationList, \ return undoAnnounce(session,baseDir,federationList, \
nickname,domain,port, \ nickname,domain,port, \
toUrl,ccUrl,httpPrefix, \ toUrl,ccUrl,httpPrefix, \
@ -429,7 +429,7 @@ def undoRepeatPost(session,baseDir: str,federationList: [], \
if ':' not in announcedDomain: if ':' not in announcedDomain:
announcedDomain=announcedDomain+':'+str(announcePort) announcedDomain=announcedDomain+':'+str(announcePort)
objectUrl = announceHttpsPrefix + '://'+announcedDomain+'/users/'+ \ objectUrl=announceHttpsPrefix+'://'+announcedDomain+'/users/'+ \
announceNickname+'/statuses/'+str(announceStatusNumber) announceNickname+'/statuses/'+str(announceStatusNumber)
return undoAnnouncePublic(session,baseDir,federationList, \ return undoAnnouncePublic(session,baseDir,federationList, \
@ -459,14 +459,14 @@ def sendAnnounceViaServer(baseDir: str,session, \
if ':' not in fromDomain: if ':' not in fromDomain:
fromDomainFull=fromDomain+':'+str(fromPort) fromDomainFull=fromDomain+':'+str(fromPort)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl='https://www.w3.org/ns/activitystreams#Public'
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers' ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
newAnnounceId= \ newAnnounceId= \
httpPrefix+'://'+fromDomainFull+'/users/'+ \ httpPrefix+'://'+fromDomainFull+'/users/'+ \
fromNickname+'/statuses/'+statusNumber fromNickname+'/statuses/'+statusNumber
newAnnounceJson = { newAnnounceJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
'atomUri': newAnnounceId, 'atomUri': newAnnounceId,
@ -481,7 +481,7 @@ def sendAnnounceViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = \ wfRequest= \
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion) fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
@ -492,7 +492,7 @@ def sendAnnounceViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname,fromDomain,postToBox) projectVersion,httpPrefix,fromNickname,fromDomain,postToBox)
@ -507,10 +507,12 @@ def sendAnnounceViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newAnnounceJson,[],inboxUrl,headers,"inbox:write") postJson(session,newAnnounceJson,[],inboxUrl,headers,"inbox:write")
if debug: if debug:

44
auth.py
View File

@ -1,10 +1,10 @@
__filename__ = "auth.py" __filename__="auth.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import base64 import base64
import hashlib import hashlib
@ -16,24 +16,24 @@ import random
def hashPassword(password: str) -> str: def hashPassword(password: str) -> str:
"""Hash a password for storing """Hash a password for storing
""" """
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') salt=hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
pwdhash = hashlib.pbkdf2_hmac('sha512', \ pwdhash=hashlib.pbkdf2_hmac('sha512', \
password.encode('utf-8'), \ password.encode('utf-8'), \
salt, 100000) salt, 100000)
pwdhash = binascii.hexlify(pwdhash) pwdhash=binascii.hexlify(pwdhash)
return (salt + pwdhash).decode('ascii') return (salt+pwdhash).decode('ascii')
def verifyPassword(storedPassword: str,providedPassword: str) -> bool: def verifyPassword(storedPassword: str,providedPassword: str) -> bool:
"""Verify a stored password against one provided by user """Verify a stored password against one provided by user
""" """
salt = storedPassword[:64] salt=storedPassword[:64]
storedPassword = storedPassword[64:] storedPassword=storedPassword[64:]
pwdhash = hashlib.pbkdf2_hmac('sha512', \ pwdhash=hashlib.pbkdf2_hmac('sha512', \
providedPassword.encode('utf-8'), \ providedPassword.encode('utf-8'), \
salt.encode('ascii'), \ salt.encode('ascii'), \
100000) 100000)
pwdhash = binascii.hexlify(pwdhash).decode('ascii') pwdhash=binascii.hexlify(pwdhash).decode('ascii')
return pwdhash == storedPassword return pwdhash==storedPassword
def createBasicAuthHeader(nickname: str,password: str) -> str: def createBasicAuthHeader(nickname: str,password: str) -> str:
"""This is only used by tests """This is only used by tests
@ -60,13 +60,13 @@ def authorizeBasic(baseDir: str,path: str,authHeader: str,debug: bool) -> bool:
print('DEBUG: This is not a users endpoint') print('DEBUG: This is not a users endpoint')
return False return False
nicknameFromPath=pathUsersSection.split('/')[0] nicknameFromPath=pathUsersSection.split('/')[0]
base64Str = authHeader.split(' ')[1].replace('\n','') base64Str=authHeader.split(' ')[1].replace('\n','')
plain = base64.b64decode(base64Str).decode('utf-8') plain=base64.b64decode(base64Str).decode('utf-8')
if ':' not in plain: if ':' not in plain:
if debug: if debug:
print('DEBUG: Basic Auth header does not contain a ":" separator for username:password') print('DEBUG: Basic Auth header does not contain a ":" separator for username:password')
return False return False
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+ \ print('DEBUG: Nickname given in the path ('+nicknameFromPath+ \
@ -78,12 +78,12 @@ def authorizeBasic(baseDir: str,path: str,authHeader: str,debug: bool) -> bool:
if debug: if debug:
print('DEBUG: passwords file missing') print('DEBUG: passwords file missing')
return False return False
providedPassword = plain.split(':')[1] providedPassword=plain.split(':')[1]
passfile = open(passwordFile, "r") passfile=open(passwordFile, "r")
for line in passfile: for line in passfile:
if line.startswith(nickname+':'): if line.startswith(nickname+':'):
storedPassword=line.split(':')[1].replace('\n','') storedPassword=line.split(':')[1].replace('\n','')
success = verifyPassword(storedPassword,providedPassword) success=verifyPassword(storedPassword,providedPassword)
if not success: if not success:
if debug: if debug:
print('DEBUG: Password check failed for '+nickname) print('DEBUG: Password check failed for '+nickname)

View File

@ -1,10 +1,10 @@
__filename__ = "availability.py" __filename__="availability.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import time import time
@ -89,10 +89,10 @@ def sendAvailabilityViaServer(baseDir: str,session, \
if ':' not in domain: if ':' not in domain:
domainFull=domain+':'+str(port) domainFull=domain+':'+str(port)
toUrl = httpPrefix+'://'+domainFull+'/users/'+nickname toUrl=httpPrefix+'://'+domainFull+'/users/'+nickname
ccUrl = httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers' ccUrl=httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers'
newAvailabilityJson = { newAvailabilityJson={
'type': 'Availability', 'type': 'Availability',
'actor': httpPrefix+'://'+domainFull+'/users/'+nickname, 'actor': httpPrefix+'://'+domainFull+'/users/'+nickname,
'object': '"'+status+'"', 'object': '"'+status+'"',
@ -103,8 +103,9 @@ def sendAvailabilityViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+domainFull+'/@'+nickname handle=httpPrefix+'://'+domainFull+'/@'+nickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest= \
domain,projectVersion) webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
domain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -113,7 +114,7 @@ def sendAvailabilityViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,nickname,domain,postToBox) projectVersion,httpPrefix,nickname,domain,postToBox)
@ -128,10 +129,12 @@ def sendAvailabilityViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(Nickname,password) authHeader=createBasicAuthHeader(Nickname,password)
headers = {'host': domain, \ headers={
'Content-type': 'application/json', \ 'host': domain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newAvailabilityJson,[],inboxUrl,headers,"inbox:write") postJson(session,newAvailabilityJson,[],inboxUrl,headers,"inbox:write")
if debug: if debug:

View File

@ -1,10 +1,10 @@
__filename__ = "blocking.py" __filename__="blocking.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
from utils import isEvil from utils import isEvil
@ -174,10 +174,10 @@ def sendBlockViaServer(baseDir: str,session, \
toUrl= 'https://www.w3.org/ns/activitystreams#Public' toUrl= 'https://www.w3.org/ns/activitystreams#Public'
ccUrl= \ ccUrl= \
httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers' httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
newBlockJson = { newBlockJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Block', 'type': 'Block',
'actor': blockActor, 'actor': blockActor,
@ -189,8 +189,9 @@ def sendBlockViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest= \
fromDomain,projectVersion) webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -199,7 +200,7 @@ def sendBlockViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -215,10 +216,12 @@ def sendBlockViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write") postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
if debug: if debug:
@ -246,10 +249,10 @@ def sendUndoBlockViaServer(baseDir: str,session, \
toUrl= 'https://www.w3.org/ns/activitystreams#Public' toUrl= 'https://www.w3.org/ns/activitystreams#Public'
ccUrl= \ ccUrl= \
httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers' httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
newBlockJson = { newBlockJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo', 'type': 'Undo',
'actor': blockActor, 'actor': blockActor,
@ -276,7 +279,7 @@ def sendUndoBlockViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -292,10 +295,12 @@ def sendUndoBlockViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write") postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
if debug: if debug:

24
blog.py
View File

@ -1,10 +1,10 @@
__filename__ = "blog.py" __filename__="blog.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import time import time
@ -63,12 +63,12 @@ def noOfBlogReplies(baseDir: str,httpPrefix: str,translate: {}, \
replies=0 replies=0
with open(postFilename, "r") as f: with open(postFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
for replyPostId in lines: for replyPostId in lines:
replyPostId= \ replyPostId= \
replyPostId.replace('\n','').replace('.json','').replace('.replies','') replyPostId.replace('\n','').replace('.json','').replace('.replies','')
replies+= \ replies+= \
1 + \ 1+ \
noOfBlogReplies(baseDir,httpPrefix,translate, \ noOfBlogReplies(baseDir,httpPrefix,translate, \
nickname,domain,domainFull, \ nickname,domain,domainFull, \
replyPostId,depth+1) replyPostId,depth+1)
@ -111,7 +111,7 @@ def getBlogReplies(baseDir: str,httpPrefix: str,translate: {}, \
return '' return ''
with open(postFilename, "r") as f: with open(postFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
repliesStr='' repliesStr=''
for replyPostId in lines: for replyPostId in lines:
replyPostId= \ replyPostId= \
@ -556,13 +556,13 @@ def htmlEditBlog(mediaInstance: bool,translate: {}, \
if os.path.isfile(baseDir+'/accounts/newpost.txt'): if os.path.isfile(baseDir+'/accounts/newpost.txt'):
with open(baseDir+'/accounts/newpost.txt', 'r') as file: with open(baseDir+'/accounts/newpost.txt', 'r') as file:
editBlogText = '<p class="new-post-text">'+file.read()+'</p>' editBlogText='<p class="new-post-text">'+file.read()+'</p>'
cssFilename=baseDir+'/epicyon-profile.css' cssFilename=baseDir+'/epicyon-profile.css'
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
editBlogCSS = cssFile.read() editBlogCSS=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
editBlogCSS=editBlogCSS.replace('https://',httpPrefix+'://') editBlogCSS=editBlogCSS.replace('https://',httpPrefix+'://')

View File

@ -32,16 +32,16 @@ Very close port of the original Swift implementation by Dag Ågren.
import math import math
# Alphabet for base 83 # Alphabet for base 83
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~" alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
alphabet_values = dict(zip(alphabet, range(len(alphabet)))) alphabet_values=dict(zip(alphabet,range(len(alphabet))))
def base83_decode(base83_str): def base83_decode(base83_str):
""" """
Decodes a base83 string, as used in blurhash, to an integer. Decodes a base83 string, as used in blurhash, to an integer.
""" """
value = 0 value=0
for base83_char in base83_str: for base83_char in base83_str:
value = value * 83 + alphabet_values[base83_char] value=value*83+alphabet_values[base83_char]
return value return value
def base83_encode(value, length): def base83_encode(value, length):
@ -54,9 +54,9 @@ def base83_encode(value, length):
if int(value) // (83 ** (length)) != 0: if int(value) // (83 ** (length)) != 0:
raise ValueError("Specified length is too short to encode given value.") raise ValueError("Specified length is too short to encode given value.")
result = "" result=""
for i in range(1, length + 1): for i in range(1,length+1):
digit = int(value) // (83 ** (length - i)) % 83 digit=int(value) // (83 ** (length - i)) % 83
result += alphabet[int(digit)] result += alphabet[int(digit)]
return result return result
@ -64,10 +64,10 @@ def srgb_to_linear(value):
""" """
srgb 0-255 integer to linear 0.0-1.0 floating point conversion. srgb 0-255 integer to linear 0.0-1.0 floating point conversion.
""" """
value = float(value) / 255.0 value=float(value) / 255.0
if value <= 0.04045: if value <= 0.04045:
return value / 12.92 return value / 12.92
return math.pow((value + 0.055) / 1.055, 2.4) return math.pow((value+0.055) / 1.055, 2.4)
def sign_pow(value, exp): def sign_pow(value, exp):
""" """
@ -79,10 +79,10 @@ def linear_to_srgb(value):
""" """
linear 0.0-1.0 floating point to srgb 0-255 integer conversion. linear 0.0-1.0 floating point to srgb 0-255 integer conversion.
""" """
value = max(0.0, min(1.0, value)) value=max(0.0, min(1.0, value))
if value <= 0.0031308: if value <= 0.0031308:
return int(value * 12.92 * 255 + 0.5) return int(value*12.92*255+0.5)
return int((1.055 * math.pow(value, 1 / 2.4) - 0.055) * 255 + 0.5) return int((1.055*math.pow(value, 1 / 2.4)-0.055)*255+0.5)
def blurhash_components(blurhash): def blurhash_components(blurhash):
""" """
@ -92,13 +92,13 @@ def blurhash_components(blurhash):
raise ValueError("BlurHash must be at least 6 characters long.") raise ValueError("BlurHash must be at least 6 characters long.")
# Decode metadata # Decode metadata
size_info = base83_decode(blurhash[0]) size_info=base83_decode(blurhash[0])
size_y = int(size_info / 9) + 1 size_y=int(size_info / 9)+1
size_x = (size_info % 9) + 1 size_x=(size_info % 9)+1
return size_x, size_y return size_x, size_y
def blurhash_decode(blurhash, width, height, punch = 1.0, linear = False): def blurhash_decode(blurhash,width,height,punch=1.0,linear=False):
""" """
Decodes the given blurhash to an image of the specified size. Decodes the given blurhash to an image of the specified size.
@ -117,20 +117,20 @@ def blurhash_decode(blurhash, width, height, punch = 1.0, linear = False):
raise ValueError("BlurHash must be at least 6 characters long.") raise ValueError("BlurHash must be at least 6 characters long.")
# Decode metadata # Decode metadata
size_info = base83_decode(blurhash[0]) size_info=base83_decode(blurhash[0])
size_y = int(size_info / 9) + 1 size_y=int(size_info / 9)+1
size_x = (size_info % 9) + 1 size_x=(size_info % 9)+1
quant_max_value = base83_decode(blurhash[1]) quant_max_value=base83_decode(blurhash[1])
real_max_value = (float(quant_max_value + 1) / 166.0) * punch real_max_value=(float(quant_max_value+1)/166.0)*punch
# Make sure we at least have the right number of characters # Make sure we at least have the right number of characters
if len(blurhash) != 4 + 2 * size_x * size_y: if len(blurhash) != 4+2*size_x*size_y:
raise ValueError("Invalid BlurHash length.") raise ValueError("Invalid BlurHash length.")
# Decode DC component # Decode DC component
dc_value = base83_decode(blurhash[2:6]) dc_value=base83_decode(blurhash[2:6])
colours = [( colours=[(
srgb_to_linear(dc_value >> 16), srgb_to_linear(dc_value >> 16),
srgb_to_linear((dc_value >> 8) & 255), srgb_to_linear((dc_value >> 8) & 255),
srgb_to_linear(dc_value & 255) srgb_to_linear(dc_value & 255)
@ -138,7 +138,7 @@ def blurhash_decode(blurhash, width, height, punch = 1.0, linear = False):
# Decode AC components # Decode AC components
for component in range(1, size_x * size_y): for component in range(1, size_x * size_y):
ac_value = base83_decode(blurhash[4+component*2:4+(component+1)*2]) ac_value=base83_decode(blurhash[4+component*2:4+(component+1)*2])
colours.append(( colours.append((
sign_pow((float(int(ac_value / (19 * 19))) - 9.0) / 9.0, 2.0) * real_max_value, sign_pow((float(int(ac_value / (19 * 19))) - 9.0) / 9.0, 2.0) * real_max_value,
sign_pow((float(int(ac_value / 19) % 19) - 9.0) / 9.0, 2.0) * real_max_value, sign_pow((float(int(ac_value / 19) % 19) - 9.0) / 9.0, 2.0) * real_max_value,
@ -147,20 +147,21 @@ def blurhash_decode(blurhash, width, height, punch = 1.0, linear = False):
# Return image RGB values, as a list of lists of lists, # Return image RGB values, as a list of lists of lists,
# consumable by something like numpy or PIL. # consumable by something like numpy or PIL.
pixels = [] pixels=[]
for y in range(height): for y in range(height):
pixel_row = [] pixel_row=[]
for x in range(width): for x in range(width):
pixel = [0.0, 0.0, 0.0] pixel=[0.0, 0.0, 0.0]
for j in range(size_y): for j in range(size_y):
for i in range(size_x): for i in range(size_x):
basis = math.cos(math.pi * float(x) * float(i) / float(width)) * \ basis= \
math.cos(math.pi * float(y) * float(j) / float(height)) math.cos(math.pi*float(x)*float(i)/float(width))* \
colour = colours[i + j * size_x] math.cos(math.pi*float(y)*float(j)/float(height))
pixel[0] += colour[0] * basis colour=colours[i+j*size_x]
pixel[1] += colour[1] * basis pixel[0] += colour[0]*basis
pixel[2] += colour[2] * basis pixel[1] += colour[1]*basis
pixel[2] += colour[2]*basis
if linear == False: if linear == False:
pixel_row.append([ pixel_row.append([
linear_to_srgb(pixel[0]), linear_to_srgb(pixel[0]),
@ -172,7 +173,7 @@ def blurhash_decode(blurhash, width, height, punch = 1.0, linear = False):
pixels.append(pixel_row) pixels.append(pixel_row)
return pixels return pixels
def blurhash_encode(image, components_x = 4, components_y = 4, linear = False): def blurhash_encode(image,components_x=4,components_y=4,linear=False):
""" """
Calculates the blurhash for an image using the given x and y component counts. Calculates the blurhash for an image using the given x and y component counts.
@ -186,14 +187,14 @@ def blurhash_encode(image, components_x = 4, components_y = 4, linear = False):
""" """
if components_x < 1 or components_x > 9 or components_y < 1 or components_y > 9: if components_x < 1 or components_x > 9 or components_y < 1 or components_y > 9:
raise ValueError("x and y component counts must be between 1 and 9 inclusive.") raise ValueError("x and y component counts must be between 1 and 9 inclusive.")
height = float(len(image)) height=float(len(image))
width = float(len(image[0])) width=float(len(image[0]))
# Convert to linear if neeeded # Convert to linear if neeeded
image_linear = [] image_linear=[]
if linear == False: if linear==False:
for y in range(int(height)): for y in range(int(height)):
image_linear_line = [] image_linear_line=[]
for x in range(int(width)): for x in range(int(width)):
image_linear_line.append([ image_linear_line.append([
srgb_to_linear(image[y][x][0]), srgb_to_linear(image[y][x][0]),
@ -202,19 +203,20 @@ def blurhash_encode(image, components_x = 4, components_y = 4, linear = False):
]) ])
image_linear.append(image_linear_line) image_linear.append(image_linear_line)
else: else:
image_linear = image image_linear=image
# Calculate components # Calculate components
components = [] components=[]
max_ac_component = 0.0 max_ac_component=0.0
for j in range(components_y): for j in range(components_y):
for i in range(components_x): for i in range(components_x):
norm_factor = 1.0 if (i == 0 and j == 0) else 2.0 norm_factor=1.0 if (i==0 and j==0) else 2.0
component = [0.0, 0.0, 0.0] component=[0.0,0.0,0.0]
for y in range(int(height)): for y in range(int(height)):
for x in range(int(width)): for x in range(int(width)):
basis = norm_factor * math.cos(math.pi * float(i) * float(x) / width) * \ basis= \
math.cos(math.pi * float(j) * float(y) / height) norm_factor * math.cos(math.pi * float(i) * float(x) / width) * \
math.cos(math.pi * float(j) * float(y) / height)
component[0] += basis * image_linear[y][x][0] component[0] += basis * image_linear[y][x][0]
component[1] += basis * image_linear[y][x][1] component[1] += basis * image_linear[y][x][1]
component[2] += basis * image_linear[y][x][2] component[2] += basis * image_linear[y][x][2]
@ -224,27 +226,32 @@ def blurhash_encode(image, components_x = 4, components_y = 4, linear = False):
component[2] /= (width * height) component[2] /= (width * height)
components.append(component) components.append(component)
if not (i == 0 and j == 0): if not (i==0 and j==0):
max_ac_component = max(max_ac_component, abs(component[0]), abs(component[1]), abs(component[2])) max_ac_component= \
max(max_ac_component,abs(component[0]), \
abs(component[1]),abs(component[2]))
# Encode components # Encode components
dc_value = (linear_to_srgb(components[0][0]) << 16) + \ dc_value= \
(linear_to_srgb(components[0][1]) << 8) + \ (linear_to_srgb(components[0][0]) << 16)+ \
linear_to_srgb(components[0][2]) (linear_to_srgb(components[0][1]) << 8)+ \
linear_to_srgb(components[0][2])
quant_max_ac_component = int(max(0, min(82, math.floor(max_ac_component * 166 - 0.5)))) quant_max_ac_component= \
ac_component_norm_factor = float(quant_max_ac_component + 1) / 166.0 int(max(0, min(82, math.floor(max_ac_component * 166 - 0.5))))
ac_component_norm_factor= \
float(quant_max_ac_component+1) / 166.0
ac_values = [] ac_values=[]
for r, g, b in components[1:]: for r, g, b in components[1:]:
ac_values.append( ac_values.append(
int(max(0.0, min(18.0, math.floor(sign_pow(r / ac_component_norm_factor, 0.5) * 9.0 + 9.5)))) * 19 * 19 + \ int(max(0.0,min(18.0,math.floor(sign_pow(r / ac_component_norm_factor, 0.5) * 9.0 + 9.5)))) * 19 * 19 + \
int(max(0.0, min(18.0, math.floor(sign_pow(g / ac_component_norm_factor, 0.5) * 9.0 + 9.5)))) * 19 + \ int(max(0.0,min(18.0, math.floor(sign_pow(g / ac_component_norm_factor, 0.5) * 9.0 + 9.5)))) * 19 + \
int(max(0.0, min(18.0, math.floor(sign_pow(b / ac_component_norm_factor, 0.5) * 9.0 + 9.5)))) int(max(0.0,min(18.0, math.floor(sign_pow(b / ac_component_norm_factor, 0.5) * 9.0 + 9.5))))
) )
# Build final blurhash # Build final blurhash
blurhash = "" blurhash=""
blurhash += base83_encode((components_x - 1) + (components_y - 1) * 9, 1) blurhash += base83_encode((components_x - 1) + (components_y - 1) * 9, 1)
blurhash += base83_encode(quant_max_ac_component, 1) blurhash += base83_encode(quant_max_ac_component, 1)
blurhash += base83_encode(dc_value, 4) blurhash += base83_encode(dc_value, 4)

View File

@ -1,10 +1,10 @@
__filename__ = "bookmarks.py" __filename__="bookmarks.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import json import json
@ -159,7 +159,7 @@ def updateBookmarksCollection(recentPostsCache: {}, \
if not postJsonObject['object'].get('bookmarks'): if not postJsonObject['object'].get('bookmarks'):
if debug: if debug:
print('DEBUG: Adding initial bookmarks to '+objectUrl) print('DEBUG: Adding initial bookmarks to '+objectUrl)
bookmarksJson = { bookmarksJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'id': objectUrl, 'id': objectUrl,
'type': 'Collection', 'type': 'Collection',
@ -198,7 +198,7 @@ def updateBookmarksCollection(recentPostsCache: {}, \
if bookmarkIndex not in open(bookmarksIndexFilename).read(): if bookmarkIndex not in open(bookmarksIndexFilename).read():
try: try:
with open(bookmarksIndexFilename, 'r+') as bookmarksIndexFile: with open(bookmarksIndexFilename, 'r+') as bookmarksIndexFile:
content = bookmarksIndexFile.read() content=bookmarksIndexFile.read()
bookmarksIndexFile.seek(0, 0) bookmarksIndexFile.seek(0, 0)
bookmarksIndexFile.write(bookmarkIndex+'\n'+content) bookmarksIndexFile.write(bookmarkIndex+'\n'+content)
if debug: if debug:
@ -239,7 +239,7 @@ def bookmark(recentPostsCache: {}, \
if '/statuses/' in objectUrl: if '/statuses/' in objectUrl:
bookmarkTo=[objectUrl.split('/statuses/')[0]] bookmarkTo=[objectUrl.split('/statuses/')[0]]
newBookmarkJson = { newBookmarkJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Bookmark', 'type': 'Bookmark',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
@ -348,7 +348,7 @@ def undoBookmark(recentPostsCache: {}, \
if '/statuses/' in objectUrl: if '/statuses/' in objectUrl:
bookmarkTo=[objectUrl.split('/statuses/')[0]] bookmarkTo=[objectUrl.split('/statuses/')[0]]
newUndoBookmarkJson = { newUndoBookmarkJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo', 'type': 'Undo',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
@ -414,8 +414,8 @@ def undoBookmarkPost(session,baseDir: str,federationList: [], \
if ':' not in bookmarkedomain: if ':' not in bookmarkedomain:
bookmarkedomain=bookmarkedomain+':'+str(bookmarkPort) bookmarkedomain=bookmarkedomain+':'+str(bookmarkPort)
objectUrl = \ objectUrl= \
httpPrefix + '://'+bookmarkedomain+'/users/'+bookmarkNickname+ \ httpPrefix+'://'+bookmarkedomain+'/users/'+bookmarkNickname+ \
'/statuses/'+str(bookmarkStatusNumber) '/statuses/'+str(bookmarkStatusNumber)
ccUrl=httpPrefix+'://'+bookmarkedomain+'/users/'+bookmarkNickname ccUrl=httpPrefix+'://'+bookmarkedomain+'/users/'+bookmarkNickname
@ -454,7 +454,7 @@ def sendBookmarkViaServer(baseDir: str,session, \
if '/statuses/' in bookmarkUrl: if '/statuses/' in bookmarkUrl:
toUrl=[bookmarkUrl.split('/statuses/')[0]] toUrl=[bookmarkUrl.split('/statuses/')[0]]
newBookmarkJson = { newBookmarkJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Bookmark', 'type': 'Bookmark',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
@ -474,7 +474,7 @@ def sendBookmarkViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -490,10 +490,12 @@ def sendBookmarkViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newBookmarkJson,[],inboxUrl,headers,"inbox:write") postJson(session,newBookmarkJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:
@ -529,7 +531,7 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
if '/statuses/' in bookmarkUrl: if '/statuses/' in bookmarkUrl:
toUrl=[bookmarkUrl.split('/statuses/')[0]] toUrl=[bookmarkUrl.split('/statuses/')[0]]
newUndoBookmarkJson = { newUndoBookmarkJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo', 'type': 'Undo',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
@ -543,8 +545,8 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion) fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -553,7 +555,7 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -569,10 +571,12 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newUndoBookmarkJson,[],inboxUrl,headers,"inbox:write") postJson(session,newUndoBookmarkJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:

View File

@ -1,10 +1,10 @@
__filename__ = "cache.py" __filename__="cache.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import time import time

View File

@ -1,10 +1,10 @@
__filename__ = "capabilities.py" __filename__="capabilities.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import datetime import datetime
@ -94,7 +94,7 @@ def capabilitiesRequest(baseDir: str,httpPrefix: str,domain: str, \
# which could be instance wide or for a particular person # which could be instance wide or for a particular person
# This could also be added to a follow activity # This could also be added to a follow activity
ocapId=createPassword(32) ocapId=createPassword(32)
ocapRequest = { ocapRequest={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": httpPrefix+"://"+requestedDomain+"/caps/request/"+ocapId, "id": httpPrefix+"://"+requestedDomain+"/caps/request/"+ocapId,
"type": "Request", "type": "Request",
@ -140,7 +140,7 @@ def capabilitiesAccept(baseDir: str,httpPrefix: str, \
ocapId=acceptedActorNickname+'@'+acceptedActorDomain+':'+str(acceptedActorPort)+'#'+createPassword(32) ocapId=acceptedActorNickname+'@'+acceptedActorDomain+':'+str(acceptedActorPort)+'#'+createPassword(32)
else: else:
ocapId=acceptedActorNickname+'@'+acceptedActorDomain+'#'+createPassword(32) ocapId=acceptedActorNickname+'@'+acceptedActorDomain+'#'+createPassword(32)
ocapAccept = { ocapAccept={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": httpPrefix+"://"+fullDomain+"/caps/"+ocapId, "id": httpPrefix+"://"+fullDomain+"/caps/"+ocapId,
"type": "Capability", "type": "Capability",
@ -196,7 +196,7 @@ def capabilitiesUpdate(baseDir: str,httpPrefix: str, \
return None return None
# create an update activity # create an update activity
ocapUpdate = { ocapUpdate={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Update', 'type': 'Update',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,

View File

@ -1,10 +1,10 @@
__filename__ = "config.py" __filename__="config.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import time import time
@ -18,7 +18,7 @@ def createConfig(baseDir: str) -> None:
configFilename=baseDir+'/config.json' configFilename=baseDir+'/config.json'
if os.path.isfile(configFilename): if os.path.isfile(configFilename):
return return
configJson = { configJson={
} }
saveJson(configJson,configFilename) saveJson(configJson,configFilename)

View File

@ -1,10 +1,10 @@
__filename__ = "content.py" __filename__="content.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import time import time
@ -158,7 +158,8 @@ def addWebLinks(content: str) -> str:
def validHashTag(hashtag: str) -> bool: def validHashTag(hashtag: str) -> bool:
"""Returns true if the give hashtag contains valid characters """Returns true if the give hashtag contains valid characters
""" """
validChars = set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') validChars= \
set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
if set(hashtag).issubset(validChars): if set(hashtag).issubset(validChars):
return True return True
return False return False
@ -417,7 +418,7 @@ def addHtmlTags(baseDir: str,httpPrefix: str, \
if '@' in words: if '@' in words:
if os.path.isfile(followingFilename): if os.path.isfile(followingFilename):
with open(followingFilename, "r") as f: with open(followingFilename, "r") as f:
following = f.readlines() following=f.readlines()
# extract mentions and tags from words # extract mentions and tags from words
longWordsList=[] longWordsList=[]
@ -569,7 +570,7 @@ def saveMediaInFormPOST(mediaBytes,debug: bool, \
if os.path.isfile(possibleOtherFormat): if os.path.isfile(possibleOtherFormat):
os.remove(possibleOtherFormat) os.remove(possibleOtherFormat)
fd = open(filename, 'wb') fd=open(filename, 'wb')
fd.write(mediaBytes[startPos:]) fd.write(mediaBytes[startPos:])
fd.close() fd.close()
@ -579,7 +580,7 @@ def extractTextFieldsInPOST(postBytes,boundary,debug: bool) -> {}:
"""Returns a dictionary containing the text fields of a http form POST """Returns a dictionary containing the text fields of a http form POST
The boundary argument comes from the http header The boundary argument comes from the http header
""" """
msg = email.parser.BytesParser().parsebytes(postBytes) msg=email.parser.BytesParser().parsebytes(postBytes)
if debug: if debug:
print('DEBUG: POST arriving '+msg.get_payload(decode=True).decode('utf-8')) print('DEBUG: POST arriving '+msg.get_payload(decode=True).decode('utf-8'))
messageFields=msg.get_payload(decode=True).decode('utf-8').split(boundary) messageFields=msg.get_payload(decode=True).decode('utf-8').split(boundary)

138
daemon.py
View File

@ -1,10 +1,10 @@
__filename__ = "daemon.py" __filename__="daemon.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
#import socketserver #import socketserver
@ -218,17 +218,17 @@ def readFollowList(filename: str) -> None:
followlist=[] followlist=[]
if not os.path.isfile(filename): if not os.path.isfile(filename):
return followlist return followlist
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:
nickname,domain = parseHandle(u) nickname,domain=parseHandle(u)
if nickname: if nickname:
followlist.append(nickname+'@'+domain) followlist.append(nickname+'@'+domain)
followUsers.close() followUsers.close()
return followlist return followlist
class PubServer(BaseHTTPRequestHandler): class PubServer(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1' protocol_version='HTTP/1.1'
def _sendReplyToQuestion(self,nickname: str,messageId: str,answer: str) -> None: def _sendReplyToQuestion(self,nickname: str,messageId: str,answer: str) -> None:
"""Sends a reply to a question """Sends a reply to a question
@ -451,7 +451,7 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isfile(mediaFilename+'.etag'): if os.path.isfile(mediaFilename+'.etag'):
try: try:
with open(mediaFilename+'.etag', 'r') as etagFile: with open(mediaFilename+'.etag', 'r') as etagFile:
etag = etagFile.read() etag=etagFile.read()
except: except:
pass pass
if not etag: if not etag:
@ -771,7 +771,7 @@ class PubServer(BaseHTTPRequestHandler):
beginSaveTime=time.time() beginSaveTime=time.time()
# save the json for later queue processing # save the json for later queue processing
queueFilename = \ queueFilename= \
savePostToInboxQueue(self.server.baseDir, savePostToInboxQueue(self.server.baseDir,
self.server.httpPrefix, self.server.httpPrefix,
nickname, nickname,
@ -962,7 +962,7 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime,GETtimings,4) self._benchmarkGETtimings(GETstartTime,GETtimings,4)
# check authorization # check authorization
authorized = self._isAuthorized() authorized=self._isAuthorized()
if self.server.debug: if self.server.debug:
if authorized: if authorized:
print('GET Authorization granted') print('GET Authorization granted')
@ -1270,7 +1270,7 @@ class PubServer(BaseHTTPRequestHandler):
while tries<5: while tries<5:
try: try:
with open('epicyon-profile.css', 'r') as cssfile: with open('epicyon-profile.css', 'r') as cssfile:
css = cssfile.read() css=cssfile.read()
break break
except Exception as e: except Exception as e:
print(e) print(e)
@ -1299,7 +1299,7 @@ class PubServer(BaseHTTPRequestHandler):
while tries<5: while tries<5:
try: try:
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
break break
except Exception as e: except Exception as e:
print(e) print(e)
@ -1324,7 +1324,7 @@ class PubServer(BaseHTTPRequestHandler):
while tries<5: while tries<5:
try: try:
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
break break
except Exception as e: except Exception as e:
print(e) print(e)
@ -1349,7 +1349,7 @@ class PubServer(BaseHTTPRequestHandler):
while tries<5: while tries<5:
try: try:
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
break break
except Exception as e: except Exception as e:
print(e) print(e)
@ -1383,7 +1383,7 @@ class PubServer(BaseHTTPRequestHandler):
else: else:
mediaImageType='gif' mediaImageType='gif'
with open(emojiFilename, 'rb') as avFile: with open(emojiFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
self._set_headers('image/'+mediaImageType,len(mediaBinary),cookie) self._set_headers('image/'+mediaImageType,len(mediaBinary),cookie)
self._write(mediaBinary) self._write(mediaBinary)
return return
@ -1439,7 +1439,7 @@ class PubServer(BaseHTTPRequestHandler):
currEtag='' currEtag=''
try: try:
with open(mediaFilename, 'r') as etagFile: with open(mediaFilename, 'r') as etagFile:
currEtag = etagFile.read() currEtag=etagFile.read()
except: except:
pass pass
if oldEtag==currEtag: if oldEtag==currEtag:
@ -1447,7 +1447,7 @@ class PubServer(BaseHTTPRequestHandler):
self._304() self._304()
return return
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
self._set_headers_etag(mediaFilename,mediaFileType,mediaBinary,cookie) self._set_headers_etag(mediaFilename,mediaFileType,mediaBinary,cookie)
self._write(mediaBinary) self._write(mediaBinary)
return return
@ -1477,7 +1477,7 @@ class PubServer(BaseHTTPRequestHandler):
else: else:
mediaFileType='gif' mediaFileType='gif'
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
self._set_headers('image/'+mediaFileType,len(mediaBinary),cookie) self._set_headers('image/'+mediaFileType,len(mediaBinary),cookie)
self._write(mediaBinary) self._write(mediaBinary)
return return
@ -1501,7 +1501,7 @@ class PubServer(BaseHTTPRequestHandler):
else: else:
if os.path.isfile(mediaFilename): if os.path.isfile(mediaFilename):
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
self._set_headers('image/png',len(mediaBinary),cookie) self._set_headers('image/png',len(mediaBinary),cookie)
self._write(mediaBinary) self._write(mediaBinary)
self.server.iconsCache[mediaStr]=mediaBinary self.server.iconsCache[mediaStr]=mediaBinary
@ -1518,7 +1518,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.baseDir+'/cache/'+self.path self.server.baseDir+'/cache/'+self.path
if os.path.isfile(mediaFilename): if os.path.isfile(mediaFilename):
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
if mediaFilename.endswith('.png'): if mediaFilename.endswith('.png'):
self._set_headers('image/png',len(mediaBinary),cookie) self._set_headers('image/png',len(mediaBinary),cookie)
elif mediaFilename.endswith('.jpg'): elif mediaFilename.endswith('.jpg'):
@ -1568,7 +1568,7 @@ class PubServer(BaseHTTPRequestHandler):
else: else:
mediaImageType='webp' mediaImageType='webp'
with open(avatarFilename, 'rb') as avFile: with open(avatarFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
self._set_headers('image/'+mediaImageType, \ self._set_headers('image/'+mediaImageType, \
len(mediaBinary),cookie) len(mediaBinary),cookie)
self._write(mediaBinary) self._write(mediaBinary)
@ -1861,7 +1861,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.httpPrefix+'://'+self.server.domainFull+ \ self.server.httpPrefix+'://'+self.server.domainFull+ \
'/users/'+self.postToNickname '/users/'+self.postToNickname
unRepeatToStr='https://www.w3.org/ns/activitystreams#Public' unRepeatToStr='https://www.w3.org/ns/activitystreams#Public'
newUndoAnnounce = { newUndoAnnounce={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'actor': undoAnnounceActor, 'actor': undoAnnounceActor,
'type': 'Undo', 'type': 'Undo',
@ -2534,7 +2534,7 @@ class PubServer(BaseHTTPRequestHandler):
nickname+'#statuses#'+statusNumber+'.replies' nickname+'#statuses#'+statusNumber+'.replies'
if not os.path.isfile(postRepliesFilename): if not os.path.isfile(postRepliesFilename):
# There are no replies, so show empty collection # There are no replies, so show empty collection
repliesJson = { repliesJson={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'first': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true', 'first': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true',
'id': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies', 'id': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies',
@ -2574,7 +2574,7 @@ class PubServer(BaseHTTPRequestHandler):
return return
else: else:
# replies exist. Itterate through the text file containing message ids # replies exist. Itterate through the text file containing message ids
repliesJson = { repliesJson={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true', 'id': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true',
'orderedItems': [ 'orderedItems': [
@ -2637,7 +2637,7 @@ class PubServer(BaseHTTPRequestHandler):
if actorJson: if actorJson:
if actorJson.get('roles'): if actorJson.get('roles'):
if self._requestHTTP(): if self._requestHTTP():
getPerson = \ getPerson= \
personLookup(self.server.domain, \ personLookup(self.server.domain, \
self.path.replace('/roles',''), \ self.path.replace('/roles',''), \
self.server.baseDir) self.server.baseDir)
@ -2683,7 +2683,7 @@ class PubServer(BaseHTTPRequestHandler):
if actorJson: if actorJson:
if actorJson.get('skills'): if actorJson.get('skills'):
if self._requestHTTP(): if self._requestHTTP():
getPerson = \ getPerson= \
personLookup(self.server.domain, \ personLookup(self.server.domain, \
self.path.replace('/skills',''), \ self.path.replace('/skills',''), \
self.server.baseDir) self.server.baseDir)
@ -3525,7 +3525,7 @@ class PubServer(BaseHTTPRequestHandler):
if pageNumberStr.isdigit(): if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr) pageNumber=int(pageNumberStr)
searchPath=self.path.split('?page=')[0] searchPath=self.path.split('?page=')[0]
getPerson = \ getPerson= \
personLookup(self.server.domain, \ personLookup(self.server.domain, \
searchPath.replace('/following',''), \ searchPath.replace('/following',''), \
self.server.baseDir) self.server.baseDir)
@ -3629,7 +3629,7 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime,GETtimings,52) self._benchmarkGETtimings(GETstartTime,GETtimings,52)
# look up a person # look up a person
getPerson = \ getPerson= \
personLookup(self.server.domain,self.path, \ personLookup(self.server.domain,self.path, \
self.server.baseDir) self.server.baseDir)
if getPerson: if getPerson:
@ -3687,7 +3687,7 @@ class PubServer(BaseHTTPRequestHandler):
filename=self.server.baseDir+self.path filename=self.server.baseDir+self.path
if os.path.isfile(filename): if os.path.isfile(filename):
with open(filename, 'r', encoding='utf-8') as File: with open(filename, 'r', encoding='utf-8') as File:
content = File.read() content=File.read()
contentJson=json.loads(content) contentJson=json.loads(content)
msg=json.dumps(contentJson,ensure_ascii=False).encode('utf-8') msg=json.dumps(contentJson,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None) self._set_headers('application/json',len(msg),None)
@ -3723,12 +3723,12 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isfile(mediaFilename+'.etag'): if os.path.isfile(mediaFilename+'.etag'):
try: try:
with open(mediaFilename+'.etag', 'r') as etagFile: with open(mediaFilename+'.etag', 'r') as etagFile:
etag = etagFile.read() etag=etagFile.read()
except: except:
pass pass
else: else:
with open(mediaFilename, 'rb') as avFile: with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
etag=sha1(mediaBinary).hexdigest() etag=sha1(mediaBinary).hexdigest()
try: try:
with open(mediaFilename+'.etag', 'w') as etagFile: with open(mediaFilename+'.etag', 'w') as etagFile:
@ -3760,10 +3760,10 @@ class PubServer(BaseHTTPRequestHandler):
postType: str,path: str,headers: {}, postType: str,path: str,headers: {},
length: int,postBytes,boundary: str) -> int: length: int,postBytes,boundary: str) -> int:
# Note: this needs to happen synchronously # Note: this needs to happen synchronously
# 0 = this is not a new post # 0=this is not a new post
# 1 = new post success # 1=new post success
# -1 = new post failed # -1=new post failed
# 2 = new post canceled # 2=new post canceled
if self.server.debug: if self.server.debug:
print('DEBUG: receiving POST') print('DEBUG: receiving POST')
@ -3776,7 +3776,7 @@ class PubServer(BaseHTTPRequestHandler):
nickname=nicknameStr.split('/')[0] nickname=nicknameStr.split('/')[0]
else: else:
return -1 return -1
length = int(headers['Content-Length']) length=int(headers['Content-Length'])
if length>self.server.maxPostLength: if length>self.server.maxPostLength:
print('POST size too large') print('POST size too large')
return -1 return -1
@ -4229,7 +4229,7 @@ class PubServer(BaseHTTPRequestHandler):
headersWithoutCookie[dictEntryName]=headerLine headersWithoutCookie[dictEntryName]=headerLine
print('New post headers: '+str(headersWithoutCookie)) print('New post headers: '+str(headersWithoutCookie))
length = int(headers['Content-Length']) length=int(headers['Content-Length'])
if length>self.server.maxPostLength: if length>self.server.maxPostLength:
print('POST size too large') print('POST size too large')
return None return None
@ -4304,7 +4304,7 @@ class PubServer(BaseHTTPRequestHandler):
cookie=self.headers['Cookie'] cookie=self.headers['Cookie']
# check authorization # check authorization
authorized = self._isAuthorized() authorized=self._isAuthorized()
if self.server.debug: if self.server.debug:
if authorized: if authorized:
print('POST Authorization granted') print('POST Authorization granted')
@ -4320,7 +4320,7 @@ class PubServer(BaseHTTPRequestHandler):
if self.path.startswith('/login'): if self.path.startswith('/login'):
# get the contents of POST containing login credentials # get the contents of POST containing login credentials
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
if length>512: if length>512:
print('Login failed - credentials too long') print('Login failed - credentials too long')
self.send_response(401) self.send_response(401)
@ -4368,7 +4368,7 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isfile(saltFilename): if os.path.isfile(saltFilename):
try: try:
with open(saltFilename, 'r') as fp: with open(saltFilename, 'r') as fp:
salt = fp.read() salt=fp.read()
except Exception as e: except Exception as e:
print('WARN: Unable to read salt for '+ \ print('WARN: Unable to read salt for '+ \
loginNickname+' '+str(e)) loginNickname+' '+str(e))
@ -4430,7 +4430,7 @@ class PubServer(BaseHTTPRequestHandler):
self._redirect_headers(actorStr,cookie) self._redirect_headers(actorStr,cookie)
self.server.POSTbusy=False self.server.POSTbusy=False
return return
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
if length>self.server.maxPostLength: if length>self.server.maxPostLength:
print('Maximum profile data length exceeded '+str(length)) print('Maximum profile data length exceeded '+str(length))
self._redirect_headers(actorStr,cookie) self._redirect_headers(actorStr,cookie)
@ -4855,7 +4855,7 @@ class PubServer(BaseHTTPRequestHandler):
actorStr= \ actorStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \ self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.replace('/moderationaction','') self.path.replace('/moderationaction','')
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
moderationParams=self.rfile.read(length).decode('utf-8') moderationParams=self.rfile.read(length).decode('utf-8')
print('moderationParams: '+moderationParams) print('moderationParams: '+moderationParams)
if '&' in moderationParams: if '&' in moderationParams:
@ -4995,7 +4995,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy=False self.server.POSTbusy=False
return return
# get the parameters # get the parameters
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
questionParams=self.rfile.read(length).decode('utf-8') questionParams=self.rfile.read(length).decode('utf-8')
questionParams= \ questionParams= \
questionParams.replace('+',' ').replace('%40','@').replace('%3A',':').replace('%23','#').replace('%2F','/').replace('%3F','').strip() questionParams.replace('+',' ').replace('%40','@').replace('%3A',':').replace('%23','#').replace('%2F','/').replace('%3F','').strip()
@ -5034,7 +5034,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.httpPrefix+'://'+ \ self.server.httpPrefix+'://'+ \
self.server.domainFull+ \ self.server.domainFull+ \
self.path.replace('/searchhandle','') self.path.replace('/searchhandle','')
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
searchParams=self.rfile.read(length).decode('utf-8') searchParams=self.rfile.read(length).decode('utf-8')
if 'submitBack=' in searchParams: if 'submitBack=' in searchParams:
# go back on search screen # go back on search screen
@ -5165,7 +5165,7 @@ class PubServer(BaseHTTPRequestHandler):
originPathStr= \ originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \ self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/rmshare')[0] self.path.split('/rmshare')[0]
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
removeShareConfirmParams=self.rfile.read(length).decode('utf-8') removeShareConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in removeShareConfirmParams: if '&submitYes=' in removeShareConfirmParams:
removeShareConfirmParams= \ removeShareConfirmParams= \
@ -5193,7 +5193,7 @@ class PubServer(BaseHTTPRequestHandler):
originPathStr= \ originPathStr= \
self.server.httpPrefix+'://'+self.server.domainFull+ \ self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/rmpost')[0] self.path.split('/rmpost')[0]
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
removePostConfirmParams=self.rfile.read(length).decode('utf-8') removePostConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in removePostConfirmParams: if '&submitYes=' in removePostConfirmParams:
removePostConfirmParams= \ removePostConfirmParams= \
@ -5255,7 +5255,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.httpPrefix+'://'+self.server.domainFull+ \ self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/followconfirm')[0] self.path.split('/followconfirm')[0]
followerNickname=getNicknameFromActor(originPathStr) followerNickname=getNicknameFromActor(originPathStr)
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
followConfirmParams=self.rfile.read(length).decode('utf-8') followConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitView=' in followConfirmParams: if '&submitView=' in followConfirmParams:
followingActor= \ followingActor= \
@ -5308,7 +5308,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.httpPrefix+'://'+self.server.domainFull+ \ self.server.httpPrefix+'://'+self.server.domainFull+ \
self.path.split('/unfollowconfirm')[0] self.path.split('/unfollowconfirm')[0]
followerNickname=getNicknameFromActor(originPathStr) followerNickname=getNicknameFromActor(originPathStr)
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
followConfirmParams=self.rfile.read(length).decode('utf-8') followConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in followConfirmParams: if '&submitYes=' in followConfirmParams:
followingActor= \ followingActor= \
@ -5329,9 +5329,9 @@ class PubServer(BaseHTTPRequestHandler):
self.server.httpPrefix+'://'+ \ self.server.httpPrefix+'://'+ \
self.server.domainFull+ \ self.server.domainFull+ \
'/users/'+followerNickname '/users/'+followerNickname
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
followId=followActor+'/statuses/'+str(statusNumber) followId=followActor+'/statuses/'+str(statusNumber)
unfollowJson = { unfollowJson={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': followId+'/undo', 'id': followId+'/undo',
'type': 'Undo', 'type': 'Undo',
@ -5363,7 +5363,7 @@ class PubServer(BaseHTTPRequestHandler):
self._redirect_headers(originPathStr,cookie) self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False self.server.POSTbusy=False
return return
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
blockConfirmParams=self.rfile.read(length).decode('utf-8') blockConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in blockConfirmParams: if '&submitYes=' in blockConfirmParams:
blockingActor= \ blockingActor= \
@ -5410,7 +5410,7 @@ class PubServer(BaseHTTPRequestHandler):
self._redirect_headers(originPathStr,cookie) self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False self.server.POSTbusy=False
return return
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
blockConfirmParams=self.rfile.read(length).decode('utf-8') blockConfirmParams=self.rfile.read(length).decode('utf-8')
if '&submitYes=' in blockConfirmParams: if '&submitYes=' in blockConfirmParams:
blockingActor= \ blockingActor= \
@ -5462,7 +5462,7 @@ class PubServer(BaseHTTPRequestHandler):
self._redirect_headers(originPathStr,cookie) self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False self.server.POSTbusy=False
return return
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
optionsConfirmParams= \ optionsConfirmParams= \
self.rfile.read(length).decode('utf-8').replace('%3A',':').replace('%2F','/') self.rfile.read(length).decode('utf-8').replace('%3A',':').replace('%2F','/')
# page number to return to # page number to return to
@ -5671,7 +5671,7 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,17) self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,17)
# read the message and convert it into a python dictionary # read the message and convert it into a python dictionary
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
if self.server.debug: if self.server.debug:
print('DEBUG: content-length: '+str(length)) print('DEBUG: content-length: '+str(length))
if not self.headers['Content-type'].startswith('image/') and \ if not self.headers['Content-type'].startswith('image/') and \
@ -5740,7 +5740,7 @@ class PubServer(BaseHTTPRequestHandler):
print("POST is not json: "+self.headers['Content-type']) print("POST is not json: "+self.headers['Content-type'])
if self.server.debug: if self.server.debug:
print(str(self.headers)) print(str(self.headers))
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
if length<self.server.maxPostLength: if length<self.server.maxPostLength:
unknownPost=self.rfile.read(length).decode('utf-8') unknownPost=self.rfile.read(length).decode('utf-8')
print(str(unknownPost)) print(str(unknownPost))
@ -5758,11 +5758,11 @@ class PubServer(BaseHTTPRequestHandler):
if self.path == '/sharedInbox' or self.path == '/inbox': if self.path == '/sharedInbox' or self.path == '/inbox':
length=0 length=0
if self.headers.get('Content-length'): if self.headers.get('Content-length'):
length = int(self.headers['Content-length']) length=int(self.headers['Content-length'])
elif self.headers.get('Content-Length'): elif self.headers.get('Content-Length'):
length = int(self.headers['Content-Length']) length=int(self.headers['Content-Length'])
elif self.headers.get('content-length'): elif self.headers.get('content-length'):
length = int(self.headers['content-length']) length=int(self.headers['content-length'])
if length>10240: if length>10240:
print('WARN: post to shared inbox is too long '+str(length)+' bytes') print('WARN: post to shared inbox is too long '+str(length)+' bytes')
self._400() self._400()
@ -5897,7 +5897,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy=False self.server.POSTbusy=False
class PubServerUnitTest(PubServer): class PubServerUnitTest(PubServer):
protocol_version = 'HTTP/1.0' protocol_version='HTTP/1.0'
def runPostsQueue(baseDir: str,sendThreads: [],debug: bool) -> None: def runPostsQueue(baseDir: str,sendThreads: [],debug: bool) -> None:
"""Manages the threads used to send posts """Manages the threads used to send posts
@ -5952,7 +5952,7 @@ def loadTokens(baseDir: str,tokensDict: {},tokensLookup: {}) -> None:
token=None token=None
try: try:
with open(tokenFilename, 'r') as fp: with open(tokenFilename, 'r') as fp:
token = fp.read() token=fp.read()
except Exception as e: except Exception as e:
print('WARN: Unable to read token for '+nickname+' '+str(e)) print('WARN: Unable to read token for '+nickname+' '+str(e))
if not token: if not token:
@ -5984,14 +5984,14 @@ def runDaemon(blogsInstance: bool,mediaInstance: bool, \
return return
if unitTest: if unitTest:
serverAddress = (domain, proxyPort) serverAddress=(domain, proxyPort)
pubHandler = partial(PubServerUnitTest) pubHandler=partial(PubServerUnitTest)
else: else:
serverAddress = ('', proxyPort) serverAddress=('', proxyPort)
pubHandler = partial(PubServer) pubHandler=partial(PubServer)
try: try:
httpd = ThreadingHTTPServer(serverAddress, pubHandler) httpd=ThreadingHTTPServer(serverAddress, pubHandler)
except Exception as e: except Exception as e:
if e.errno==98: if e.errno==98:
print('ERROR: HTTP server address is already in use. '+str(serverAddress)) print('ERROR: HTTP server address is already in use. '+str(serverAddress))
@ -6068,7 +6068,7 @@ def runDaemon(blogsInstance: bool,mediaInstance: bool, \
httpd.personCache={} httpd.personCache={}
httpd.cachedWebfingers={} httpd.cachedWebfingers={}
httpd.useTor=useTor httpd.useTor=useTor
httpd.session = None httpd.session=None
httpd.sessionLastUpdate=0 httpd.sessionLastUpdate=0
httpd.lastGET=0 httpd.lastGET=0
httpd.lastPOST=0 httpd.lastPOST=0

View File

@ -1,10 +1,10 @@
__filename__ = "delete.py" __filename__="delete.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import json import json
@ -46,10 +46,10 @@ def createDelete(session,baseDir: str,federationList: [], \
if ':' not in domain: if ':' not in domain:
fullDomain=domain+':'+str(port) fullDomain=domain+':'+str(port)
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
newDeleteId= \ newDeleteId= \
httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber
newDelete = { newDelete={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber, 'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber,
@ -101,10 +101,10 @@ def sendDeleteViaServer(baseDir: str,session, \
if ':' not in fromDomain: if ':' not in fromDomain:
fromDomainFull=fromDomain+':'+str(fromPort) fromDomainFull=fromDomain+':'+str(fromPort)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl='https://www.w3.org/ns/activitystreams#Public'
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers' ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
newDeleteJson = { newDeleteJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
'cc': [ccUrl], 'cc': [ccUrl],
@ -116,8 +116,9 @@ def sendDeleteViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest= \
fromDomain,projectVersion) webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -126,7 +127,7 @@ def sendDeleteViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -142,10 +143,12 @@ def sendDeleteViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newDeleteJson,[],inboxUrl,headers,"inbox:write") postJson(session,newDeleteJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:
@ -171,8 +174,8 @@ def deletePublic(session,baseDir: str,federationList: [], \
if ':' not in domain: if ':' not in domain:
fromDomain=domain+':'+str(port) fromDomain=domain+':'+str(port)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl='https://www.w3.org/ns/activitystreams#Public'
ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers' ccUrl=httpPrefix+'://'+fromDomain+'/users/'+nickname+'/followers'
return createDelete(session,baseDir,federationList, \ return createDelete(session,baseDir,federationList, \
nickname,domain,port, \ nickname,domain,port, \
toUrl,ccUrl,httpPrefix, \ toUrl,ccUrl,httpPrefix, \
@ -197,7 +200,7 @@ def deletePostPub(session,baseDir: str,federationList: [], \
if ':' not in deletedDomain: if ':' not in deletedDomain:
deletedDomain=deletedDomain+':'+str(deletePort) deletedDomain=deletedDomain+':'+str(deletePort)
objectUrl = \ objectUrl= \
deleteHttpsPrefix + '://'+deletedDomain+'/users/'+ \ deleteHttpsPrefix + '://'+deletedDomain+'/users/'+ \
deleteNickname+'/statuses/'+str(deleteStatusNumber) deleteNickname+'/statuses/'+str(deleteStatusNumber)

View File

@ -1,10 +1,10 @@
__filename__ = "donate.py" __filename__="donate.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json

View File

@ -1,10 +1,10 @@
__filename__ = "epicyon.py" __filename__="epicyon.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
from person import createPerson from person import createPerson
from person import createGroup from person import createGroup
@ -93,7 +93,7 @@ def str2bool(v):
else: else:
raise argparse.ArgumentTypeError('Boolean value expected.') raise argparse.ArgumentTypeError('Boolean value expected.')
parser = argparse.ArgumentParser(description='ActivityPub Server') parser=argparse.ArgumentParser(description='ActivityPub Server')
parser.add_argument('-n','--nickname', dest='nickname', type=str,default=None, \ parser.add_argument('-n','--nickname', dest='nickname', type=str,default=None, \
help='Nickname of the account to use') help='Nickname of the account to use')
parser.add_argument('--fol','--follow', dest='follow', type=str,default=None, \ parser.add_argument('--fol','--follow', dest='follow', type=str,default=None, \
@ -311,7 +311,7 @@ parser.add_argument('--maxregistrations', dest='maxRegistrations', type=int,defa
parser.add_argument("--resetregistrations", type=str2bool, nargs='?', \ parser.add_argument("--resetregistrations", type=str2bool, nargs='?', \
const=True, default=False, \ const=True, default=False, \
help="Reset the number of remaining registrations") help="Reset the number of remaining registrations")
args = parser.parse_args() args=parser.parse_args()
debug=False debug=False
if args.debug: if args.debug:
@ -365,9 +365,11 @@ if args.postsraw:
sys.exit() sys.exit()
if args.json: if args.json:
session = createSession(False) session=createSession(False)
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
testJson = getJson(session,args.json,asHeader,None,__version__,httpPrefix,None) 'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
testJson=getJson(session,args.json,asHeader,None,__version__,httpPrefix,None)
pprint(testJson) pprint(testJson)
sys.exit() sys.exit()
@ -533,7 +535,7 @@ if args.approve:
if '@' not in args.approve: if '@' not in args.approve:
print('syntax: --approve nick@domain') print('syntax: --approve nick@domain')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
sendThreads=[] sendThreads=[]
postLog=[] postLog=[]
cachedWebfingers={} cachedWebfingers={}
@ -557,7 +559,7 @@ if args.deny:
if '@' not in args.deny: if '@' not in args.deny:
print('syntax: --deny nick@domain') print('syntax: --deny nick@domain')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
sendThreads=[] sendThreads=[]
postLog=[] postLog=[]
cachedWebfingers={} cachedWebfingers={}
@ -599,7 +601,7 @@ if args.message:
print('Specify a password with the --password option') print('Specify a password with the --password option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
if not args.sendto: if not args.sendto:
print('Specify an account to sent to: --sendto [nickname@domain]') print('Specify an account to sent to: --sendto [nickname@domain]')
sys.exit() sys.exit()
@ -634,8 +636,8 @@ if args.message:
clientToServer=args.client clientToServer=args.client
attachedImageDescription=args.imageDescription attachedImageDescription=args.imageDescription
useBlurhash=args.blurhash useBlurhash=args.blurhash
sendThreads = [] sendThreads=[]
postLog = [] postLog=[]
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
subject=args.subject subject=args.subject
@ -671,7 +673,7 @@ if args.announce:
print('Specify a password with the --password option') print('Specify a password with the --password option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending announce/repeat of '+args.announce) print('Sending announce/repeat of '+args.announce)
@ -715,7 +717,7 @@ if args.itemName:
print('Specify a duration to share the object with the --duration option') print('Specify a duration to share the object with the --duration option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending shared item: '+args.itemName) print('Sending shared item: '+args.itemName)
@ -747,7 +749,7 @@ if args.undoItemName:
print('Specify a nickname with the --nickname option') print('Specify a nickname with the --nickname option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending undo of shared item: '+args.undoItemName) print('Sending undo of shared item: '+args.undoItemName)
@ -773,7 +775,7 @@ if args.like:
print('Specify a password with the --password option') print('Specify a password with the --password option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending like of '+args.like) print('Sending like of '+args.like)
@ -798,7 +800,7 @@ if args.undolike:
print('Specify a password with the --password option') print('Specify a password with the --password option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending undo like of '+args.undolike) print('Sending undo like of '+args.undolike)
@ -823,7 +825,7 @@ if args.delete:
print('Specify a password with the --password option') print('Specify a password with the --password option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending delete request of '+args.delete) print('Sending delete request of '+args.delete)
@ -857,7 +859,7 @@ if args.follow:
sys.exit() sys.exit()
followDomain,followPort=getDomainFromActor(args.follow) followDomain,followPort=getDomainFromActor(args.follow)
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
followHttpPrefix=httpPrefix followHttpPrefix=httpPrefix
@ -895,7 +897,7 @@ if args.unfollow:
sys.exit() sys.exit()
followDomain,followPort=getDomainFromActor(args.unfollow) followDomain,followPort=getDomainFromActor(args.unfollow)
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
followHttpPrefix=httpPrefix followHttpPrefix=httpPrefix
@ -994,24 +996,34 @@ if args.actor:
else: else:
sys.exit() sys.exit()
asHeader = {'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
}
if not personUrl: if not personUrl:
personUrl = getUserUrl(wfRequest) personUrl=getUserUrl(wfRequest)
if nickname==domain: if nickname==domain:
personUrl=personUrl.replace('/users/','/actor/').replace('/channel/','/actor/').replace('/profile/','/actor/') personUrl=personUrl.replace('/users/','/actor/').replace('/channel/','/actor/').replace('/profile/','/actor/')
if not personUrl: if not personUrl:
# try single user instance # try single user instance
personUrl=httpPrefix+'://'+domain personUrl=httpPrefix+'://'+domain
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
if '/channel/' in personUrl: if '/channel/' in personUrl:
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
personJson = getJson(session,personUrl,asHeader,None,__version__,httpPrefix,None) personJson= \
getJson(session,personUrl,asHeader,None,__version__,httpPrefix,None)
if personJson: if personJson:
pprint(personJson) pprint(personJson)
else: else:
asHeader = {'Accept': 'application/jrd+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
personJson = getJson(session,personUrl,asHeader,None,__version__,httpPrefix,None) 'Accept': 'application/jrd+json; profile="https://www.w3.org/ns/activitystreams"'
}
personJson= \
getJson(session,personUrl,asHeader,None,__version__,httpPrefix,None)
if personJson: if personJson:
pprint(personJson) pprint(personJson)
else: else:
@ -1237,7 +1249,7 @@ if args.skill:
print('Skill level should be a percentage in the range 0-100') print('Skill level should be a percentage in the range 0-100')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending '+args.skill+' skill level '+str(args.skillLevelPercent)+' for '+nickname) print('Sending '+args.skill+' skill level '+str(args.skillLevelPercent)+' for '+nickname)
@ -1263,7 +1275,7 @@ if args.availability:
print('Specify a password with the --password option') print('Specify a password with the --password option')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending availability status of '+nickname+' as '+args.availability) print('Sending availability status of '+nickname+' as '+args.availability)
@ -1308,7 +1320,7 @@ if args.block:
print(args.block+' does not look like an actor url') print(args.block+' does not look like an actor url')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending block of '+args.block) print('Sending block of '+args.block)
@ -1344,7 +1356,7 @@ if args.delegate:
delegatedNickname=args.delegate.split('@')[0] delegatedNickname=args.delegate.split('@')[0]
args.delegate=blockedActor args.delegate=blockedActor
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending delegation for '+args.delegate+' with role '+args.role+' in project '+args.project) print('Sending delegation for '+args.delegate+' with role '+args.role+' in project '+args.project)
@ -1378,7 +1390,7 @@ if args.undelegate:
delegatedNickname=args.undelegate.split('@')[0] delegatedNickname=args.undelegate.split('@')[0]
args.undelegate=blockedActor args.undelegate=blockedActor
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending delegation removal for '+args.undelegate+' from role '+args.role+' in project '+args.project) print('Sending delegation removal for '+args.undelegate+' from role '+args.role+' in project '+args.project)
@ -1414,7 +1426,7 @@ if args.unblock:
print(args.unblock+' does not look like an actor url') print(args.unblock+' does not look like an actor url')
sys.exit() sys.exit()
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
print('Sending undo block of '+args.unblock) print('Sending undo block of '+args.unblock)

View File

@ -1,10 +1,10 @@
__filename__ = "filters.py" __filename__="filters.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os

View File

@ -1,10 +1,10 @@
__filename__ = "follow.py" __filename__="follow.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import time import time
@ -56,7 +56,7 @@ def removeFromFollowBase(baseDir: str, \
return return
if acceptOrDenyHandle not in open(approveFollowsFilename).read(): if acceptOrDenyHandle not in open(approveFollowsFilename).read():
return return
approvefilenew = open(approveFollowsFilename+'.new', 'w+') approvefilenew=open(approveFollowsFilename+'.new', 'w+')
with open(approveFollowsFilename, 'r') as approvefile: with open(approveFollowsFilename, 'r') as approvefile:
for approveHandle in approvefile: for approveHandle in approvefile:
if not approveHandle.startswith(acceptOrDenyHandle): if not approveHandle.startswith(acceptOrDenyHandle):
@ -137,8 +137,8 @@ def getFollowersOfPerson(baseDir: str, \
return followers return followers
for subdir, dirs, files in os.walk(baseDir+'/accounts'): for subdir, dirs, files in os.walk(baseDir+'/accounts'):
for account in dirs: for account in dirs:
filename = os.path.join(subdir, account)+'/'+followFile filename=os.path.join(subdir, account)+'/'+followFile
if account == handle or account.startswith('inbox@'): if account==handle or account.startswith('inbox@'):
continue continue
if not os.path.isfile(filename): if not os.path.isfile(filename):
continue continue
@ -195,7 +195,7 @@ def unfollowPerson(baseDir: str,nickname: str, domain: str, \
print('DEBUG: handle to unfollow '+handleToUnfollow+' is not in '+filename) print('DEBUG: handle to unfollow '+handleToUnfollow+' is not in '+filename)
return return
with open(filename, "r") as f: with open(filename, "r") as f:
lines = f.readlines() lines=f.readlines()
with open(filename, "w") as f: with open(filename, "w") as f:
for line in lines: for line in lines:
if line.strip("\n") != handleToUnfollow: if line.strip("\n") != handleToUnfollow:
@ -254,9 +254,9 @@ def getNoOfFollows(baseDir: str,nickname: str,domain: str, \
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
ctr = 0 ctr=0
with open(filename, "r") as f: with open(filename, "r") as f:
lines = f.readlines() lines=f.readlines()
for line in lines: for line in lines:
if '#' not in line: if '#' not in line:
if '@' in line and '.' in line and not line.startswith('http'): if '@' in line and '.' in line and not line.startswith('http'):
@ -315,25 +315,27 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str, \
domain=domain+':'+str(port) domain=domain+':'+str(port)
if headerOnly: if headerOnly:
following = { following={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page=1', 'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page=1',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile, 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'totalItems': getNoOfFollows(baseDir,nickname,domain,authenticated), 'totalItems': getNoOfFollows(baseDir,nickname,domain,authenticated),
'type': 'OrderedCollection'} 'type': 'OrderedCollection'
}
return following return following
if not pageNumber: if not pageNumber:
pageNumber=1 pageNumber=1
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': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(pageNumber), 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(pageNumber),
'orderedItems': [], 'orderedItems': [],
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile, 'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile,
'totalItems': 0, 'totalItems': 0,
'type': 'OrderedCollectionPage'} 'type': 'OrderedCollectionPage'
}
handleDomain=domain handleDomain=domain
if ':' in handleDomain: if ':' in handleDomain:
@ -346,15 +348,15 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str, \
pageCtr=0 pageCtr=0
totalCtr=0 totalCtr=0
with open(filename, "r") as f: with open(filename, "r") as f:
lines = f.readlines() lines=f.readlines()
for line in lines: for line in lines:
if '#' not in line: if '#' not in line:
if '@' in line and not line.startswith('http'): if '@' in line and not line.startswith('http'):
pageCtr += 1 pageCtr += 1
totalCtr += 1 totalCtr += 1
if currPage==pageNumber: if currPage==pageNumber:
url = httpPrefix + '://' + line.lower().replace('\n','').split('@')[1] + \ url=httpPrefix+'://' + line.lower().replace('\n','').split('@')[1] + \
'/users/' + line.lower().replace('\n','').split('@')[0] '/users/'+line.lower().replace('\n','').split('@')[0]
following['orderedItems'].append(url) following['orderedItems'].append(url)
elif (line.startswith('http') or line.startswith('dat')) and '/users/' in line: elif (line.startswith('http') or line.startswith('dat')) and '/users/' in line:
pageCtr += 1 pageCtr += 1
@ -411,7 +413,7 @@ def noOfFollowRequests(baseDir: str, \
return 0 return 0
ctr=0 ctr=0
with open(approveFollowsFilename, "r") as f: with open(approveFollowsFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
if followType != "onion": if followType != "onion":
return len(lines) return len(lines)
for l in lines: for l in lines:
@ -590,7 +592,7 @@ def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \
if approveHandle not in open(followersFilename).read(): if approveHandle not in open(followersFilename).read():
try: try:
with open(followersFilename, 'r+') as followersFile: with open(followersFilename, 'r+') as followersFile:
content = followersFile.read() content=followersFile.read()
followersFile.seek(0, 0) followersFile.seek(0, 0)
followersFile.write(approveHandle+'\n'+content) followersFile.write(approveHandle+'\n'+content)
except Exception as e: except Exception as e:
@ -746,7 +748,7 @@ def sendFollowRequest(session,baseDir: str, \
if ':' not in followDomain: if ':' not in followDomain:
requestDomain=followDomain+':'+str(followPort) requestDomain=followDomain+':'+str(followPort)
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
if followNickname: if followNickname:
followedId=followHttpPrefix+'://'+requestDomain+'/users/'+followNickname followedId=followHttpPrefix+'://'+requestDomain+'/users/'+followNickname
@ -758,7 +760,7 @@ def sendFollowRequest(session,baseDir: str, \
singleUserNickname='dev' singleUserNickname='dev'
followHandle=singleUserNickname+'@'+requestDomain followHandle=singleUserNickname+'@'+requestDomain
newFollowJson = { newFollowJson={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': followActor+'/statuses/'+str(statusNumber), 'id': followActor+'/statuses/'+str(statusNumber),
'type': 'Follow', 'type': 'Follow',
@ -813,8 +815,8 @@ def sendFollowRequestViaServer(baseDir: str,session, \
followActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname followActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
followedId=httpPrefix+'://'+followDomainFull+'/users/'+followNickname followedId=httpPrefix+'://'+followDomainFull+'/users/'+followNickname
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
newFollowJson = { newFollowJson={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': followActor+'/statuses/'+str(statusNumber), 'id': followActor+'/statuses/'+str(statusNumber),
'type': 'Follow', 'type': 'Follow',
@ -825,8 +827,9 @@ def sendFollowRequestViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest= \
fromDomain,projectVersion) webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -835,7 +838,7 @@ def sendFollowRequestViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -851,10 +854,12 @@ def sendFollowRequestViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newFollowJson,[],inboxUrl,headers,"inbox:write") postJson(session,newFollowJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:
@ -892,9 +897,9 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
followActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname followActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
followedId=httpPrefix+'://'+followDomainFull+'/users/'+followNickname followedId=httpPrefix+'://'+followDomainFull+'/users/'+followNickname
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
unfollowJson = { unfollowJson={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': followActor+'/statuses/'+str(statusNumber)+'/undo', 'id': followActor+'/statuses/'+str(statusNumber)+'/undo',
'type': 'Undo', 'type': 'Undo',
@ -910,8 +915,9 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest= \
fromDomain,projectVersion) webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -920,7 +926,7 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -936,10 +942,12 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,unfollowJson,[],inboxUrl,headers,"inbox:write") postJson(session,unfollowJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:
@ -980,7 +988,7 @@ def getFollowersOfActor(baseDir :str,actor :str,debug: bool) -> {}:
for subdir, dirs, files in os.walk(baseDir+'/accounts'): for subdir, dirs, files in os.walk(baseDir+'/accounts'):
for account in dirs: for account in dirs:
if '@' in account and not account.startswith('inbox@'): if '@' in account and not account.startswith('inbox@'):
followingFilename = os.path.join(subdir, account)+'/following.txt' followingFilename=os.path.join(subdir, account)+'/following.txt'
if debug: if debug:
print('DEBUG: examining follows of '+account) print('DEBUG: examining follows of '+account)
print(followingFilename) print(followingFilename)

View File

@ -1,10 +1,10 @@
__filename__ = "happening.py" __filename__="happening.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import time import time
@ -380,7 +380,7 @@ def removeCalendarEvent(baseDir: str,nickname: str,domain: str, \
return return
lines=None lines=None
with open(calendarFilename, "r") as f: with open(calendarFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
if not lines: if not lines:
return return
with open(calendarFilename, "w+") as f: with open(calendarFilename, "w+") as f:

View File

@ -1,11 +1,11 @@
__filename__ = "posts.py" __filename__="posts.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__credits__ = ['lamia'] __credits__=['lamia']
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
# see https://tools.ietf.org/html/draft-cavage-http-signatures-06 # see https://tools.ietf.org/html/draft-cavage-http-signatures-06
@ -63,27 +63,27 @@ def signPostHeaders(dateStr: str,privateKeyPem: str, \
# '(request-target)': f'post {path}', # '(request-target)': f'post {path}',
#}) #})
# build a digest for signing # build a digest for signing
signedHeaderKeys = headers.keys() signedHeaderKeys=headers.keys()
signedHeaderText = '' signedHeaderText=''
for headerKey in signedHeaderKeys: for headerKey in signedHeaderKeys:
signedHeaderText += f'{headerKey}: {headers[headerKey]}\n' signedHeaderText += f'{headerKey}: {headers[headerKey]}\n'
#print(f'*********************signing: headerKey: {headerKey}: {headers[headerKey]}') #print(f'*********************signing: headerKey: {headerKey}: {headers[headerKey]}')
signedHeaderText = signedHeaderText.strip() signedHeaderText=signedHeaderText.strip()
#print('******************************Send: signedHeaderText: '+signedHeaderText) #print('******************************Send: signedHeaderText: '+signedHeaderText)
headerDigest = SHA256.new(signedHeaderText.encode('ascii')) headerDigest=SHA256.new(signedHeaderText.encode('ascii'))
# Sign the digest # Sign the digest
rawSignature = pkcs1_15.new(privateKeyPem).sign(headerDigest) rawSignature=pkcs1_15.new(privateKeyPem).sign(headerDigest)
signature = base64.b64encode(rawSignature).decode('ascii') signature=base64.b64encode(rawSignature).decode('ascii')
# Put it into a valid HTTP signature format # Put it into a valid HTTP signature format
signatureDict = { signatureDict={
'keyId': keyID, 'keyId': keyID,
'algorithm': 'rsa-sha256', 'algorithm': 'rsa-sha256',
'headers': ' '.join(signedHeaderKeys), 'headers': ' '.join(signedHeaderKeys),
'signature': signature 'signature': signature
} }
signatureHeader = ','.join( signatureHeader=','.join(
[f'{k}="{v}"' for k, v in signatureDict.items()]) [f'{k}="{v}"' for k, v in signatureDict.items()])
return signatureHeader return signatureHeader
@ -104,8 +104,10 @@ def createSignedHeader(privateKeyPem: str,nickname: str, \
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
if not withDigest: if not withDigest:
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr} headers={
signatureHeader = \ '(request-target)': f'post {path}','host': headerDomain,'date': dateStr
}
signatureHeader= \
signPostHeaders(dateStr,privateKeyPem,nickname, \ signPostHeaders(dateStr,privateKeyPem,nickname, \
domain,port,toDomain,toPort, \ domain,port,toDomain,toPort, \
path,httpPrefix,None) path,httpPrefix,None)
@ -119,13 +121,20 @@ def createSignedHeader(privateKeyPem: str,nickname: str, \
#print('***************************Send Content-type: '+contentType) #print('***************************Send Content-type: '+contentType)
#print('***************************Send Content-Length: '+str(len(messageBodyJsonStr))) #print('***************************Send Content-Length: '+str(len(messageBodyJsonStr)))
#print('***************************Send messageBodyJsonStr: '+messageBodyJsonStr) #print('***************************Send messageBodyJsonStr: '+messageBodyJsonStr)
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-length': str(contentLength),'content-type': contentType} headers={
signatureHeader = \ '(request-target)': f'post {path}',
'host': headerDomain,
'date': dateStr,
'digest': f'SHA-256={bodyDigest}',
'content-length': str(contentLength),
'content-type': contentType
}
signatureHeader= \
signPostHeaders(dateStr,privateKeyPem,nickname, \ signPostHeaders(dateStr,privateKeyPem,nickname, \
domain,port, \ domain,port, \
toDomain,toPort, \ toDomain,toPort, \
path,httpPrefix,messageBodyJsonStr) path,httpPrefix,messageBodyJsonStr)
headers['signature'] = signatureHeader headers['signature']=signatureHeader
return headers return headers
def verifyRecentSignature(signedDateStr: str) -> bool: def verifyRecentSignature(signedDateStr: str) -> bool:
@ -167,10 +176,10 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
if debug: if debug:
print('DEBUG: verifyPostHeaders '+method) print('DEBUG: verifyPostHeaders '+method)
publicKeyPem = RSA.import_key(publicKeyPem) publicKeyPem=RSA.import_key(publicKeyPem)
# Build a dictionary of the signature values # Build a dictionary of the signature values
signatureHeader = headers['signature'] signatureHeader=headers['signature']
signatureDict = { signatureDict={
k: v[1:-1] k: v[1:-1]
for k, v in [i.split('=', 1) for i in signatureHeader.split(',')] for k, v in [i.split('=', 1) for i in signatureHeader.split(',')]
} }
@ -179,7 +188,7 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
# Unpack the signed headers and set values based on current headers and # Unpack the signed headers and set values based on current headers and
# body (if a digest was included) # body (if a digest was included)
signedHeaderList = [] signedHeaderList=[]
for signedHeader in signatureDict['headers'].split(' '): for signedHeader in signatureDict['headers'].split(' '):
if debug: if debug:
print('DEBUG: verifyPostHeaders signedHeader='+signedHeader) print('DEBUG: verifyPostHeaders signedHeader='+signedHeader)
@ -236,12 +245,12 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
if debug: if debug:
print('DEBUG: signedHeaderList: '+str(signedHeaderList)) print('DEBUG: signedHeaderList: '+str(signedHeaderList))
# Now we have our header data digest # Now we have our header data digest
signedHeaderText = '\n'.join(signedHeaderList) signedHeaderText='\n'.join(signedHeaderList)
#print('***********************Verify: signedHeaderText: '+signedHeaderText) #print('***********************Verify: signedHeaderText: '+signedHeaderText)
headerDigest = SHA256.new(signedHeaderText.encode('ascii')) headerDigest=SHA256.new(signedHeaderText.encode('ascii'))
# Get the signature, verify with public key, return result # Get the signature, verify with public key, return result
signature = base64.b64decode(signatureDict['signature']) signature=base64.b64decode(signatureDict['signature'])
try: try:
pkcs1_15.new(publicKeyPem).verify(headerDigest, signature) pkcs1_15.new(publicKeyPem).verify(headerDigest, signature)

View File

@ -1,10 +1,10 @@
__filename__ = "inbox.py" __filename__="inbox.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import os import os
@ -101,7 +101,7 @@ def storeHashTags(baseDir: str,nickname: str,postJsonObject: {}) -> None:
if postUrl not in open(tagsFilename).read(): if postUrl not in open(tagsFilename).read():
try: try:
with open(tagsFilename, 'r+') as tagsFile: with open(tagsFilename, 'r+') as tagsFile:
content = tagsFile.read() content=tagsFile.read()
tagsFile.seek(0, 0) tagsFile.seek(0, 0)
tagsFile.write(tagline+content) tagsFile.write(tagline+content)
except Exception as e: except Exception as e:
@ -142,7 +142,7 @@ def validInbox(baseDir: str,nickname: str,domain: str) -> bool:
return True return True
for subdir, dirs, files in os.walk(inboxDir): for subdir, dirs, files in os.walk(inboxDir):
for f in files: for f in files:
filename = os.path.join(subdir, f) filename=os.path.join(subdir, f)
if not os.path.isfile(filename): if not os.path.isfile(filename):
print('filename: '+filename) print('filename: '+filename)
return False return False
@ -164,7 +164,7 @@ def validInboxFilenames(baseDir: str,nickname: str,domain: str, \
expectedStr=expectedDomain+':'+str(expectedPort) expectedStr=expectedDomain+':'+str(expectedPort)
for subdir, dirs, files in os.walk(inboxDir): for subdir, dirs, files in os.walk(inboxDir):
for f in files: for f in files:
filename = os.path.join(subdir, f) filename=os.path.join(subdir, f)
if not os.path.isfile(filename): if not os.path.isfile(filename):
print('filename: '+filename) print('filename: '+filename)
return False return False
@ -185,7 +185,7 @@ def getPersonPubKey(baseDir: str,session,personUrl: str, \
if debug: if debug:
print('DEBUG: Obtaining public key for shared inbox') print('DEBUG: Obtaining public key for shared inbox')
personUrl=personUrl.replace('/users/inbox','/inbox') personUrl=personUrl.replace('/users/inbox','/inbox')
personJson = getPersonFromCache(baseDir,personUrl,personCache) personJson=getPersonFromCache(baseDir,personUrl,personCache)
if not personJson: if not personJson:
if debug: if debug:
print('DEBUG: Obtaining public key for '+personUrl) print('DEBUG: Obtaining public key for '+personUrl)
@ -193,10 +193,10 @@ def getPersonPubKey(baseDir: str,session,personUrl: str, \
if onionDomain: if onionDomain:
if '.onion/' in personUrl: if '.onion/' in personUrl:
personDomain=onionDomain personDomain=onionDomain
asHeader = { asHeader={
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"' 'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
} }
personJson = \ personJson= \
getJson(session,personUrl,asHeader,None,projectVersion, \ getJson(session,personUrl,asHeader,None,projectVersion, \
httpPrefix,personDomain) httpPrefix,personDomain)
if not personJson: if not personJson:
@ -258,7 +258,7 @@ def inboxPermittedMessage(domain: str,messageJson: {},federationList: []) -> boo
def validPublishedDate(published: str) -> bool: def validPublishedDate(published: str) -> bool:
currTime=datetime.datetime.utcnow() currTime=datetime.datetime.utcnow()
pubDate=datetime.datetime.strptime(published,"%Y-%m-%dT%H:%M:%SZ") pubDate=datetime.datetime.strptime(published,"%Y-%m-%dT%H:%M:%SZ")
daysSincePublished = (currTime - pubTime).days daysSincePublished=(currTime - pubTime).days
if daysSincePublished>30: if daysSincePublished>30:
return False return False
return True return True
@ -340,7 +340,7 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str, \
postId=postJsonObject['id'].replace('/activity','').replace('/undo','') postId=postJsonObject['id'].replace('/activity','').replace('/undo','')
published=currTime.strftime("%Y-%m-%dT%H:%M:%SZ") published=currTime.strftime("%Y-%m-%dT%H:%M:%SZ")
if not postId: if not postId:
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
if actor: if actor:
postId=actor+'/statuses/'+statusNumber postId=actor+'/statuses/'+statusNumber
else: else:
@ -372,7 +372,7 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str, \
timeDiffStr='0'+timeDiffStr timeDiffStr='0'+timeDiffStr
print('DIGEST|'+timeDiffStr+'|'+filename) print('DIGEST|'+timeDiffStr+'|'+filename)
newQueueItem = { newQueueItem={
'originalId': originalPostId, 'originalId': originalPostId,
'id': postId, 'id': postId,
'actor': actor, 'actor': actor,
@ -531,7 +531,7 @@ def inboxPostRecipients(baseDir :str,postJsonObject :{}, \
domain=domain+':'+str(port) domain=domain+':'+str(port)
domainMatch='/'+domain+'/users/' domainMatch='/'+domain+'/users/'
actor = postJsonObject['actor'] actor=postJsonObject['actor']
# first get any specific people which the post is addressed to # first get any specific people which the post is addressed to
followerRecipients=False followerRecipients=False
@ -1417,7 +1417,7 @@ def populateReplies(baseDir :str,httpPrefix :str,domain :str, \
postRepliesFilename=postFilename.replace('.json','.replies') postRepliesFilename=postFilename.replace('.json','.replies')
messageId=messageJson['id'].replace('/activity','').replace('/undo','') messageId=messageJson['id'].replace('/activity','').replace('/undo','')
if os.path.isfile(postRepliesFilename): if os.path.isfile(postRepliesFilename):
numLines = sum(1 for line in open(postRepliesFilename)) numLines=sum(1 for line in open(postRepliesFilename))
if numLines>maxReplies: if numLines>maxReplies:
return False return False
if messageId not in open(postRepliesFilename).read(): if messageId not in open(postRepliesFilename).read():
@ -1727,7 +1727,7 @@ def inboxUpdateIndex(boxname: str,baseDir: str,handle: str,destinationFilename:
if os.path.isfile(indexFilename): if os.path.isfile(indexFilename):
try: try:
with open(indexFilename, 'r+') as indexFile: with open(indexFilename, 'r+') as indexFile:
content = indexFile.read() content=indexFile.read()
indexFile.seek(0, 0) indexFile.seek(0, 0)
indexFile.write(destinationFilename+'\n'+content) indexFile.write(destinationFilename+'\n'+content)
return True return True

57
like.py
View File

@ -1,10 +1,10 @@
__filename__ = "like.py" __filename__="like.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import json import json
@ -135,7 +135,7 @@ def updateLikesCollection(recentPostsCache: {}, \
if not postJsonObject['object'].get('likes'): if not postJsonObject['object'].get('likes'):
if debug: if debug:
print('DEBUG: Adding initial likes to '+objectUrl) print('DEBUG: Adding initial likes to '+objectUrl)
likesJson = { likesJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'id': objectUrl, 'id': objectUrl,
'type': 'Collection', 'type': 'Collection',
@ -193,7 +193,7 @@ def like(recentPostsCache: {}, \
if '/statuses/' in objectUrl: if '/statuses/' in objectUrl:
likeTo=[objectUrl.split('/statuses/')[0]] likeTo=[objectUrl.split('/statuses/')[0]]
newLikeJson = { newLikeJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Like', 'type': 'Like',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
@ -302,7 +302,7 @@ def undolike(recentPostsCache: {}, \
if '/statuses/' in objectUrl: if '/statuses/' in objectUrl:
likeTo=[objectUrl.split('/statuses/')[0]] likeTo=[objectUrl.split('/statuses/')[0]]
newUndoLikeJson = { newUndoLikeJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo', 'type': 'Undo',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname, 'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
@ -368,8 +368,8 @@ def undoLikePost(recentPostsCache: {}, \
if ':' not in likeDomain: if ':' not in likeDomain:
likeDomain=likeDomain+':'+str(likePort) likeDomain=likeDomain+':'+str(likePort)
objectUrl = \ objectUrl= \
httpPrefix + '://'+likeDomain+'/users/'+likeNickname+ \ httpPrefix+'://'+likeDomain+'/users/'+likeNickname+ \
'/statuses/'+str(likeStatusNumber) '/statuses/'+str(likeStatusNumber)
ccUrl=httpPrefix+'://'+likeDomain+'/users/'+likeNickname ccUrl=httpPrefix+'://'+likeDomain+'/users/'+likeNickname
@ -409,7 +409,7 @@ def sendLikeViaServer(baseDir: str,session, \
if '/statuses/' in likeUrl: if '/statuses/' in likeUrl:
toUrl=[likeUrl.split('/statuses/')[0]] toUrl=[likeUrl.split('/statuses/')[0]]
newLikeJson = { newLikeJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Like', 'type': 'Like',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
@ -429,7 +429,7 @@ def sendLikeViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -445,10 +445,12 @@ def sendLikeViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newLikeJson,[],inboxUrl,headers,"inbox:write") postJson(session,newLikeJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:
@ -484,7 +486,7 @@ def sendUndoLikeViaServer(baseDir: str,session, \
if '/statuses/' in likeUrl: if '/statuses/' in likeUrl:
toUrl=[likeUrl.split('/statuses/')[0]] toUrl=[likeUrl.split('/statuses/')[0]]
newUndoLikeJson = { newUndoLikeJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo', 'type': 'Undo',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
@ -498,8 +500,9 @@ def sendUndoLikeViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest= \
fromDomain,projectVersion) webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -508,7 +511,7 @@ def sendUndoLikeViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -524,10 +527,12 @@ def sendUndoLikeViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newUndoLikeJson,[],inboxUrl,headers,"inbox:write") postJson(session,newUndoLikeJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:

View File

@ -1,10 +1,10 @@
__filename__ = "manualapprove.py" __filename__="manualapprove.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import json import json
@ -99,7 +99,7 @@ def manualApproveFollowRequest(session,baseDir: str, \
print('Manual follow accept: '+approveHandle+' not in requests file '+approveFollowsFilename) print('Manual follow accept: '+approveHandle+' not in requests file '+approveFollowsFilename)
return return
approvefilenew = open(approveFollowsFilename+'.new', 'w+') approvefilenew=open(approveFollowsFilename+'.new', 'w+')
updateApprovedFollowers=False updateApprovedFollowers=False
followActivityfilename=None followActivityfilename=None
with open(approveFollowsFilename, 'r') as approvefile: with open(approveFollowsFilename, 'r') as approvefile:
@ -147,7 +147,7 @@ def manualApproveFollowRequest(session,baseDir: str, \
if approveHandle not in open(followersFilename).read(): if approveHandle not in open(followersFilename).read():
try: try:
with open(followersFilename, 'r+') as followersFile: with open(followersFilename, 'r+') as followersFile:
content = followersFile.read() content=followersFile.read()
followersFile.seek(0, 0) followersFile.seek(0, 0)
followersFile.write(approveHandle+'\n'+content) followersFile.write(approveHandle+'\n'+content)
except Exception as e: except Exception as e:

View File

@ -1,10 +1,10 @@
__filename__ = "matrix.py" __filename__="matrix.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json

View File

@ -1,10 +1,10 @@
__filename__ = "media.py" __filename__="media.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
from blurhash import blurhash_encode as blurencode from blurhash import blurhash_encode as blurencode
from PIL import Image from PIL import Image

View File

@ -1,10 +1,10 @@
__filename__ = "metadata.py" __filename__="metadata.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import json import json
@ -18,7 +18,7 @@ def metaDataNodeInfo(baseDir: str,registration: bool,version: str) -> {}:
activeAccounts=noOfAccounts(baseDir) activeAccounts=noOfAccounts(baseDir)
activeAccountsMonthly=noOfActiveAccountsMonthly(baseDir,1) activeAccountsMonthly=noOfActiveAccountsMonthly(baseDir,1)
activeAccountsHalfYear=noOfActiveAccountsMonthly(baseDir,6) activeAccountsHalfYear=noOfActiveAccountsMonthly(baseDir,6)
nodeinfo = { nodeinfo={
'openRegistrations': registration, 'openRegistrations': registration,
'protocols': ['activitypub'], 'protocols': ['activitypub'],
'software': { 'software': {
@ -59,27 +59,28 @@ def metaDataInstance(instanceTitle: str, \
if adminActor['type']!='Person': if adminActor['type']!='Person':
isBot=True isBot=True
instance = { instance={
'approval_required': False, 'approval_required': False,
'contact_account': {'acct': adminActor['preferredUsername'], 'contact_account': {
'avatar': adminActor['icon']['url'], 'acct': adminActor['preferredUsername'],
'avatar_static': adminActor['icon']['url'], 'avatar': adminActor['icon']['url'],
'bot': isBot, 'avatar_static': adminActor['icon']['url'],
'created_at': '2019-07-01T10:30:00Z', 'bot': isBot,
'display_name': adminActor['name'], 'created_at': '2019-07-01T10:30:00Z',
'emojis': [], 'display_name': adminActor['name'],
'fields': [], 'emojis': [],
'followers_count': 1, 'fields': [],
'following_count': 1, 'followers_count': 1,
'header': adminActor['image']['url'], 'following_count': 1,
'header_static': adminActor['image']['url'], 'header': adminActor['image']['url'],
'id': '1', 'header_static': adminActor['image']['url'],
'last_status_at': '2019-07-01T10:30:00Z', 'id': '1',
'locked': adminActor['manuallyApprovesFollowers'], 'last_status_at': '2019-07-01T10:30:00Z',
'note': '<p>Admin of '+domain+'</p>', 'locked': adminActor['manuallyApprovesFollowers'],
'statuses_count': 1, 'note': '<p>Admin of '+domain+'</p>',
'url': httpPrefix+'://'+domainFull+'/@'+adminActor['preferredUsername'], 'statuses_count': 1,
'username': adminActor['preferredUsername'] 'url': httpPrefix+'://'+domainFull+'/@'+adminActor['preferredUsername'],
'username': adminActor['preferredUsername']
}, },
'description': instanceDescription, 'description': instanceDescription,
'email': 'admin@'+domain, 'email': 'admin@'+domain,

View File

@ -1,10 +1,10 @@
__filename__ = "migrate.py" __filename__="migrate.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os

View File

@ -1,10 +1,10 @@
__filename__ = "outbox.py" __filename__="outbox.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import json import json

162
person.py
View File

@ -1,10 +1,10 @@
__filename__ = "person.py" __filename__="person.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import time import time
@ -42,9 +42,9 @@ from config import setConfigParam
from config import getConfigParam from config import getConfigParam
def generateRSAKey() -> (str,str): def generateRSAKey() -> (str,str):
key = RSA.generate(2048) key=RSA.generate(2048)
privateKeyPem = key.exportKey("PEM").decode("utf-8") privateKeyPem=key.exportKey("PEM").decode("utf-8")
publicKeyPem = key.publickey().exportKey("PEM").decode("utf-8") publicKeyPem=key.publickey().exportKey("PEM").decode("utf-8")
return privateKeyPem,publicKeyPem return privateKeyPem,publicKeyPem
def setProfileImage(baseDir: str,httpPrefix :str,nickname: str,domain: str, \ def setProfileImage(baseDir: str,httpPrefix :str,nickname: str,domain: str, \
@ -100,10 +100,13 @@ def setProfileImage(baseDir: str,httpPrefix :str,nickname: str,domain: str, \
personJson=loadJson(personFilename) personJson=loadJson(personFilename)
if personJson: if personJson:
personJson[iconFilenameBase]['mediaType']=mediaType personJson[iconFilenameBase]['mediaType']=mediaType
personJson[iconFilenameBase]['url']=httpPrefix+'://'+fullDomain+'/users/'+nickname+'/'+iconFilename personJson[iconFilenameBase]['url']= \
httpPrefix+'://'+fullDomain+'/users/'+nickname+'/'+iconFilename
saveJson(personJson,personFilename) saveJson(personJson,personFilename)
cmd = '/usr/bin/convert '+imageFilename+' -size '+resolution+' -quality 50 '+profileFilename cmd= \
'/usr/bin/convert '+imageFilename+' -size '+ \
resolution+' -quality 50 '+profileFilename
subprocess.call(cmd, shell=True) subprocess.call(cmd, shell=True)
removeMetaData(profileFilename,profileFilename) removeMetaData(profileFilename,profileFilename)
return True return True
@ -180,61 +183,66 @@ def createPersonBase(baseDir: str,nickname: str,domain: str,port: int, \
approveFollowers=True approveFollowers=True
personType='Application' personType='Application'
newPerson = {'@context': ['https://www.w3.org/ns/activitystreams', newPerson={
'https://w3id.org/security/v1', '@context': ['https://www.w3.org/ns/activitystreams',
{'Emoji': 'toot:Emoji', 'https://w3id.org/security/v1',
'Hashtag': 'as:Hashtag', {'Emoji': 'toot:Emoji',
'IdentityProof': 'toot:IdentityProof', 'Hashtag': 'as:Hashtag',
'PropertyValue': 'schema:PropertyValue', 'IdentityProof': 'toot:IdentityProof',
'alsoKnownAs': {'@id': 'as:alsoKnownAs', '@type': '@id'}, 'PropertyValue': 'schema:PropertyValue',
'focalPoint': {'@container': '@list', '@id': 'toot:focalPoint'}, 'alsoKnownAs': {'@id': 'as:alsoKnownAs', '@type': '@id'},
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers', 'focalPoint': {'@container': '@list', '@id': 'toot:focalPoint'},
'movedTo': {'@id': 'as:movedTo', '@type': '@id'}, 'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
'schema': 'http://schema.org#', 'movedTo': {'@id': 'as:movedTo', '@type': '@id'},
'value': 'schema:value'}], 'schema': 'http://schema.org#',
'attachment': [], 'value': 'schema:value'}],
'alsoKnownAs': [], 'attachment': [],
'discoverable': False, 'alsoKnownAs': [],
'endpoints': { 'discoverable': False,
'id': personId+'/endpoints', 'endpoints': {
'sharedInbox': httpPrefix+'://'+domain+'/inbox', 'id': personId+'/endpoints',
}, 'sharedInbox': httpPrefix+'://'+domain+'/inbox',
'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new', },
'followers': personId+'/followers', 'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new',
'following': personId+'/following', 'followers': personId+'/followers',
'shares': personId+'/shares', 'following': personId+'/following',
'orgSchema': None, 'shares': personId+'/shares',
'skills': {}, 'orgSchema': None,
'roles': {}, 'skills': {},
'availability': None, 'roles': {},
'icon': {'mediaType': 'image/png', 'availability': None,
'type': 'Image', 'icon': {
'url': personId+'/avatar'+str(randint(10000000000000,99999999999999))+'.png'}, 'mediaType': 'image/png',
'id': personId, 'type': 'Image',
'image': {'mediaType': 'image/png', 'url': personId+'/avatar'+str(randint(10000000000000,99999999999999))+'.png'
'type': 'Image', },
'url': personId+'/image'+str(randint(10000000000000,99999999999999))+'.png'}, 'id': personId,
'inbox': inboxStr, 'image': {
'manuallyApprovesFollowers': approveFollowers, 'mediaType': 'image/png',
'name': personName, 'type': 'Image',
'outbox': personId+'/outbox', 'url': personId+'/image'+str(randint(10000000000000,99999999999999))+'.png'
'preferredUsername': personName, },
'summary': '', 'inbox': inboxStr,
'publicKey': { 'manuallyApprovesFollowers': approveFollowers,
'id': personId+'#main-key', 'name': personName,
'owner': personId, 'outbox': personId+'/outbox',
'publicKeyPem': publicKeyPem 'preferredUsername': personName,
}, 'summary': '',
'tag': [], 'publicKey': {
'type': personType, 'id': personId+'#main-key',
'url': personUrl, 'owner': personId,
'nomadicLocations': [{ 'publicKeyPem': publicKeyPem
'id': personId, },
'type': 'nomadicLocation', 'tag': [],
'locationAddress':'acct:'+nickname+'@'+domain, 'type': personType,
'locationPrimary':True, 'url': personUrl,
'locationDeleted':False 'nomadicLocations': [{
}] 'id': personId,
'type': 'nomadicLocation',
'locationAddress':'acct:'+nickname+'@'+domain,
'locationPrimary':True,
'locationDeleted':False
}]
} }
if nickname=='inbox': if nickname=='inbox':
@ -344,7 +352,7 @@ def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
if registrationsRemaining<=0: if registrationsRemaining<=0:
return None,None,None,None return None,None,None,None
privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint = \ privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint= \
createPersonBase(baseDir,nickname,domain,port,httpPrefix,saveToFile,password) createPersonBase(baseDir,nickname,domain,port,httpPrefix,saveToFile,password)
if noOfAccounts(baseDir)==1: if noOfAccounts(baseDir)==1:
#print(nickname+' becomes the instance admin and a moderator') #print(nickname+' becomes the instance admin and a moderator')
@ -621,7 +629,7 @@ def isSuspended(baseDir: str,nickname: str) -> bool:
suspendedFilename=baseDir+'/accounts/suspended.txt' suspendedFilename=baseDir+'/accounts/suspended.txt'
if os.path.isfile(suspendedFilename): if os.path.isfile(suspendedFilename):
with open(suspendedFilename, "r") as f: with open(suspendedFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
suspendedFile=open(suspendedFilename,"w+") suspendedFile=open(suspendedFilename,"w+")
for suspended in lines: for suspended in lines:
if suspended.strip('\n')==nickname: if suspended.strip('\n')==nickname:
@ -634,7 +642,7 @@ def unsuspendAccount(baseDir: str,nickname: str) -> None:
suspendedFilename=baseDir+'/accounts/suspended.txt' suspendedFilename=baseDir+'/accounts/suspended.txt'
if os.path.isfile(suspendedFilename): if os.path.isfile(suspendedFilename):
with open(suspendedFilename, "r") as f: with open(suspendedFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
suspendedFile=open(suspendedFilename,"w+") suspendedFile=open(suspendedFilename,"w+")
for suspended in lines: for suspended in lines:
if suspended.strip('\n')!=nickname: if suspended.strip('\n')!=nickname:
@ -653,7 +661,7 @@ def suspendAccount(baseDir: str,nickname: str,domain: str) -> None:
moderatorsFile=baseDir+'/accounts/moderators.txt' moderatorsFile=baseDir+'/accounts/moderators.txt'
if os.path.isfile(moderatorsFile): if os.path.isfile(moderatorsFile):
with open(moderatorsFile, "r") as f: with open(moderatorsFile, "r") as f:
lines = f.readlines() lines=f.readlines()
for moderator in lines: for moderator in lines:
if moderator.strip('\n')==nickname: if moderator.strip('\n')==nickname:
return return
@ -668,7 +676,7 @@ def suspendAccount(baseDir: str,nickname: str,domain: str) -> None:
suspendedFilename=baseDir+'/accounts/suspended.txt' suspendedFilename=baseDir+'/accounts/suspended.txt'
if os.path.isfile(suspendedFilename): if os.path.isfile(suspendedFilename):
with open(suspendedFilename, "r") as f: with open(suspendedFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
for suspended in lines: for suspended in lines:
if suspended.strip('\n')==nickname: if suspended.strip('\n')==nickname:
return return
@ -703,7 +711,7 @@ def canRemovePost(baseDir: str,nickname: str,domain: str,port: int,postId: str)
moderatorsFile=baseDir+'/accounts/moderators.txt' moderatorsFile=baseDir+'/accounts/moderators.txt'
if os.path.isfile(moderatorsFile): if os.path.isfile(moderatorsFile):
with open(moderatorsFile, "r") as f: with open(moderatorsFile, "r") as f:
lines = f.readlines() lines=f.readlines()
for moderator in lines: for moderator in lines:
if domainFull+'/users/'+moderator.strip('\n')+'/' in postId: if domainFull+'/users/'+moderator.strip('\n')+'/' in postId:
return False return False
@ -720,10 +728,10 @@ def removeTagsForNickname(baseDir: str,nickname: str,domain: str,port: int) -> N
if ':' not in domain: if ':' not in domain:
domainFull=domain+':'+str(port) domainFull=domain+':'+str(port)
matchStr=domainFull+'/users/'+nickname+'/' matchStr=domainFull+'/users/'+nickname+'/'
directory = os.fsencode(baseDir+'/tags/') directory=os.fsencode(baseDir+'/tags/')
for f in os.scandir(directory): for f in os.scandir(directory):
f=f.name f=f.name
filename = os.fsdecode(f) filename=os.fsdecode(f)
if not filename.endswith(".txt"): if not filename.endswith(".txt"):
continue continue
tagFilename=os.path.join(directory,filename) tagFilename=os.path.join(directory,filename)
@ -732,7 +740,7 @@ def removeTagsForNickname(baseDir: str,nickname: str,domain: str,port: int) -> N
if matchStr not in open(tagFilename).read(): if matchStr not in open(tagFilename).read():
continue continue
with open(tagFilename, "r") as f: with open(tagFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
tagFile=open(tagFilename,"w+") tagFile=open(tagFilename,"w+")
if tagFile: if tagFile:
for tagline in lines: for tagline in lines:
@ -752,7 +760,7 @@ def removeAccount(baseDir: str,nickname: str,domain: str,port: int) -> bool:
moderatorsFile=baseDir+'/accounts/moderators.txt' moderatorsFile=baseDir+'/accounts/moderators.txt'
if os.path.isfile(moderatorsFile): if os.path.isfile(moderatorsFile):
with open(moderatorsFile, "r") as f: with open(moderatorsFile, "r") as f:
lines = f.readlines() lines=f.readlines()
for moderator in lines: for moderator in lines:
if moderator.strip('\n')==nickname: if moderator.strip('\n')==nickname:
return False return False

14
pgp.py
View File

@ -1,10 +1,10 @@
__filename__ = "pgp.py" __filename__="pgp.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json

261
posts.py
View File

@ -1,10 +1,10 @@
__filename__ = "posts.py" __filename__="posts.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import requests import requests
import json import json
@ -68,7 +68,7 @@ def isModerator(baseDir: str,nickname: str) -> bool:
return False return False
with open(moderatorsFile, "r") as f: with open(moderatorsFile, "r") as f:
lines = f.readlines() lines=f.readlines()
if len(lines)==0: if len(lines)==0:
if getConfigParam(baseDir,'admin')==nickname: if getConfigParam(baseDir,'admin')==nickname:
return True return True
@ -116,7 +116,7 @@ def getPersonKey(nickname: str,domain: str,baseDir: str,keyType='public', \
return keyPem return keyPem
def cleanHtml(rawHtml: str) -> str: def cleanHtml(rawHtml: str) -> str:
#text = BeautifulSoup(rawHtml, 'html.parser').get_text() #text=BeautifulSoup(rawHtml, 'html.parser').get_text()
text=rawHtml text=rawHtml
return html.unescape(text) return html.unescape(text)
@ -134,8 +134,8 @@ def getUserUrl(wfRequest: {}) -> str:
def parseUserFeed(session,feedUrl: str,asHeader: {}, \ def parseUserFeed(session,feedUrl: str,asHeader: {}, \
projectVersion: str,httpPrefix: str,domain: str) -> None: projectVersion: str,httpPrefix: str,domain: str) -> None:
feedJson = getJson(session,feedUrl,asHeader,None, \ feedJson=getJson(session,feedUrl,asHeader,None, \
projectVersion,httpPrefix,domain) projectVersion,httpPrefix,domain)
if not feedJson: if not feedJson:
return return
@ -143,11 +143,11 @@ def parseUserFeed(session,feedUrl: str,asHeader: {}, \
for item in feedJson['orderedItems']: for item in feedJson['orderedItems']:
yield item yield item
nextUrl = None nextUrl=None
if 'first' in feedJson: if 'first' in feedJson:
nextUrl = feedJson['first'] nextUrl=feedJson['first']
elif 'next' in feedJson: elif 'next' in feedJson:
nextUrl = feedJson['next'] nextUrl=feedJson['next']
if nextUrl: if nextUrl:
if isinstance(nextUrl, str): if isinstance(nextUrl, str):
@ -165,29 +165,37 @@ def getPersonBox(baseDir: str,session,wfRequest: {},personCache: {}, \
projectVersion: str,httpPrefix: str, \ projectVersion: str,httpPrefix: str, \
nickname: str,domain: str, \ nickname: str,domain: str, \
boxName='inbox') -> (str,str,str,str,str,str,str,str): boxName='inbox') -> (str,str,str,str,str,str,str,str):
asHeader = {'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
}
if not wfRequest.get('errors'): if not wfRequest.get('errors'):
personUrl = getUserUrl(wfRequest) personUrl=getUserUrl(wfRequest)
else: else:
if nickname=='dev': if nickname=='dev':
# try single user instance # try single user instance
print('getPersonBox: Trying single user instance with ld+json') print('getPersonBox: Trying single user instance with ld+json')
personUrl = httpPrefix+'://'+domain personUrl=httpPrefix+'://'+domain
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
else: else:
personUrl = httpPrefix+'://'+domain+'/users/'+nickname personUrl=httpPrefix+'://'+domain+'/users/'+nickname
if not personUrl: if not personUrl:
return None,None,None,None,None,None,None,None return None,None,None,None,None,None,None,None
personJson = getPersonFromCache(baseDir,personUrl,personCache) personJson=getPersonFromCache(baseDir,personUrl,personCache)
if not personJson: if not personJson:
if '/channel/' in personUrl: if '/channel/' in personUrl:
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
personJson = getJson(session,personUrl,asHeader,None, \ 'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
projectVersion,httpPrefix,domain) }
personJson=getJson(session,personUrl,asHeader,None, \
projectVersion,httpPrefix,domain)
if not personJson: if not personJson:
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
personJson = getJson(session,personUrl,asHeader,None, \ 'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
projectVersion,httpPrefix,domain) }
personJson=getJson(session,personUrl,asHeader,None, \
projectVersion,httpPrefix,domain)
if not personJson: if not personJson:
print('Unable to get actor') print('Unable to get actor')
return None,None,None,None,None,None,None,None return None,None,None,None,None,None,None,None
@ -246,12 +254,16 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
personPosts={} personPosts={}
if not outboxUrl: if not outboxUrl:
return personPosts return personPosts
asHeader = {'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
}
if '/outbox/' in outboxUrl: if '/outbox/' in outboxUrl:
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
if raw: if raw:
result = [] result=[]
i = 0 i=0
userFeed=parseUserFeed(session,outboxUrl,asHeader, \ userFeed=parseUserFeed(session,outboxUrl,asHeader, \
projectVersion,httpPrefix,domain) projectVersion,httpPrefix,domain)
for item in userFeed: for item in userFeed:
@ -262,7 +274,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
pprint(result) pprint(result)
return None return None
i = 0 i=0
userFeed=parseUserFeed(session,outboxUrl,asHeader, \ userFeed=parseUserFeed(session,outboxUrl,asHeader, \
projectVersion,httpPrefix,domain) projectVersion,httpPrefix,domain)
for item in userFeed: for item in userFeed:
@ -291,7 +303,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
print('No published attribute') print('No published attribute')
continue continue
#pprint(item) #pprint(item)
published = item['object']['published'] published=item['object']['published']
if not personPosts.get(item['id']): if not personPosts.get(item['id']):
# check that this is a public post # check that this is a public post
# #Public should appear in the "to" list # #Public should appear in the "to" list
@ -304,7 +316,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
if not isPublic: if not isPublic:
continue continue
content = item['object']['content'].replace('&apos;',"'") content=item['object']['content'].replace('&apos;',"'")
mentions=[] mentions=[]
emoji={} emoji={}
@ -337,12 +349,12 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
print('max emojis reached') print('max emojis reached')
continue continue
summary = '' summary=''
if item['object'].get('summary'): if item['object'].get('summary'):
if item['object']['summary']: if item['object']['summary']:
summary = item['object']['summary'] summary=item['object']['summary']
inReplyTo = '' inReplyTo=''
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
@ -352,17 +364,17 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
if debug: if debug:
print('url not permitted '+item['object']['inReplyTo']) print('url not permitted '+item['object']['inReplyTo'])
continue continue
inReplyTo = item['object']['inReplyTo'] inReplyTo=item['object']['inReplyTo']
conversation = '' conversation=''
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'], \ if urlPermitted(item['object']['conversation'], \
federationList,"objects:read"): federationList,"objects:read"):
conversation = item['object']['conversation'] conversation=item['object']['conversation']
attachment = [] attachment=[]
if item['object'].get('attachment'): if item['object'].get('attachment'):
if item['object']['attachment']: if item['object']['attachment']:
for attach in item['object']['attachment']: for attach in item['object']['attachment']:
@ -376,15 +388,15 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
if debug: if debug:
print('url not permitted '+attach['url']) print('url not permitted '+attach['url'])
sensitive = False sensitive=False
if item['object'].get('sensitive'): if item['object'].get('sensitive'):
sensitive = item['object']['sensitive'] sensitive=item['object']['sensitive']
if simple: if simple:
print(cleanHtml(content)+'\n') print(cleanHtml(content)+'\n')
else: else:
pprint(item) pprint(item)
personPosts[item['id']] = { personPosts[item['id']]={
"sensitive": sensitive, "sensitive": sensitive,
"inreplyto": inReplyTo, "inreplyto": inReplyTo,
"summary": summary, "summary": summary,
@ -406,10 +418,10 @@ def deleteAllPosts(baseDir: str,nickname: str, domain: str,boxname: str) -> None
""" """
if boxname!='inbox' and boxname!='outbox' and boxname!='tlblogs': if boxname!='inbox' and boxname!='outbox' and boxname!='tlblogs':
return return
boxDir = createPersonDir(nickname,domain,baseDir,boxname) boxDir=createPersonDir(nickname,domain,baseDir,boxname)
for deleteFilename in os.scandir(boxDir): for deleteFilename in os.scandir(boxDir):
deleteFilename=deleteFilename.name deleteFilename=deleteFilename.name
filePath = os.path.join(boxDir, deleteFilename) filePath=os.path.join(boxDir,deleteFilename)
try: try:
if os.path.isfile(filePath): if os.path.isfile(filePath):
os.unlink(filePath) os.unlink(filePath)
@ -431,7 +443,7 @@ def savePostToBox(baseDir: str,httpPrefix: str,postId: str, \
domain=domain.split(':')[0] domain=domain.split(':')[0]
if not postId: if not postId:
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
postId= \ postId= \
httpPrefix+'://'+originalDomain+'/users/'+nickname+ \ httpPrefix+'://'+originalDomain+'/users/'+nickname+ \
'/statuses/'+statusNumber '/statuses/'+statusNumber
@ -441,7 +453,7 @@ def savePostToBox(baseDir: str,httpPrefix: str,postId: str, \
postJsonObject['object']['id']=postId postJsonObject['object']['id']=postId
postJsonObject['object']['atomUri']=postId postJsonObject['object']['atomUri']=postId
boxDir = createPersonDir(nickname,domain,baseDir,boxname) boxDir=createPersonDir(nickname,domain,baseDir,boxname)
filename=boxDir+'/'+postId.replace('/','#')+'.json' filename=boxDir+'/'+postId.replace('/','#')+'.json'
saveJson(postJsonObject,filename) saveJson(postJsonObject,filename)
return filename return filename
@ -472,7 +484,7 @@ def updateHashtagsIndex(baseDir: str,tag: {},newPostId: str) -> None:
if tagline not in open(tagsFilename).read(): if tagline not in open(tagsFilename).read():
try: try:
with open(tagsFilename, 'r+') as tagsFile: with open(tagsFilename, 'r+') as tagsFile:
content = tagsFile.read() content=tagsFile.read()
tagsFile.seek(0, 0) tagsFile.seek(0, 0)
tagsFile.write(tagline+content) tagsFile.write(tagline+content)
except Exception as e: except Exception as e:
@ -491,7 +503,7 @@ def addSchedulePost(baseDir: str,nickname: str,domain: str, \
if indexStr not in open(scheduleIndexFilename).read(): if indexStr not in open(scheduleIndexFilename).read():
try: try:
with open(scheduleIndexFilename, 'r+') as scheduleFile: with open(scheduleIndexFilename, 'r+') as scheduleFile:
content = scheduleFile.read() content=scheduleFile.read()
scheduleFile.seek(0, 0) scheduleFile.seek(0, 0)
scheduleFile.write(indexStr+'\n'+content) scheduleFile.write(indexStr+'\n'+content)
print('DEBUG: scheduled post added to index') print('DEBUG: scheduled post added to index')
@ -551,7 +563,7 @@ def createPostBase(baseDir: str,nickname: str,domain: str,port: int, \
if tag['name'] not in content: if tag['name'] not in content:
del hashtagsDict[tagName] del hashtagsDict[tagName]
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
postTo='https://www.w3.org/ns/activitystreams#Public' postTo='https://www.w3.org/ns/activitystreams#Public'
postCC=httpPrefix+'://'+domain+'/users/'+nickname+'/followers' postCC=httpPrefix+'://'+domain+'/users/'+nickname+'/followers'
if followersOnly: if followersOnly:
@ -659,7 +671,7 @@ def createPostBase(baseDir: str,nickname: str,domain: str,port: int, \
if oc: if oc:
if oc.get('id'): if oc.get('id'):
capabilityIdList=[oc['id']] capabilityIdList=[oc['id']]
newPost = { newPost={
"@context": postContext, "@context": postContext,
'id': newPostId+'/activity', 'id': newPostId+'/activity',
'capability': capabilityIdList, 'capability': capabilityIdList,
@ -704,7 +716,7 @@ def createPostBase(baseDir: str,nickname: str,domain: str,port: int, \
newPost['object'],attachImageFilename, \ newPost['object'],attachImageFilename, \
mediaType,imageDescription,useBlurhash) mediaType,imageDescription,useBlurhash)
else: else:
newPost = { newPost={
"@context": postContext, "@context": postContext,
'id': newPostId, 'id': newPostId,
'type': 'Note', 'type': 'Note',
@ -788,16 +800,16 @@ def outboxMessageCreateWrap(httpPrefix: str, \
if port!=80 and port!=443: if port!=80 and port!=443:
if ':' not in domain: if ':' not in domain:
domain=domain+':'+str(port) domain=domain+':'+str(port)
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
if messageJson.get('published'): if messageJson.get('published'):
published = messageJson['published'] published=messageJson['published']
newPostId=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber newPostId=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
cc=[] cc=[]
if messageJson.get('cc'): if messageJson.get('cc'):
cc=messageJson['cc'] cc=messageJson['cc']
# TODO # TODO
capabilityUrl=[] capabilityUrl=[]
newPost = { newPost={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'id': newPostId+'/activity', 'id': newPostId+'/activity',
'capability': capabilityUrl, 'capability': capabilityUrl,
@ -1207,7 +1219,7 @@ def threadSendPost(session,postJsonStr: str,federationList: [],\
postResult=None postResult=None
unauthorized=False unauthorized=False
try: try:
postResult,unauthorized = \ postResult,unauthorized= \
postJsonString(session,postJsonStr,federationList, \ postJsonString(session,postJsonStr,federationList, \
inboxUrl,signatureHeaderJson, \ inboxUrl,signatureHeaderJson, \
"inbox:write",debug) "inbox:write",debug)
@ -1272,8 +1284,8 @@ def sendPost(projectVersion: str, \
handle=httpPrefix+'://'+toDomain+'/@'+toNickname handle=httpPrefix+'://'+toDomain+'/@'+toNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
domain,projectVersion) domain,projectVersion)
if not wfRequest: if not wfRequest:
return 1 return 1
@ -1285,7 +1297,7 @@ def sendPost(projectVersion: str, \
postToBox='tlblogs' postToBox='tlblogs'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,toPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,toPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix, \ projectVersion,httpPrefix, \
nickname,domain,postToBox) nickname,domain,postToBox)
@ -1305,7 +1317,7 @@ def sendPost(projectVersion: str, \
return 5 return 5
# sharedInbox and capabilities are optional # sharedInbox and capabilities are optional
postJsonObject = \ postJsonObject= \
createPostBase(baseDir,nickname,domain,port, \ createPostBase(baseDir,nickname,domain,port, \
toPersonId,cc,httpPrefix,content, \ toPersonId,cc,httpPrefix,content, \
followersOnly,saveToFile,clientToServer, \ followersOnly,saveToFile,clientToServer, \
@ -1328,7 +1340,7 @@ def sendPost(projectVersion: str, \
postJsonStr=json.dumps(postJsonObject) postJsonStr=json.dumps(postJsonObject)
# construct the http header, including the message body digest # construct the http header, including the message body digest
signatureHeaderJson = \ signatureHeaderJson= \
createSignedHeader(privateKeyPem,nickname,domain,port, \ createSignedHeader(privateKeyPem,nickname,domain,port, \
toDomain,toPort, \ toDomain,toPort, \
postPath,httpPrefix,withDigest,postJsonStr) postPath,httpPrefix,withDigest,postJsonStr)
@ -1339,7 +1351,7 @@ def sendPost(projectVersion: str, \
sendThreads[0].kill() sendThreads[0].kill()
sendThreads.pop(0) sendThreads.pop(0)
print('WARN: thread killed') print('WARN: thread killed')
thr = \ thr= \
threadWithTrace(target=threadSendPost, \ threadWithTrace(target=threadSendPost, \
args=(session, \ args=(session, \
postJsonStr, \ postJsonStr, \
@ -1378,7 +1390,7 @@ def sendPostViaServer(projectVersion: str, \
handle=httpPrefix+'://'+fromDomain+'/@'+fromNickname handle=httpPrefix+'://'+fromDomain+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = \ wfRequest= \
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion) fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
@ -1391,7 +1403,7 @@ def sendPostViaServer(projectVersion: str, \
postToBox='tlblogs' postToBox='tlblogs'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \ projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox) fromDomain,postToBox)
@ -1431,7 +1443,7 @@ def sendPostViaServer(projectVersion: str, \
toDomainFull=toDomain+':'+str(toPort) toDomainFull=toDomain+':'+str(toPort)
toPersonId=httpPrefix+'://'+toDomainFull+'/users/'+toNickname toPersonId=httpPrefix+'://'+toDomainFull+'/users/'+toNickname
postJsonObject = \ postJsonObject= \
createPostBase(baseDir, \ createPostBase(baseDir, \
fromNickname,fromDomain,fromPort, \ fromNickname,fromDomain,fromPort, \
toPersonId,cc,httpPrefix,content, \ toPersonId,cc,httpPrefix,content, \
@ -1444,9 +1456,11 @@ def sendPostViaServer(projectVersion: str, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
if attachImageFilename: if attachImageFilename:
headers = {'host': fromDomain, \ headers={
'Authorization': authHeader} 'host': fromDomain, \
postResult = \ 'Authorization': authHeader
}
postResult= \
postImage(session,attachImageFilename,[], \ postImage(session,attachImageFilename,[], \
inboxUrl,headers,"inbox:write") inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
@ -1454,10 +1468,12 @@ def sendPostViaServer(projectVersion: str, \
# print('DEBUG: Failed to upload image') # print('DEBUG: Failed to upload image')
# return 9 # return 9
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJsonString(session,json.dumps(postJsonObject),[], \ postJsonString(session,json.dumps(postJsonObject),[], \
inboxUrl,headers,"inbox:write",debug) inboxUrl,headers,"inbox:write",debug)
#if not postResult: #if not postResult:
@ -1578,7 +1594,7 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox/outbox/capabilities for the To handle # get the actor inbox/outbox/capabilities for the To handle
inboxUrl,pubKeyId,pubKey,toPersonId,sharedInboxUrl,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,toPersonId,sharedInboxUrl,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,nickname,domain,postToBox) projectVersion,httpPrefix,nickname,domain,postToBox)
@ -1633,7 +1649,7 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
postJsonStr=json.dumps(postJsonObject) postJsonStr=json.dumps(postJsonObject)
# construct the http header, including the message body digest # construct the http header, including the message body digest
signatureHeaderJson = \ signatureHeaderJson= \
createSignedHeader(privateKeyPem,nickname,domain,port, \ createSignedHeader(privateKeyPem,nickname,domain,port, \
toDomain,toPort, \ toDomain,toPort, \
postPath,httpPrefix,withDigest,postJsonStr) postPath,httpPrefix,withDigest,postJsonStr)
@ -1648,14 +1664,15 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
if debug: if debug:
print('DEBUG: starting thread to send post') print('DEBUG: starting thread to send post')
pprint(postJsonObject) pprint(postJsonObject)
thr = threadWithTrace(target=threadSendPost, \ thr= \
args=(session, \ threadWithTrace(target=threadSendPost, \
postJsonStr, \ args=(session, \
federationList, \ postJsonStr, \
inboxUrl,baseDir, \ federationList, \
signatureHeaderJson.copy(), \ inboxUrl,baseDir, \
postLog, signatureHeaderJson.copy(), \
debug),daemon=True) postLog,
debug),daemon=True)
sendThreads.append(thr) sendThreads.append(thr)
#thr.start() #thr.start()
return 0 return 0
@ -2030,7 +2047,7 @@ def createModeration(baseDir: str,nickname: str,domain: str,port: int, \
httpPrefix: str, \ httpPrefix: str, \
itemsPerPage: int,headerOnly: bool, \ itemsPerPage: int,headerOnly: bool, \
ocapAlways: bool,pageNumber=None) -> {}: ocapAlways: bool,pageNumber=None) -> {}:
boxDir = createPersonDir(nickname,domain,baseDir,'inbox') boxDir=createPersonDir(nickname,domain,baseDir,'inbox')
boxname='moderation' boxname='moderation'
if port: if port:
@ -2042,24 +2059,28 @@ def createModeration(baseDir: str,nickname: str,domain: str,port: int, \
pageNumber=1 pageNumber=1
pageStr='?page='+str(pageNumber) pageStr='?page='+str(pageNumber)
boxHeader = {'@context': 'https://www.w3.org/ns/activitystreams', boxHeader={
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true', '@context': 'https://www.w3.org/ns/activitystreams',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname, 'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true', 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
'totalItems': 0, 'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
'type': 'OrderedCollection'} 'totalItems': 0,
boxItems = {'@context': 'https://www.w3.org/ns/activitystreams', 'type': 'OrderedCollection'
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr, }
'orderedItems': [ boxItems={
], '@context': 'https://www.w3.org/ns/activitystreams',
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname, 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr,
'type': 'OrderedCollectionPage'} 'orderedItems': [
],
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
'type': 'OrderedCollectionPage'
}
if isModerator(baseDir,nickname): if isModerator(baseDir,nickname):
moderationIndexFile=baseDir+'/accounts/moderation.txt' moderationIndexFile=baseDir+'/accounts/moderation.txt'
if os.path.isfile(moderationIndexFile): if os.path.isfile(moderationIndexFile):
with open(moderationIndexFile, "r") as f: with open(moderationIndexFile, "r") as f:
lines = f.readlines() lines=f.readlines()
boxHeader['totalItems']=len(lines) boxHeader['totalItems']=len(lines)
if headerOnly: if headerOnly:
return boxHeader return boxHeader
@ -2237,7 +2258,7 @@ def createSharedInboxIndex(baseDir: str,sharedBoxDir: str, \
# is the actor followed by this account? # is the actor followed by this account?
if not followingHandles: if not followingHandles:
with open(followingFilename, 'r') as followingFile: with open(followingFilename, 'r') as followingFile:
followingHandles = followingFile.read() followingHandles=followingFile.read()
if actorNickname+'@'+actorDomain not in followingHandles: if actorNickname+'@'+actorDomain not in followingHandles:
continue continue
@ -2304,7 +2325,7 @@ def addPostToTimeline(filePath: str,boxname: str, \
""" Reads a post from file and decides whether it is valid """ Reads a post from file and decides whether it is valid
""" """
with open(filePath, 'r') as postFile: with open(filePath, 'r') as postFile:
postStr = postFile.read() postStr=postFile.read()
return addPostStringToTimeline(postStr,boxname,postsInBox,boxActor) return addPostStringToTimeline(postStr,boxname,postsInBox,boxActor)
return False return False
@ -2327,17 +2348,17 @@ def createBoxIndexed(recentPostsCache: {}, \
if boxname!='dm' and boxname!='tlreplies' and \ if boxname!='dm' and boxname!='tlreplies' and \
boxname!='tlmedia' and boxname!='tlblogs' and \ boxname!='tlmedia' and boxname!='tlblogs' and \
boxname!='tlbookmarks': boxname!='tlbookmarks':
boxDir = createPersonDir(nickname,domain,baseDir,boxname) boxDir=createPersonDir(nickname,domain,baseDir,boxname)
else: else:
# extract DMs or replies or media from the inbox # extract DMs or replies or media from the inbox
boxDir = createPersonDir(nickname,domain,baseDir,'inbox') boxDir=createPersonDir(nickname,domain,baseDir,'inbox')
announceCacheDir=baseDir+'/cache/announce/'+nickname announceCacheDir=baseDir+'/cache/announce/'+nickname
sharedBoxDir=None sharedBoxDir=None
if boxname=='inbox' or boxname=='tlreplies' or \ if boxname=='inbox' or boxname=='tlreplies' or \
boxname=='tlmedia' or boxname=='tlblogs': boxname=='tlmedia' or boxname=='tlblogs':
sharedBoxDir = createPersonDir('inbox',domain,baseDir,boxname) sharedBoxDir=createPersonDir('inbox',domain,baseDir,boxname)
# bookmarks timeline is like the inbox but has its own separate index # bookmarks timeline is like the inbox but has its own separate index
indexBoxName=boxname indexBoxName=boxname
@ -2365,18 +2386,22 @@ def createBoxIndexed(recentPostsCache: {}, \
pageStr='?page='+str(pageNumber) pageStr='?page='+str(pageNumber)
except: except:
pass pass
boxHeader = {'@context': 'https://www.w3.org/ns/activitystreams', boxHeader={
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true', '@context': 'https://www.w3.org/ns/activitystreams',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname, 'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true', 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
'totalItems': 0, 'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
'type': 'OrderedCollection'} 'totalItems': 0,
boxItems = {'@context': 'https://www.w3.org/ns/activitystreams', 'type': 'OrderedCollection'
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr, }
'orderedItems': [ boxItems={
], '@context': 'https://www.w3.org/ns/activitystreams',
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname, 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr,
'type': 'OrderedCollectionPage'} 'orderedItems': [
],
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
'type': 'OrderedCollectionPage'
}
postsInBox=[] postsInBox=[]
@ -2528,7 +2553,7 @@ def archivePostsForPerson(httpPrefix: str,nickname: str,domain: str,baseDir: str
if archiveDir: if archiveDir:
if not os.path.isdir(archiveDir): if not os.path.isdir(archiveDir):
os.mkdir(archiveDir) os.mkdir(archiveDir)
boxDir = createPersonDir(nickname,domain,baseDir,boxname) boxDir=createPersonDir(nickname,domain,baseDir,boxname)
postsInBox=os.scandir(boxDir) postsInBox=os.scandir(boxDir)
noOfPosts=0 noOfPosts=0
for f in postsInBox: for f in postsInBox:
@ -2624,7 +2649,7 @@ def getPublicPostsOfPerson(baseDir: str,nickname: str,domain: str, \
debug: bool,projectVersion: str) -> None: debug: bool,projectVersion: str) -> None:
""" This is really just for test purposes """ This is really just for test purposes
""" """
session = createSession(useTor) session=createSession(useTor)
personCache={} personCache={}
cachedWebfingers={} cachedWebfingers={}
federationList=[] federationList=[]
@ -2635,7 +2660,7 @@ def getPublicPostsOfPerson(baseDir: str,nickname: str,domain: str, \
if ':' not in domain: if ':' not in domain:
domainFull=domain+':'+str(port) domainFull=domain+':'+str(port)
handle=httpPrefix+"://"+domainFull+"/@"+nickname handle=httpPrefix+"://"+domainFull+"/@"+nickname
wfRequest = \ wfRequest= \
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
domain,projectVersion) domain,projectVersion)
if not wfRequest: if not wfRequest:
@ -2644,12 +2669,12 @@ def getPublicPostsOfPerson(baseDir: str,nickname: str,domain: str, \
personUrl,pubKeyId,pubKey,personId,shaedInbox,capabilityAcquisition,avatarUrl,displayName= \ personUrl,pubKeyId,pubKey,personId,shaedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,nickname,domain,'outbox') projectVersion,httpPrefix,nickname,domain,'outbox')
wfResult = json.dumps(wfRequest, indent=2, sort_keys=False) wfResult=json.dumps(wfRequest,indent=2,sort_keys=False)
maxMentions=10 maxMentions=10
maxEmoji=10 maxEmoji=10
maxAttachments=5 maxAttachments=5
userPosts = \ userPosts= \
getPosts(session,personUrl,30,maxMentions,maxEmoji, \ getPosts(session,personUrl,30,maxMentions,maxEmoji, \
maxAttachments,federationList, \ maxAttachments,federationList, \
personCache,raw,simple,debug, \ personCache,raw,simple,debug, \

View File

@ -1,10 +1,10 @@
__filename__ = "question.py" __filename__="question.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
from utils import locatePost from utils import locatePost
@ -76,7 +76,7 @@ def questionUpdateVotes(baseDir: str,nickname: str,domain: str,replyJson: {}) ->
else: else:
# change an entry in the voters file # change an entry in the voters file
with open(votersFilename, "r") as votersFile: with open(votersFilename, "r") as votersFile:
lines = votersFile.readlines() lines=votersFile.readlines()
newlines=[] newlines=[]
saveVotersFile=False saveVotersFile=False
for voteLine in lines: for voteLine in lines:
@ -101,7 +101,7 @@ def questionUpdateVotes(baseDir: str,nickname: str,domain: str,replyJson: {}) ->
continue continue
totalItems=0 totalItems=0
with open(votersFilename, "r") as votersFile: with open(votersFilename, "r") as votersFile:
lines = votersFile.readlines() lines=votersFile.readlines()
for voteLine in lines: for voteLine in lines:
if voteLine.endswith(votersFileSeparator+possibleAnswer['name']+'\n'): if voteLine.endswith(votersFileSeparator+possibleAnswer['name']+'\n'):
totalItems+=1 totalItems+=1

View File

@ -1,10 +1,10 @@
__filename__ = "roles.py" __filename__="roles.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import os import os
@ -23,10 +23,10 @@ def clearModeratorStatus(baseDir: str) -> None:
This could be slow if there are many users, but only happens This could be slow if there are many users, but only happens
rarely when moderators are appointed or removed rarely when moderators are appointed or removed
""" """
directory = os.fsencode(baseDir+'/accounts/') directory=os.fsencode(baseDir+'/accounts/')
for f in os.scandir(directory): for f in os.scandir(directory):
f=f.name f=f.name
filename = os.fsdecode(f) filename=os.fsdecode(f)
if filename.endswith(".json") and '@' in filename: if filename.endswith(".json") and '@' in filename:
filename=os.path.join(baseDir+'/accounts/', filename) filename=os.path.join(baseDir+'/accounts/', filename)
if '"moderator"' in open(filename).read(): if '"moderator"' in open(filename).read():
@ -46,7 +46,7 @@ def addModerator(baseDir: str,nickname: str,domain: str) -> None:
if os.path.isfile(moderatorsFile): if os.path.isfile(moderatorsFile):
# is this nickname already in the file? # is this nickname already in the file?
with open(moderatorsFile, "r") as f: with open(moderatorsFile, "r") as f:
lines = f.readlines() lines=f.readlines()
for moderator in lines: for moderator in lines:
moderator=moderator.strip('\n') moderator=moderator.strip('\n')
if line==nickname: if line==nickname:
@ -70,7 +70,7 @@ def removeModerator(baseDir: str,nickname: str):
if not os.path.isfile(moderatorsFile): if not os.path.isfile(moderatorsFile):
return return
with open(moderatorsFile, "r") as f: with open(moderatorsFile, "r") as f:
lines = f.readlines() lines=f.readlines()
with open(moderatorsFile, "w") as f: with open(moderatorsFile, "w") as f:
for moderator in lines: for moderator in lines:
moderator=moderator.strip('\n') moderator=moderator.strip('\n')
@ -229,14 +229,17 @@ def sendRoleViaServer(baseDir: str,session, \
if ':' not in delegatorDomain: if ':' not in delegatorDomain:
delegatorDomainFull=delegatorDomain+':'+str(fromPort) delegatorDomainFull=delegatorDomain+':'+str(fromPort)
toUrl = httpPrefix+'://'+delegatorDomainFull+'/users/'+nickname toUrl= \
ccUrl = httpPrefix+'://'+delegatorDomainFull+'/users/'+delegatorNickname+'/followers' httpPrefix+'://'+delegatorDomainFull+'/users/'+nickname
ccUrl= \
httpPrefix+'://'+delegatorDomainFull+'/users/'+ \
delegatorNickname+'/followers'
if role: if role:
roleStr=project.lower()+';'+role.lower() roleStr=project.lower()+';'+role.lower()
else: else:
roleStr=project.lower()+';' roleStr=project.lower()+';'
newRoleJson = { newRoleJson={
'type': 'Delegate', 'type': 'Delegate',
'actor': httpPrefix+'://'+delegatorDomainFull+'/users/'+delegatorNickname, 'actor': httpPrefix+'://'+delegatorDomainFull+'/users/'+delegatorNickname,
'object': { 'object': {
@ -253,8 +256,8 @@ def sendRoleViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+delegatorDomainFull+'/@'+delegatorNickname handle=httpPrefix+'://'+delegatorDomainFull+'/@'+delegatorNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
delegatorDomain,projectVersion) delegatorDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -263,7 +266,7 @@ def sendRoleViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix, \ projectVersion,httpPrefix, \
delegatorNickname,delegatorDomain,postToBox) delegatorNickname,delegatorDomain,postToBox)
@ -279,10 +282,12 @@ def sendRoleViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(delegatorNickname,password) authHeader=createBasicAuthHeader(delegatorNickname,password)
headers = {'host': delegatorDomain, \ headers={
'Content-type': 'application/json', \ 'host': delegatorDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newRoleJson,[],inboxUrl,headers,"inbox:write") postJson(session,newRoleJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:

View File

@ -1,10 +1,10 @@
__filename__ = "schedule.py" __filename__="schedule.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import time import time
@ -70,7 +70,7 @@ def updatePostSchedule(baseDir: str,handle: str,httpd,maxScheduledPosts: int) ->
# set the published time # set the published time
# If this is not recent then http checks on the receiving side # If this is not recent then http checks on the receiving side
# will reject it # will reject it
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
if postJsonObject.get('published'): if postJsonObject.get('published'):
postJsonObject['published']=published postJsonObject['published']=published
if postJsonObject.get('object'): if postJsonObject.get('object'):

View File

@ -1,10 +1,10 @@
__filename__ = "session.py" __filename__="session.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import sys import sys
@ -15,11 +15,11 @@ import json
baseDirectory=None baseDirectory=None
def createSession(onionRoute: bool): def createSession(onionRoute: bool):
session = requests.session() session=requests.session()
if onionRoute: if onionRoute:
session.proxies = {} session.proxies={}
session.proxies['http'] = 'socks5h://localhost:9050' session.proxies['http']='socks5h://localhost:9050'
session.proxies['https'] = 'socks5h://localhost:9050' session.proxies['https']='socks5h://localhost:9050'
return session return session
def getJson(session,url: str,headers: {},params: {}, \ def getJson(session,url: str,headers: {},params: {}, \
@ -94,11 +94,12 @@ def postJsonString(session,postJsonStr: str, \
print('postJson: '+inboxUrl+' not permitted by capabilities') print('postJson: '+inboxUrl+' not permitted by capabilities')
return None,None return None,None
postResult = session.post(url = inboxUrl, data = postJsonStr, headers=headers) postResult= \
session.post(url=inboxUrl,data=postJsonStr,headers=headers)
if postResult.status_code<200 or postResult.status_code>202: if postResult.status_code<200 or postResult.status_code>202:
#if postResult.status_code==400: #if postResult.status_code==400:
# headers['content-type']='application/ld+json' # headers['content-type']='application/ld+json'
# postResult = session.post(url = inboxUrl, data = postJsonStr, headers=headers) # postResult=session.post(url=inboxUrl,data=postJsonStr,headers=headers)
# if not (postResult.status_code<200 or postResult.status_code>202): # if not (postResult.status_code<200 or postResult.status_code>202):
# return True # return True
if postResult.status_code>=400 and postResult.status_code<=405 and \ if postResult.status_code>=400 and postResult.status_code<=405 and \
@ -140,8 +141,8 @@ def postImage(session,attachImageFilename: str,federationList: [], \
headers['Content-type']=contentType headers['Content-type']=contentType
with open(attachImageFilename, 'rb') as avFile: with open(attachImageFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary=avFile.read()
postResult = session.post(url=inboxUrl, data=mediaBinary, headers=headers) postResult=session.post(url=inboxUrl,data=mediaBinary,headers=headers)
if postResult: if postResult:
return postResult.text return postResult.text
return None return None

View File

@ -1,10 +1,10 @@
__filename__ = "shares.py" __filename__="shares.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import os import os
@ -142,7 +142,7 @@ def addShare(baseDir: str, \
os.remove(imageFilename) os.remove(imageFilename)
imageUrl=httpPrefix+'://'+domainFull+'/sharefiles/'+nickname+'/'+itemID+'.gif' imageUrl=httpPrefix+'://'+domainFull+'/sharefiles/'+nickname+'/'+itemID+'.gif'
sharesJson[itemID] = { sharesJson[itemID]={
"displayName": displayName, "displayName": displayName,
"summary": summary, "summary": summary,
"imageUrl": imageUrl, "imageUrl": imageUrl,
@ -261,25 +261,27 @@ def getSharesFeedForPerson(baseDir: str, \
sharesJson=loadJson(sharesFilename) sharesJson=loadJson(sharesFilename)
if sharesJson: if sharesJson:
noOfShares=len(sharesJson.items()) noOfShares=len(sharesJson.items())
shares = { shares={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page=1', 'first': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page=1',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares', 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares',
'totalItems': str(noOfShares), 'totalItems': str(noOfShares),
'type': 'OrderedCollection'} 'type': 'OrderedCollection'
}
return shares return shares
if not pageNumber: if not pageNumber:
pageNumber=1 pageNumber=1
nextPageNumber=int(pageNumber+1) nextPageNumber=int(pageNumber+1)
shares = { shares={
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page='+str(pageNumber), 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page='+str(pageNumber),
'orderedItems': [], 'orderedItems': [],
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/shares', 'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/shares',
'totalItems': 0, 'totalItems': 0,
'type': 'OrderedCollectionPage'} 'type': 'OrderedCollectionPage'
}
if not os.path.isfile(sharesFilename): if not os.path.isfile(sharesFilename):
print("test5") print("test5")
@ -332,10 +334,10 @@ def sendShareViaServer(baseDir,session, \
if ':' not in fromDomain: if ':' not in fromDomain:
fromDomainFull=fromDomain+':'+str(fromPort) fromDomainFull=fromDomain+':'+str(fromPort)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl='https://www.w3.org/ns/activitystreams#Public'
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers' ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
newShareJson = { newShareJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Add', 'type': 'Add',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
@ -358,8 +360,8 @@ def sendShareViaServer(baseDir,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion) fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -368,7 +370,7 @@ def sendShareViaServer(baseDir,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix, \ projectVersion,httpPrefix, \
fromNickname,fromDomain,postToBox) fromNickname,fromDomain,postToBox)
@ -385,15 +387,19 @@ def sendShareViaServer(baseDir,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
if imageFilename: if imageFilename:
headers = {'host': fromDomain, \ headers={
'Authorization': authHeader} 'host': fromDomain, \
postResult = \ 'Authorization': authHeader
}
postResult= \
postImage(session,imageFilename,[],inboxUrl.replace('/'+postToBox,'/shares'),headers,"inbox:write") postImage(session,imageFilename,[],inboxUrl.replace('/'+postToBox,'/shares'),headers,"inbox:write")
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newShareJson,[],inboxUrl,headers,"inbox:write") postJson(session,newShareJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:
@ -424,10 +430,10 @@ def sendUndoShareViaServer(baseDir: str,session, \
if ':' not in fromDomain: if ':' not in fromDomain:
fromDomainFull=fromDomain+':'+str(fromPort) fromDomainFull=fromDomain+':'+str(fromPort)
toUrl = 'https://www.w3.org/ns/activitystreams#Public' toUrl='https://www.w3.org/ns/activitystreams#Public'
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers' ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
undoShareJson = { undoShareJson={
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
'type': 'Remove', 'type': 'Remove',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname, 'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
@ -445,8 +451,8 @@ def sendUndoShareViaServer(baseDir: str,session, \
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion) fromDomain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -455,7 +461,7 @@ def sendUndoShareViaServer(baseDir: str,session, \
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix, \ projectVersion,httpPrefix, \
fromNickname,fromDomain,postToBox) fromNickname,fromDomain,postToBox)
@ -471,10 +477,12 @@ def sendUndoShareViaServer(baseDir: str,session, \
authHeader=createBasicAuthHeader(fromNickname,password) authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \ headers={
'Content-type': 'application/json', \ 'host': fromDomain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,undoShareJson,[],inboxUrl,headers,"inbox:write") postJson(session,undoShareJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:

View File

@ -1,10 +1,10 @@
__filename__ = "skills.py" __filename__="skills.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import os import os
@ -109,14 +109,14 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
if ':' not in domain: if ':' not in domain:
domainFull=domain+':'+str(port) domainFull=domain+':'+str(port)
toUrl = httpPrefix+'://'+domainFull+'/users/'+nickname toUrl=httpPrefix+'://'+domainFull+'/users/'+nickname
ccUrl = httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers' ccUrl=httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers'
if skillLevelPercent: if skillLevelPercent:
skillStr=skill+';'+str(skillLevelPercent) skillStr=skill+';'+str(skillLevelPercent)
else: else:
skillStr=skill+';0' skillStr=skill+';0'
newSkillJson = { newSkillJson={
'type': 'Skill', 'type': 'Skill',
'actor': httpPrefix+'://'+domainFull+'/users/'+nickname, 'actor': httpPrefix+'://'+domainFull+'/users/'+nickname,
'object': '"'+skillStr+'"', 'object': '"'+skillStr+'"',
@ -127,8 +127,8 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
handle=httpPrefix+'://'+domainFull+'/@'+nickname handle=httpPrefix+'://'+domainFull+'/@'+nickname
# lookup the inbox for the To handle # lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \ wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
domain,projectVersion) domain,projectVersion)
if not wfRequest: if not wfRequest:
if debug: if debug:
print('DEBUG: announce webfinger failed for '+handle) print('DEBUG: announce webfinger failed for '+handle)
@ -137,7 +137,7 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
postToBox='outbox' postToBox='outbox'
# get the actor inbox for the To handle # get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,nickname,domain,postToBox) projectVersion,httpPrefix,nickname,domain,postToBox)
@ -152,10 +152,12 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
authHeader=createBasicAuthHeader(Nickname,password) authHeader=createBasicAuthHeader(Nickname,password)
headers = {'host': domain, \ headers={
'Content-type': 'application/json', \ 'host': domain, \
'Authorization': authHeader} 'Content-type': 'application/json', \
postResult = \ 'Authorization': authHeader
}
postResult= \
postJson(session,newSkillJson,[],inboxUrl,headers,"inbox:write") postJson(session,newSkillJson,[],inboxUrl,headers,"inbox:write")
#if not postResult: #if not postResult:
# if debug: # if debug:

14
ssb.py
View File

@ -1,10 +1,10 @@
__filename__ = "ssb.py" __filename__="ssb.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json

137
tests.py
View File

@ -1,10 +1,10 @@
__filename__ = "tests.py" __filename__="tests.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import base64 import base64
import time import time
@ -77,9 +77,9 @@ from content import addHtmlTags
from content import removeLongWords from content import removeLongWords
from theme import setCSSparam from theme import setCSSparam
testServerAliceRunning = False testServerAliceRunning=False
testServerBobRunning = False testServerBobRunning=False
testServerEveRunning = False testServerEveRunning=False
thrAlice=None thrAlice=None
thrBob=None thrBob=None
thrEve=None thrEve=None
@ -103,7 +103,11 @@ def testHttpsigBase(withDigest):
privateKeyPem,publicKeyPem,person,wfEndpoint= \ privateKeyPem,publicKeyPem,person,wfEndpoint= \
createPerson(path,nickname,domain,port,httpPrefix,False,password) createPerson(path,nickname,domain,port,httpPrefix,False,password)
assert privateKeyPem assert privateKeyPem
messageBodyJson = {"a key": "a value", "another key": "A string","yet another key": "Another string"} messageBodyJson={
"a key": "a value",
"another key": "A string",
"yet another key": "Another string"
}
messageBodyJsonStr=json.dumps(messageBodyJson) messageBodyJsonStr=json.dumps(messageBodyJson)
headersDomain=domain headersDomain=domain
@ -115,23 +119,33 @@ def testHttpsigBase(withDigest):
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
boxpath='/inbox' boxpath='/inbox'
if not withDigest: if not withDigest:
headers = {'host': headersDomain,'date': dateStr,'content-type': 'application/json'} headers={
signatureHeader = \ 'host': headersDomain,
'date': dateStr,
'content-type': 'application/json'
}
signatureHeader= \
signPostHeaders(dateStr,privateKeyPem, nickname, \ signPostHeaders(dateStr,privateKeyPem, nickname, \
domain, port, \ domain, port, \
domain, port, \ domain, port, \
boxpath, httpPrefix, None) boxpath, httpPrefix, None)
else: else:
bodyDigest = messageContentDigest(messageBodyJsonStr) bodyDigest=messageContentDigest(messageBodyJsonStr)
contentLength=len(messageBodyJsonStr) contentLength=len(messageBodyJsonStr)
headers = {'host': headersDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType,'content-length': str(contentLength)} headers={
signatureHeader = \ 'host': headersDomain,
'date': dateStr,
'digest': f'SHA-256={bodyDigest}',
'content-type': contentType,
'content-length': str(contentLength)
}
signatureHeader= \
signPostHeaders(dateStr,privateKeyPem, nickname, \ signPostHeaders(dateStr,privateKeyPem, nickname, \
domain, port, \ domain, port, \
domain, port, \ domain, port, \
boxpath, httpPrefix, messageBodyJsonStr) boxpath, httpPrefix, messageBodyJsonStr)
headers['signature'] = signatureHeader headers['signature']=signatureHeader
assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \ assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
boxpath,False,None, \ boxpath,False,None, \
messageBodyJsonStr,False) messageBodyJsonStr,False)
@ -149,14 +163,24 @@ def testHttpsigBase(withDigest):
messageBodyJsonStr,False) == False messageBodyJsonStr,False) == False
if not withDigest: if not withDigest:
# fake domain # fake domain
headers = {'host': 'bogon.domain','date': dateStr,'content-type': 'application/json'} headers={
'host': 'bogon.domain',
'date': dateStr,
'content-type': 'application/json'
}
else: else:
# correct domain but fake message # correct domain but fake message
messageBodyJsonStr = '{"a key": "a value", "another key": "Fake GNUs", "yet another key": "More Fake GNUs"}' messageBodyJsonStr='{"a key": "a value", "another key": "Fake GNUs", "yet another key": "More Fake GNUs"}'
contentLength=len(messageBodyJsonStr) contentLength=len(messageBodyJsonStr)
bodyDigest = messageContentDigest(messageBodyJsonStr) bodyDigest=messageContentDigest(messageBodyJsonStr)
headers = {'host': domain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType,'content-length': str(contentLength)} headers={
headers['signature'] = signatureHeader 'host': domain,
'date': dateStr,
'digest': f'SHA-256={bodyDigest}',
'content-type': contentType,
'content-length': str(contentLength)
}
headers['signature']=signatureHeader
assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \ assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
boxpath,True,None, \ boxpath,True,None, \
messageBodyJsonStr,False) == False messageBodyJsonStr,False) == False
@ -184,7 +208,8 @@ def testThreadsFunction(param: str):
def testThreads(): def testThreads():
print('testThreads') print('testThreads')
thr = threadWithTrace(target=testThreadsFunction,args=('test',),daemon=True) thr= \
threadWithTrace(target=testThreadsFunction,args=('test',),daemon=True)
thr.start() thr.start()
assert thr.isAlive()==True assert thr.isAlive()==True
time.sleep(1) time.sleep(1)
@ -235,7 +260,7 @@ def createServerAlice(path: str,domain: str,port: int,bobAddress: str,federation
"In the gardens of memory, in the palace of dreams, that is where you and I shall meet", \ "In the gardens of memory, in the palace of dreams, that is where you and I shall meet", \
False, True, clientToServer,None,None,useBlurhash) False, True, clientToServer,None,None,useBlurhash)
global testServerAliceRunning global testServerAliceRunning
testServerAliceRunning = True testServerAliceRunning=True
maxMentions=10 maxMentions=10
maxEmoji=10 maxEmoji=10
onionDomain=None onionDomain=None
@ -292,7 +317,7 @@ def createServerBob(path: str,domain: str,port: int,aliceAddress: str,federation
"Quantum physics is a bit of a passion of mine", \ "Quantum physics is a bit of a passion of mine", \
False, True, clientToServer,None,None,useBlurhash) False, True, clientToServer,None,None,useBlurhash)
global testServerBobRunning global testServerBobRunning
testServerBobRunning = True testServerBobRunning=True
maxMentions=10 maxMentions=10
maxEmoji=10 maxEmoji=10
onionDomain=None onionDomain=None
@ -329,7 +354,7 @@ def createServerEve(path: str,domain: str,port: int,federationList: [], \
deleteAllPosts(path,nickname,domain,'inbox') deleteAllPosts(path,nickname,domain,'inbox')
deleteAllPosts(path,nickname,domain,'outbox') deleteAllPosts(path,nickname,domain,'outbox')
global testServerEveRunning global testServerEveRunning
testServerEveRunning = True testServerEveRunning=True
maxMentions=10 maxMentions=10
maxEmoji=10 maxEmoji=10
onionDomain=None onionDomain=None
@ -346,8 +371,8 @@ def testPostMessageBetweenServers():
global testServerAliceRunning global testServerAliceRunning
global testServerBobRunning global testServerBobRunning
testServerAliceRunning = False testServerAliceRunning=False
testServerBobRunning = False testServerBobRunning=False
httpPrefix='http' httpPrefix='http'
useTor=False useTor=False
@ -380,7 +405,7 @@ def testPostMessageBetweenServers():
time.sleep(1) time.sleep(1)
thrAlice.kill() thrAlice.kill()
thrAlice = \ thrAlice= \
threadWithTrace(target=createServerAlice, \ threadWithTrace(target=createServerAlice, \
args=(aliceDir,aliceDomain,alicePort,bobAddress, \ args=(aliceDir,aliceDomain,alicePort,bobAddress, \
federationList,False,False, \ federationList,False,False, \
@ -393,7 +418,7 @@ def testPostMessageBetweenServers():
time.sleep(1) time.sleep(1)
thrBob.kill() thrBob.kill()
thrBob = \ thrBob= \
threadWithTrace(target=createServerBob, \ threadWithTrace(target=createServerBob, \
args=(bobDir,bobDomain,bobPort,aliceAddress, \ args=(bobDir,bobDomain,bobPort,aliceAddress, \
federationList,False,False, \ federationList,False,False, \
@ -413,11 +438,11 @@ def testPostMessageBetweenServers():
print('\n\n*******************************************************') print('\n\n*******************************************************')
print('Alice sends to Bob') print('Alice sends to Bob')
os.chdir(aliceDir) os.chdir(aliceDir)
sessionAlice = createSession(useTor) sessionAlice=createSession(useTor)
inReplyTo=None inReplyTo=None
inReplyToAtomUri=None inReplyToAtomUri=None
subject=None subject=None
alicePostLog = [] alicePostLog=[]
followersOnly=False followersOnly=False
saveToFile=True saveToFile=True
clientToServer=False clientToServer=False
@ -433,7 +458,7 @@ def testPostMessageBetweenServers():
outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox' outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==0 assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))])==0
sendResult = \ sendResult= \
sendPost(__version__, \ sendPost(__version__, \
sessionAlice,aliceDir,'alice',aliceDomain,alicePort, \ sessionAlice,aliceDir,'alice',aliceDomain,alicePort, \
'bob',bobDomain,bobPort,ccUrl,httpPrefix, \ 'bob',bobDomain,bobPort,ccUrl,httpPrefix, \
@ -486,8 +511,8 @@ def testPostMessageBetweenServers():
followPerson(aliceDir,'alice',aliceDomain,'bob', \ followPerson(aliceDir,'alice',aliceDomain,'bob', \
bobDomain+':'+str(bobPort),federationList,False) bobDomain+':'+str(bobPort),federationList,False)
sessionBob = createSession(useTor) sessionBob=createSession(useTor)
bobPostLog = [] bobPostLog=[]
bobPersonCache={} bobPersonCache={}
bobCachedWebfingers={} bobCachedWebfingers={}
statusNumber=None statusNumber=None
@ -563,8 +588,8 @@ def testFollowBetweenServers():
global testServerAliceRunning global testServerAliceRunning
global testServerBobRunning global testServerBobRunning
testServerAliceRunning = False testServerAliceRunning=False
testServerBobRunning = False testServerBobRunning=False
httpPrefix='http' httpPrefix='http'
useTor=False useTor=False
@ -597,7 +622,7 @@ def testFollowBetweenServers():
time.sleep(1) time.sleep(1)
thrAlice.kill() thrAlice.kill()
thrAlice = \ thrAlice= \
threadWithTrace(target=createServerAlice, \ threadWithTrace(target=createServerAlice, \
args=(aliceDir,aliceDomain,alicePort,bobAddress, \ args=(aliceDir,aliceDomain,alicePort,bobAddress, \
federationList,False,False, \ federationList,False,False, \
@ -610,7 +635,7 @@ def testFollowBetweenServers():
time.sleep(1) time.sleep(1)
thrBob.kill() thrBob.kill()
thrBob = \ thrBob= \
threadWithTrace(target=createServerBob, \ threadWithTrace(target=createServerBob, \
args=(bobDir,bobDomain,bobPort,aliceAddress, \ args=(bobDir,bobDomain,bobPort,aliceAddress, \
federationList,False,False, \ federationList,False,False, \
@ -638,11 +663,11 @@ def testFollowBetweenServers():
print('*********************************************************') print('*********************************************************')
print('Alice sends a follow request to Bob') print('Alice sends a follow request to Bob')
os.chdir(aliceDir) os.chdir(aliceDir)
sessionAlice = createSession(useTor) sessionAlice=createSession(useTor)
inReplyTo=None inReplyTo=None
inReplyToAtomUri=None inReplyToAtomUri=None
subject=None subject=None
alicePostLog = [] alicePostLog=[]
followersOnly=False followersOnly=False
saveToFile=True saveToFile=True
clientToServer=False clientToServer=False
@ -650,7 +675,7 @@ def testFollowBetweenServers():
alicePersonCache={} alicePersonCache={}
aliceCachedWebfingers={} aliceCachedWebfingers={}
alicePostLog=[] alicePostLog=[]
sendResult = \ sendResult= \
sendFollowRequest(sessionAlice,aliceDir, \ sendFollowRequest(sessionAlice,aliceDir, \
'alice',aliceDomain,alicePort,httpPrefix, \ 'alice',aliceDomain,alicePort,httpPrefix, \
'bob',bobDomain,bobPort,httpPrefix, \ 'bob',bobDomain,bobPort,httpPrefix, \
@ -671,13 +696,13 @@ def testFollowBetweenServers():
print('\n\n*********************************************************') print('\n\n*********************************************************')
print('Alice sends a message to Bob') print('Alice sends a message to Bob')
alicePostLog = [] alicePostLog=[]
alicePersonCache={} alicePersonCache={}
aliceCachedWebfingers={} aliceCachedWebfingers={}
alicePostLog=[] alicePostLog=[]
useBlurhash=False useBlurhash=False
isArticle=False isArticle=False
sendResult = \ sendResult= \
sendPost(__version__, \ sendPost(__version__, \
sessionAlice,aliceDir,'alice',aliceDomain,alicePort, \ sessionAlice,aliceDir,'alice',aliceDomain,alicePort, \
'bob',bobDomain,bobPort,ccUrl, \ 'bob',bobDomain,bobPort,ccUrl, \
@ -860,7 +885,7 @@ def testFollows():
followPerson(baseDir,nickname,domain,'batman','mesh.com',federationList,False) followPerson(baseDir,nickname,domain,'batman','mesh.com',federationList,False)
followPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList,False) followPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList,False)
f = open(baseDir+'/accounts/'+nickname+'@'+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','')
@ -887,7 +912,7 @@ def testFollows():
followerOfPerson(baseDir,nickname,domain,'batman','mesh.com',federationList,False) followerOfPerson(baseDir,nickname,domain,'batman','mesh.com',federationList,False)
followerOfPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList,False) followerOfPerson(baseDir,nickname,domain,'giraffe','trees.com',federationList,False)
f = open(baseDir+'/accounts/'+nickname+'@'+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:
@ -949,7 +974,7 @@ def testDelegateRoles():
httpPrefix='http' httpPrefix='http'
project='artechoke' project='artechoke'
role='delegator' role='delegator'
newRoleJson = { newRoleJson={
'type': 'Delegate', 'type': 'Delegate',
'actor': httpPrefix+'://'+domain+'/users/'+nickname, 'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'object': { 'object': {
@ -970,7 +995,7 @@ def testDelegateRoles():
assert '"delegator"' in open(baseDir+'/accounts/'+nickname+'@'+domain+'.json').read() assert '"delegator"' in open(baseDir+'/accounts/'+nickname+'@'+domain+'.json').read()
assert '"delegator"' in open(baseDir+'/accounts/'+nicknameDelegated+'@'+domain+'.json').read() assert '"delegator"' in open(baseDir+'/accounts/'+nicknameDelegated+'@'+domain+'.json').read()
newRoleJson = { newRoleJson={
'type': 'Delegate', 'type': 'Delegate',
'actor': httpPrefix+'://'+domain+'/users/'+nicknameDelegated, 'actor': httpPrefix+'://'+domain+'/users/'+nicknameDelegated,
'object': { 'object': {
@ -1030,8 +1055,8 @@ def testClientToServer():
global testServerAliceRunning global testServerAliceRunning
global testServerBobRunning global testServerBobRunning
testServerAliceRunning = False testServerAliceRunning=False
testServerBobRunning = False testServerBobRunning=False
httpPrefix='http' httpPrefix='http'
useTor=False useTor=False
@ -1064,7 +1089,7 @@ def testClientToServer():
time.sleep(1) time.sleep(1)
thrAlice.kill() thrAlice.kill()
thrAlice = \ thrAlice= \
threadWithTrace(target=createServerAlice, \ threadWithTrace(target=createServerAlice, \
args=(aliceDir,aliceDomain,alicePort,bobAddress, \ args=(aliceDir,aliceDomain,alicePort,bobAddress, \
federationList,False,False, \ federationList,False,False, \
@ -1077,7 +1102,7 @@ def testClientToServer():
time.sleep(1) time.sleep(1)
thrBob.kill() thrBob.kill()
thrBob = \ thrBob= \
threadWithTrace(target=createServerBob, \ threadWithTrace(target=createServerBob, \
args=(bobDir,bobDomain,bobPort,aliceAddress, \ args=(bobDir,bobDomain,bobPort,aliceAddress, \
federationList,False,False, \ federationList,False,False, \
@ -1103,7 +1128,7 @@ def testClientToServer():
print('\n\n*******************************************************') print('\n\n*******************************************************')
print('Alice sends to Bob via c2s') print('Alice sends to Bob via c2s')
sessionAlice = createSession(useTor) sessionAlice=createSession(useTor)
followersOnly=False followersOnly=False
attachedImageFilename=baseDir+'/img/logo.png' attachedImageFilename=baseDir+'/img/logo.png'
mediaType=getAttachmentMediaType(attachedImageFilename) mediaType=getAttachmentMediaType(attachedImageFilename)
@ -1214,7 +1239,7 @@ def testClientToServer():
print('\n\nBob likes the post') print('\n\nBob likes the post')
sessionBob = createSession(useTor) sessionBob=createSession(useTor)
password='bobpass' password='bobpass'
outboxPath=bobDir+'/accounts/bob@'+bobDomain+'/outbox' outboxPath=bobDir+'/accounts/bob@'+bobDomain+'/outbox'
inboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/inbox' inboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/inbox'
@ -1262,7 +1287,7 @@ def testClientToServer():
inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox' inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox' outboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/outbox'
postsBefore = len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) postsBefore=len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))])
print('\n\nAlice deletes her post: '+outboxPostId+' '+str(postsBefore)) print('\n\nAlice deletes her post: '+outboxPostId+' '+str(postsBefore))
password='alicepass' password='alicepass'
sendDeleteViaServer(aliceDir,sessionAlice,'alice',password, sendDeleteViaServer(aliceDir,sessionAlice,'alice',password,
@ -1418,7 +1443,7 @@ def testGetStatusNumber():
print('testGetStatusNumber') print('testGetStatusNumber')
prevStatusNumber=None prevStatusNumber=None
for i in range(1,20): for i in range(1,20):
statusNumber,published = getStatusNumber() statusNumber,published=getStatusNumber()
if prevStatusNumber: if prevStatusNumber:
assert len(statusNumber) == 18 assert len(statusNumber) == 18
assert int(statusNumber) > prevStatusNumber assert int(statusNumber) > prevStatusNumber

View File

@ -1,10 +1,10 @@
__filename__ = "theme.py" __filename__="theme.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
from utils import loadJson from utils import loadJson

View File

@ -1,10 +1,10 @@
__filename__ = "threads.py" __filename__="threads.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import threading import threading
import os import os
@ -20,9 +20,9 @@ class threadWithTrace(threading.Thread):
tries=0 tries=0
while tries<3: while tries<3:
try: try:
self._args, self._keywords = args, keywords self._args,self._keywords=args,keywords
threading.Thread.__init__(self, *self._args, **self._keywords) threading.Thread.__init__(self,*self._args,**self._keywords)
self.killed = False self.killed=False
break break
except Exception as e: except Exception as e:
print('ERROR: threads.py/__init__ failed - '+str(e)) print('ERROR: threads.py/__init__ failed - '+str(e))
@ -33,8 +33,8 @@ class threadWithTrace(threading.Thread):
tries=0 tries=0
while tries<3: while tries<3:
try: try:
self.__run_backup = self.run self.__run_backup=self.run
self.run = self.__run self.run=self.__run
threading.Thread.start(self) threading.Thread.start(self)
break break
except Exception as e: except Exception as e:
@ -47,7 +47,7 @@ class threadWithTrace(threading.Thread):
def __run(self): def __run(self):
sys.settrace(self.globaltrace) sys.settrace(self.globaltrace)
self.__run_backup() self.__run_backup()
self.run = self.__run_backup self.run=self.__run_backup
def globaltrace(self, frame, event, arg): def globaltrace(self, frame, event, arg):
if event == 'call': if event == 'call':
@ -62,7 +62,7 @@ class threadWithTrace(threading.Thread):
return self.localtrace return self.localtrace
def kill(self): def kill(self):
self.killed = True self.killed=True
def clone(self,fn): def clone(self,fn):
return threadWithTrace(target=fn, \ return threadWithTrace(target=fn, \

14
tox.py
View File

@ -1,10 +1,10 @@
__filename__ = "tox.py" __filename__="tox.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json

View File

@ -1,10 +1,10 @@
__filename__ = "utils.py" __filename__="utils.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import os import os
import time import time
@ -198,17 +198,21 @@ def getDomainFromActor(actor: str) -> (str,int):
""" """
port=None port=None
if '/profile/' in actor: if '/profile/' in actor:
domain = actor.split('/profile/')[0].replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','') domain= \
actor.split('/profile/')[0].replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','')
else: else:
if '/channel/' in actor: if '/channel/' in actor:
domain = actor.split('/channel/')[0].replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','') domain= \
actor.split('/channel/')[0].replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','')
else: else:
if '/users/' not in actor: if '/users/' not in actor:
domain = actor.replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','') domain= \
actor.replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','')
if '/' in actor: if '/' in actor:
domain=domain.split('/')[0] domain=domain.split('/')[0]
else: else:
domain = actor.split('/users/')[0].replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','') domain= \
actor.split('/users/')[0].replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','')
if ':' in domain: if ':' in domain:
portStr=domain.split(':')[1] portStr=domain.split(':')[1]
if not portStr.isdigit(): if not portStr.isdigit():
@ -252,7 +256,7 @@ def followPerson(baseDir: str,nickname: str, domain: str, \
# remove them from the unfollowed file # remove them from the unfollowed file
newLines='' newLines=''
with open(unfollowedFilename, "r") as f: with open(unfollowedFilename, "r") as f:
lines = f.readlines() lines=f.readlines()
for line in lines: for line in lines:
if handleToFollow not in line: if handleToFollow not in line:
newLines+=line newLines+=line
@ -271,7 +275,7 @@ def followPerson(baseDir: str,nickname: str, domain: str, \
# prepend to follow file # prepend to follow file
try: try:
with open(filename, 'r+') as followFile: with open(filename, 'r+') as followFile:
content = followFile.read() content=followFile.read()
followFile.seek(0, 0) followFile.seek(0, 0)
followFile.write(handleToFollow+'\n'+content) followFile.write(handleToFollow+'\n'+content)
if debug: if debug:
@ -347,7 +351,7 @@ def removeModerationPostFromIndex(baseDir: str,postUrl: str,debug: bool) -> None
postId=postUrl.replace('/activity','') postId=postUrl.replace('/activity','')
if postId in open(moderationIndexFile).read(): if postId in open(moderationIndexFile).read():
with open(moderationIndexFile, "r") as f: with open(moderationIndexFile, "r") as f:
lines = f.readlines() lines=f.readlines()
with open(moderationIndexFile, "w+") as f: with open(moderationIndexFile, "w+") as f:
for line in lines: for line in lines:
if line.strip("\n") != postId: if line.strip("\n") != postId:
@ -485,7 +489,7 @@ def noOfActiveAccountsMonthly(baseDir: str,months: int) -> bool:
lastUsedFilename=baseDir+'/accounts/'+account+'/.lastUsed' lastUsedFilename=baseDir+'/accounts/'+account+'/.lastUsed'
if os.path.isfile(lastUsedFilename): if os.path.isfile(lastUsedFilename):
with open(lastUsedFilename, 'r') as lastUsedFile: with open(lastUsedFilename, 'r') as lastUsedFile:
lastUsed = lastUsedFile.read() lastUsed=lastUsedFile.read()
if lastUsed.isdigit(): if lastUsed.isdigit():
timeDiff=(currTime-int(lastUsed)) timeDiff=(currTime-int(lastUsed))
if timeDiff<monthSeconds: if timeDiff<monthSeconds:
@ -525,8 +529,8 @@ def copytree(src: str, dst: str, symlinks=False, ignore=None):
"""Copy a directory """Copy a directory
""" """
for item in os.listdir(src): for item in os.listdir(src):
s = os.path.join(src, item) s=os.path.join(src, item)
d = os.path.join(dst, item) d=os.path.join(dst, item)
if os.path.isdir(s): if os.path.isdir(s):
shutil.copytree(s, d, symlinks, ignore) shutil.copytree(s, d, symlinks, ignore)
else: else:
@ -609,7 +613,7 @@ def updateRecentPostsCache(recentPostsCache: {},maxRecentPosts: int, \
def fileLastModified(filename: str) -> str: def fileLastModified(filename: str) -> str:
"""Returns the date when a file was last modified """Returns the date when a file was last modified
""" """
t = os.path.getmtime(filename) t=os.path.getmtime(filename)
modifiedTime=datetime.datetime.fromtimestamp(t) modifiedTime=datetime.datetime.fromtimestamp(t)
return modifiedTime.strftime("%Y-%m-%dT%H:%M:%SZ") return modifiedTime.strftime("%Y-%m-%dT%H:%M:%SZ")
@ -624,7 +628,7 @@ def daysInMonth(year: int,monthNumber: int) -> int:
def mergeDicts(dict1: {}, dict2: {}) -> {}: def mergeDicts(dict1: {}, dict2: {}) -> {}:
"""Merges two dictionaries """Merges two dictionaries
""" """
res = {**dict1, **dict2} res={**dict1,**dict2}
return res return res
def isBlogPost(postJsonObject: {}) -> bool: def isBlogPost(postJsonObject: {}) -> bool:

View File

@ -1,10 +1,10 @@
__filename__ = "webfinger.py" __filename__="webfinger.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import base64 import base64
try: try:
@ -26,21 +26,21 @@ from utils import saveJson
def parseHandle(handle: str) -> (str,str): 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, nickname = \ domain,nickname= \
handle.replace('https://','').replace('http://','').replace('dat://','').replace('i2p://','').split('/@') handle.replace('https://','').replace('http://','').replace('dat://','').replace('i2p://','').split('/@')
else: else:
if '/users/' in handle: if '/users/' in handle:
domain, nickname = \ domain,nickname= \
handle.replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','').split('/users/') handle.replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','').split('/users/')
else: else:
if '@' in handle: if '@' in handle:
nickname, domain = handle.split('@') nickname,domain=handle.split('@')
else: else:
return None, None return None,None
return nickname, domain return nickname,domain
def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \ def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \
fromDomain: str,projectVersion: str) -> {}: fromDomain: str,projectVersion: str) -> {}:
@ -48,7 +48,7 @@ def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \
print('WARN: No session specified for webfingerHandle') print('WARN: No session specified for webfingerHandle')
return None return None
nickname, domain = parseHandle(handle) nickname,domain=parseHandle(handle)
if not nickname: if not nickname:
return None return None
wfDomain=domain wfDomain=domain
@ -61,11 +61,15 @@ def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \
wf=getWebfingerFromCache(nickname+'@'+wfDomain,cachedWebfingers) wf=getWebfingerFromCache(nickname+'@'+wfDomain,cachedWebfingers)
if wf: if wf:
return wf return wf
url = '{}://{}/.well-known/webfinger'.format(httpPrefix,domain) url='{}://{}/.well-known/webfinger'.format(httpPrefix,domain)
par = {'resource': 'acct:{}'.format(nickname+'@'+wfDomain)} par={
hdr = {'Accept': 'application/jrd+json'} 'resource': 'acct:{}'.format(nickname+'@'+wfDomain)
}
hdr={
'Accept': 'application/jrd+json'
}
try: try:
result = getJson(session,url,hdr,par,projectVersion,httpPrefix,fromDomain) result=getJson(session,url,hdr,par,projectVersion,httpPrefix,fromDomain)
except Exception as e: except Exception as e:
print("Unable to webfinger " + url) print("Unable to webfinger " + url)
print('nickname: '+str(nickname)) print('nickname: '+str(nickname))
@ -81,9 +85,9 @@ def generateMagicKey(publicKeyPem) -> str:
"""See magic_key method in """See magic_key method in
https://github.com/tootsuite/mastodon/blob/707ddf7808f90e3ab042d7642d368c2ce8e95e6f/app/models/account.rb https://github.com/tootsuite/mastodon/blob/707ddf7808f90e3ab042d7642d368c2ce8e95e6f/app/models/account.rb
""" """
privkey = RSA.importKey(publicKeyPem) privkey=RSA.importKey(publicKeyPem)
mod = base64.urlsafe_b64encode(number.long_to_bytes(privkey.n)).decode("utf-8") mod=base64.urlsafe_b64encode(number.long_to_bytes(privkey.n)).decode("utf-8")
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(nickname: str,domain: str,port: int,baseDir: str, \ def storeWebfingerEndpoint(nickname: str,domain: str,port: int,baseDir: str, \
@ -127,7 +131,7 @@ def createWebfingerEndpoint(nickname: str,domain: str,port: int, \
subjectStr="acct:"+originalDomain+"@"+originalDomain subjectStr="acct:"+originalDomain+"@"+originalDomain
profilePageHref=httpPrefix+'://'+domain+'/about/more?instance_actor=true' profilePageHref=httpPrefix+'://'+domain+'/about/more?instance_actor=true'
account = { account={
"aliases": [ "aliases": [
httpPrefix+"://"+domain+"/@"+personName, httpPrefix+"://"+domain+"/@"+personName,
personId personId
@ -160,7 +164,7 @@ def createWebfingerEndpoint(nickname: str,domain: str,port: int, \
def webfingerNodeInfo(httpPrefix: str,domainFull: str) -> {}: def webfingerNodeInfo(httpPrefix: str,domainFull: str) -> {}:
""" /.well-known/nodeinfo endpoint """ /.well-known/nodeinfo endpoint
""" """
nodeinfo = { nodeinfo={
'links': [ 'links': [
{ {
'href': httpPrefix+'://'+domainFull+'/nodeinfo/2.0', 'href': httpPrefix+'://'+domainFull+'/nodeinfo/2.0',

View File

@ -1,10 +1,10 @@
__filename__ = "webinterface.py" __filename__="webinterface.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json
import time import time
@ -81,17 +81,25 @@ def updateAvatarImageCache(session,baseDir: str,httpPrefix: str, \
actorStr=actor.replace('/','-') actorStr=actor.replace('/','-')
avatarImagePath=baseDir+'/cache/avatars/'+actorStr avatarImagePath=baseDir+'/cache/avatars/'+actorStr
if avatarUrl.endswith('.png') or '.png?' in avatarUrl: if avatarUrl.endswith('.png') or '.png?' in avatarUrl:
sessionHeaders = {'Accept': 'image/png'} sessionHeaders={
'Accept': 'image/png'
}
avatarImageFilename=avatarImagePath+'.png' avatarImageFilename=avatarImagePath+'.png'
elif avatarUrl.endswith('.jpg') or avatarUrl.endswith('.jpeg') or \ elif avatarUrl.endswith('.jpg') or avatarUrl.endswith('.jpeg') or \
'.jpg?' in avatarUrl or '.jpeg?' in avatarUrl: '.jpg?' in avatarUrl or '.jpeg?' in avatarUrl:
sessionHeaders = {'Accept': 'image/jpeg'} sessionHeaders={
'Accept': 'image/jpeg'
}
avatarImageFilename=avatarImagePath+'.jpg' avatarImageFilename=avatarImagePath+'.jpg'
elif avatarUrl.endswith('.gif') or '.gif?' in avatarUrl: elif avatarUrl.endswith('.gif') or '.gif?' in avatarUrl:
sessionHeaders = {'Accept': 'image/gif'} sessionHeaders={
'Accept': 'image/gif'
}
avatarImageFilename=avatarImagePath+'.gif' avatarImageFilename=avatarImagePath+'.gif'
elif avatarUrl.endswith('.webp') or '.webp?' in avatarUrl: elif avatarUrl.endswith('.webp') or '.webp?' in avatarUrl:
sessionHeaders = {'Accept': 'image/webp'} sessionHeaders={
'Accept': 'image/webp'
}
avatarImageFilename=avatarImagePath+'.webp' avatarImageFilename=avatarImagePath+'.webp'
else: else:
return None return None
@ -114,14 +122,14 @@ def updateAvatarImageCache(session,baseDir: str,httpPrefix: str, \
print('Failed to download avatar image: '+str(avatarUrl)) print('Failed to download avatar image: '+str(avatarUrl))
print(e) print(e)
if '/channel/' not in actor: if '/channel/' not in actor:
sessionHeaders = { sessionHeaders={
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"' 'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
} }
else: else:
sessionHeaders = { sessionHeaders={
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' 'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
} }
personJson = \ personJson= \
getJson(session,actor,sessionHeaders,None,__version__, \ getJson(session,actor,sessionHeaders,None,__version__, \
httpPrefix,None) httpPrefix,None)
if personJson: if personJson:
@ -147,7 +155,7 @@ def updateAvatarImageCache(session,baseDir: str,httpPrefix: str, \
def getPersonAvatarUrl(baseDir: str,personUrl: str,personCache: {}) -> str: def getPersonAvatarUrl(baseDir: str,personUrl: str,personCache: {}) -> str:
"""Returns the avatar url for the person """Returns the avatar url for the person
""" """
personJson = getPersonFromCache(baseDir,personUrl,personCache) personJson=getPersonFromCache(baseDir,personUrl,personCache)
if not personJson: if not personJson:
return None return None
# get from locally stored image # get from locally stored image
@ -204,10 +212,10 @@ def htmlSearchEmoji(translate: {},baseDir: str,httpPrefix: str, \
results={} results={}
for emojiName,filename in emojiJson.items(): for emojiName,filename in emojiJson.items():
if searchStr in emojiName: if searchStr in emojiName:
results[emojiName] = filename+'.png' results[emojiName]=filename+'.png'
for emojiName,filename in emojiJson.items(): for emojiName,filename in emojiJson.items():
if emojiName in searchStr: if emojiName in searchStr:
results[emojiName] = filename+'.png' results[emojiName]=filename+'.png'
headingShown=False headingShown=False
emojiForm+='<center>' emojiForm+='<center>'
for emojiName,filename in results.items(): for emojiName,filename in results.items():
@ -382,7 +390,7 @@ def htmlModerationInfo(translate: {},baseDir: str,httpPrefix: str) -> str:
suspendedFilename=baseDir+'/accounts/suspended.txt' suspendedFilename=baseDir+'/accounts/suspended.txt'
if os.path.isfile(suspendedFilename): if os.path.isfile(suspendedFilename):
with open(suspendedFilename, "r") as f: with open(suspendedFilename, "r") as f:
suspendedStr = f.read() suspendedStr=f.read()
infoForm+='<div class="container">' infoForm+='<div class="container">'
infoForm+=' <br><b>'+translate['Suspended accounts']+'</b>' infoForm+=' <br><b>'+translate['Suspended accounts']+'</b>'
infoForm+=' <br>'+translate['These are currently suspended'] infoForm+=' <br>'+translate['These are currently suspended']
@ -395,7 +403,7 @@ def htmlModerationInfo(translate: {},baseDir: str,httpPrefix: str) -> str:
blockingFilename=baseDir+'/accounts/blocking.txt' blockingFilename=baseDir+'/accounts/blocking.txt'
if os.path.isfile(blockingFilename): if os.path.isfile(blockingFilename):
with open(blockingFilename, "r") as f: with open(blockingFilename, "r") as f:
blockedStr = f.read() blockedStr=f.read()
infoForm+='<div class="container">' infoForm+='<div class="container">'
infoForm+= \ infoForm+= \
' <br><b>'+translate['Blocked accounts and hashtags']+'</b>' ' <br><b>'+translate['Blocked accounts and hashtags']+'</b>'
@ -437,14 +445,14 @@ def htmlHashtagSearch(nickname: str,domain: str,port: int, \
# read the index # read the index
with open(hashtagIndexFile, "r") as f: with open(hashtagIndexFile, "r") as f:
lines = f.readlines() lines=f.readlines()
# read the css # read the css
cssFilename=baseDir+'/epicyon-profile.css' cssFilename=baseDir+'/epicyon-profile.css'
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
hashtagSearchCSS = cssFile.read() hashtagSearchCSS=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
hashtagSearchCSS= \ hashtagSearchCSS= \
hashtagSearchCSS.replace('https://',httpPrefix+'://') hashtagSearchCSS.replace('https://',httpPrefix+'://')
@ -546,7 +554,7 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
continue continue
if f.startswith('inbox@'): if f.startswith('inbox@'):
continue continue
actorFilename = os.path.join(subdir, f) actorFilename=os.path.join(subdir, f)
actorJson=loadJson(actorFilename) actorJson=loadJson(actorFilename)
if actorJson: if actorJson:
if actorJson.get('id') and \ if actorJson.get('id') and \
@ -556,17 +564,19 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
actor=actorJson['id'] actor=actorJson['id']
for skillName,skillLevel in actorJson['skills'].items(): for skillName,skillLevel in actorJson['skills'].items():
skillName=skillName.lower() skillName=skillName.lower()
if skillName in skillsearch or skillsearch in skillName: if not (skillName in skillsearch or \
skillLevelStr=str(skillLevel) skillsearch in skillName):
if skillLevel<100: continue
skillLevelStr='0'+skillLevelStr skillLevelStr=str(skillLevel)
if skillLevel<10: if skillLevel<100:
skillLevelStr='0'+skillLevelStr skillLevelStr='0'+skillLevelStr
indexStr= \ if skillLevel<10:
skillLevelStr+';'+actor+';'+actorJson['name']+ \ skillLevelStr='0'+skillLevelStr
';'+actorJson['icon']['url'] indexStr= \
if indexStr not in results: skillLevelStr+';'+actor+';'+actorJson['name']+ \
results.append(indexStr) ';'+actorJson['icon']['url']
if indexStr not in results:
results.append(indexStr)
if not instanceOnly: if not instanceOnly:
# search actor cache # search actor cache
for subdir, dirs, files in os.walk(baseDir+'/cache/actors/'): for subdir, dirs, files in os.walk(baseDir+'/cache/actors/'):
@ -577,7 +587,7 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
continue continue
if f.startswith('inbox@'): if f.startswith('inbox@'):
continue continue
actorFilename = os.path.join(subdir, f) actorFilename=os.path.join(subdir, f)
cachedActorJson=loadJson(actorFilename) cachedActorJson=loadJson(actorFilename)
if cachedActorJson: if cachedActorJson:
if cachedActorJson.get('actor'): if cachedActorJson.get('actor'):
@ -589,17 +599,19 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
actor=actorJson['id'] actor=actorJson['id']
for skillName,skillLevel in actorJson['skills'].items(): for skillName,skillLevel in actorJson['skills'].items():
skillName=skillName.lower() skillName=skillName.lower()
if skillName in skillsearch or skillsearch in skillName: if not (skillName in skillsearch or \
skillLevelStr=str(skillLevel) skillsearch in skillName):
if skillLevel<100: continue
skillLevelStr='0'+skillLevelStr skillLevelStr=str(skillLevel)
if skillLevel<10: if skillLevel<100:
skillLevelStr='0'+skillLevelStr skillLevelStr='0'+skillLevelStr
indexStr= \ if skillLevel<10:
skillLevelStr+';'+actor+';'+actorJson['name']+ \ skillLevelStr='0'+skillLevelStr
';'+actorJson['icon']['url'] indexStr= \
if indexStr not in results: skillLevelStr+';'+actor+';'+actorJson['name']+ \
results.append(indexStr) ';'+actorJson['icon']['url']
if indexStr not in results:
results.append(indexStr)
results.sort(reverse=True) results.sort(reverse=True)
@ -607,7 +619,7 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
skillSearchCSS = cssFile.read() skillSearchCSS=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
skillSearchCSS=skillSearchCSS.replace('https://',httpPrefix+'://') skillSearchCSS=skillSearchCSS.replace('https://',httpPrefix+'://')
skillSearchForm=htmlHeader(cssFilename,skillSearchCSS) skillSearchForm=htmlHeader(cssFilename,skillSearchCSS)
@ -622,17 +634,20 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
ctr=0 ctr=0
for skillMatch in results: for skillMatch in results:
skillMatchFields=skillMatch.split(';') skillMatchFields=skillMatch.split(';')
if len(skillMatchFields)==4: if len(skillMatchFields)!=4:
actor=skillMatchFields[1] continue
actorName=skillMatchFields[2] actor=skillMatchFields[1]
avatarUrl=skillMatchFields[3] actorName=skillMatchFields[2]
skillSearchForm+='<div class="search-result""><a href="'+actor+'/skills">' avatarUrl=skillMatchFields[3]
skillSearchForm+= \ skillSearchForm+= \
'<img loading="lazy" src="'+avatarUrl+ \ '<div class="search-result""><a href="'+actor+'/skills">'
'"/><span class="search-result-text">'+actorName+'</span></a></div>' skillSearchForm+= \
ctr+=1 '<img loading="lazy" src="'+avatarUrl+ \
if ctr>=postsPerPage: '"/><span class="search-result-text">'+actorName+ \
break '</span></a></div>'
ctr+=1
if ctr>=postsPerPage:
break
skillSearchForm+='</center>' skillSearchForm+='</center>'
skillSearchForm+=htmlFooter() skillSearchForm+=htmlFooter()
return skillSearchForm return skillSearchForm
@ -640,7 +655,8 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
def scheduledPostsExist(baseDir: str,nickname: str,domain: str) -> bool: def scheduledPostsExist(baseDir: str,nickname: str,domain: str) -> bool:
"""Returns true if there are posts scheduled to be delivered """Returns true if there are posts scheduled to be delivered
""" """
scheduleIndexFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/schedule.index' scheduleIndexFilename= \
baseDir+'/accounts/'+nickname+'@'+domain+'/schedule.index'
if not os.path.isfile(scheduleIndexFilename): if not os.path.isfile(scheduleIndexFilename):
return False return False
if '#users#' in open(scheduleIndexFilename).read(): if '#users#' in open(scheduleIndexFilename).read():
@ -653,7 +669,8 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
""" """
imageFormats='.png, .jpg, .jpeg, .gif, .webp' imageFormats='.png, .jpg, .jpeg, .gif, .webp'
pathOriginal=path pathOriginal=path
path=path.replace('/inbox','').replace('/outbox','').replace('/shares','') path= \
path.replace('/inbox','').replace('/outbox','').replace('/shares','')
nickname=getNicknameFromActor(path) nickname=getNicknameFromActor(path)
if not nickname: if not nickname:
return '' return ''
@ -730,13 +747,15 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
switchStr=switchfile.read() switchStr=switchfile.read()
blockedStr='' blockedStr=''
blockedFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt' blockedFilename= \
baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
if os.path.isfile(blockedFilename): if os.path.isfile(blockedFilename):
with open(blockedFilename, 'r') as blockedfile: with open(blockedFilename, 'r') as blockedfile:
blockedStr=blockedfile.read() blockedStr=blockedfile.read()
allowedInstancesStr='' allowedInstancesStr=''
allowedInstancesFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/allowedinstances.txt' allowedInstancesFilename= \
baseDir+'/accounts/'+nickname+'@'+domain+'/allowedinstances.txt'
if os.path.isfile(allowedInstancesFilename): if os.path.isfile(allowedInstancesFilename):
with open(allowedInstancesFilename, 'r') as allowedInstancesFile: with open(allowedInstancesFilename, 'r') as allowedInstancesFile:
allowedInstancesStr=allowedInstancesFile.read() allowedInstancesStr=allowedInstancesFile.read()
@ -766,7 +785,7 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
editProfileCSS = cssFile.read() editProfileCSS=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
editProfileCSS=editProfileCSS.replace('https://',httpPrefix+'://') editProfileCSS=editProfileCSS.replace('https://',httpPrefix+'://')
@ -804,7 +823,7 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
moderatorsFile=baseDir+'/accounts/moderators.txt' moderatorsFile=baseDir+'/accounts/moderators.txt'
if os.path.isfile(moderatorsFile): if os.path.isfile(moderatorsFile):
with open(moderatorsFile, "r") as f: with open(moderatorsFile, "r") as f:
moderators = f.read() moderators=f.read()
moderatorsStr='<div class="container">' moderatorsStr='<div class="container">'
moderatorsStr+=' <b>'+translate['Moderators']+'</b><br>' moderatorsStr+=' <b>'+translate['Moderators']+'</b><br>'
moderatorsStr+=' '+translate['A list of moderator nicknames. One per line.'] moderatorsStr+=' '+translate['A list of moderator nicknames. One per line.']
@ -928,7 +947,8 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
editProfileForm+=htmlFooter() editProfileForm+=htmlFooter()
return editProfileForm return editProfileForm
def htmlGetLoginCredentials(loginParams: str,lastLoginTime: int) -> (str,str,bool): def htmlGetLoginCredentials(loginParams: str, \
lastLoginTime: int) -> (str,str,bool):
"""Receives login credentials via HTTPServer POST """Receives login credentials via HTTPServer POST
""" """
if not loginParams.startswith('username='): if not loginParams.startswith('username='):
@ -997,13 +1017,13 @@ def htmlLogin(translate: {},baseDir: str,autocomplete=True) -> str:
if os.path.isfile(baseDir+'/accounts/login.txt'): if os.path.isfile(baseDir+'/accounts/login.txt'):
# custom login message # custom login message
with open(baseDir+'/accounts/login.txt', 'r') as file: with open(baseDir+'/accounts/login.txt', 'r') as file:
loginText = '<p class="login-text">'+file.read()+'</p>' loginText='<p class="login-text">'+file.read()+'</p>'
cssFilename=baseDir+'/epicyon-login.css' cssFilename=baseDir+'/epicyon-login.css'
if os.path.isfile(baseDir+'/login.css'): if os.path.isfile(baseDir+'/login.css'):
cssFilename=baseDir+'/login.css' cssFilename=baseDir+'/login.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
loginCSS = cssFile.read() loginCSS=cssFile.read()
# show the register button # show the register button
registerButtonStr='' registerButtonStr=''
@ -1065,7 +1085,7 @@ def htmlLogin(translate: {},baseDir: str,autocomplete=True) -> str:
def htmlTermsOfService(baseDir: str,httpPrefix: str,domainFull: str) -> str: def htmlTermsOfService(baseDir: str,httpPrefix: str,domainFull: str) -> str:
"""Show the terms of service screen """Show the terms of service screen
""" """
adminNickname = getConfigParam(baseDir,'admin') adminNickname=getConfigParam(baseDir,'admin')
if not os.path.isfile(baseDir+'/accounts/tos.txt'): if not os.path.isfile(baseDir+'/accounts/tos.txt'):
copyfile(baseDir+'/default_tos.txt',baseDir+'/accounts/tos.txt') copyfile(baseDir+'/default_tos.txt',baseDir+'/accounts/tos.txt')
if os.path.isfile(baseDir+'/img/login-background.png'): if os.path.isfile(baseDir+'/img/login-background.png'):
@ -1076,14 +1096,14 @@ def htmlTermsOfService(baseDir: str,httpPrefix: str,domainFull: str) -> str:
TOSText='Terms of Service go here.' TOSText='Terms of Service go here.'
if os.path.isfile(baseDir+'/accounts/tos.txt'): if os.path.isfile(baseDir+'/accounts/tos.txt'):
with open(baseDir+'/accounts/tos.txt', 'r') as file: with open(baseDir+'/accounts/tos.txt', 'r') as file:
TOSText = file.read() TOSText=file.read()
TOSForm='' TOSForm=''
cssFilename=baseDir+'/epicyon-profile.css' cssFilename=baseDir+'/epicyon-profile.css'
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
termsCSS = cssFile.read() termsCSS=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
termsCSS=termsCSS.replace('https://',httpPrefix+'://') termsCSS=termsCSS.replace('https://',httpPrefix+'://')
@ -1100,7 +1120,7 @@ def htmlTermsOfService(baseDir: str,httpPrefix: str,domainFull: str) -> str:
def htmlAbout(baseDir: str,httpPrefix: str,domainFull: str) -> str: def htmlAbout(baseDir: str,httpPrefix: str,domainFull: str) -> str:
"""Show the about screen """Show the about screen
""" """
adminNickname = getConfigParam(baseDir,'admin') adminNickname=getConfigParam(baseDir,'admin')
if not os.path.isfile(baseDir+'/accounts/about.txt'): if not os.path.isfile(baseDir+'/accounts/about.txt'):
copyfile(baseDir+'/default_about.txt',baseDir+'/accounts/about.txt') copyfile(baseDir+'/default_about.txt',baseDir+'/accounts/about.txt')
if os.path.isfile(baseDir+'/img/login-background.png'): if os.path.isfile(baseDir+'/img/login-background.png'):
@ -1111,14 +1131,14 @@ def htmlAbout(baseDir: str,httpPrefix: str,domainFull: str) -> str:
aboutText='Information about this instance goes here.' aboutText='Information about this instance goes here.'
if os.path.isfile(baseDir+'/accounts/about.txt'): if os.path.isfile(baseDir+'/accounts/about.txt'):
with open(baseDir+'/accounts/about.txt', 'r') as file: with open(baseDir+'/accounts/about.txt', 'r') as file:
aboutText = file.read() aboutText=file.read()
aboutForm='' aboutForm=''
cssFilename=baseDir+'/epicyon-profile.css' cssFilename=baseDir+'/epicyon-profile.css'
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
termsCSS = cssFile.read() termsCSS=cssFile.read()
if httpPrefix!='http': if httpPrefix!='http':
termsCSS=termsCSS.replace('https://',httpPrefix+'://') termsCSS=termsCSS.replace('https://',httpPrefix+'://')
@ -1231,13 +1251,13 @@ def htmlNewPost(mediaInstance: bool,translate: {}, \
if os.path.isfile(baseDir+'/accounts/newpost.txt'): if os.path.isfile(baseDir+'/accounts/newpost.txt'):
with open(baseDir+'/accounts/newpost.txt', 'r') as file: with open(baseDir+'/accounts/newpost.txt', 'r') as file:
newPostText = '<p class="new-post-text">'+file.read()+'</p>' newPostText='<p class="new-post-text">'+file.read()+'</p>'
cssFilename=baseDir+'/epicyon-profile.css' cssFilename=baseDir+'/epicyon-profile.css'
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
newPostCSS = cssFile.read() newPostCSS=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
newPostCSS=newPostCSS.replace('https://',httpPrefix+'://') newPostCSS=newPostCSS.replace('https://',httpPrefix+'://')
@ -2017,7 +2037,7 @@ def htmlProfile(defaultTimeline: str, \
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = \ profileStyle= \
cssFile.read().replace('image.png', \ cssFile.read().replace('image.png', \
profileJson['image']['url']) profileJson['image']['url'])
@ -2085,7 +2105,7 @@ def individualFollowAsHtml(translate: {}, \
if not avatarUrl: if not avatarUrl:
avatarUrl=followUrl+'/avatar.png' avatarUrl=followUrl+'/avatar.png'
if domain not in followUrl: if domain not in followUrl:
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl2,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl2,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,nickname,domain,'outbox') projectVersion,httpPrefix,nickname,domain,'outbox')
if avatarUrl2: if avatarUrl2:
@ -2131,8 +2151,8 @@ def cursorToEndOfMessageScript() -> str:
This avoids the cursor being in the wrong position when replying This avoids the cursor being in the wrong position when replying
""" """
script='function focusOnMessage() {\n' script='function focusOnMessage() {\n'
script+=" var replyTextArea = document.getElementById('message');\n" script+=" var replyTextArea=document.getElementById('message');\n"
script+=' val = replyTextArea.value;\n' script+=' val=replyTextArea.value;\n'
script+=' if ((val.length>0) && (val.charAt(val.length-1) != " ")) {\n' script+=' if ((val.length>0) && (val.charAt(val.length-1) != " ")) {\n'
script+=' val += " ";\n' script+=' val += " ";\n'
script+=' }\n' script+=' }\n'
@ -2140,8 +2160,8 @@ def cursorToEndOfMessageScript() -> str:
script+=' replyTextArea.value="";\n' script+=' replyTextArea.value="";\n'
script+=' replyTextArea.value=val;\n' script+=' replyTextArea.value=val;\n'
script+='}\n' script+='}\n'
script+="var replyTextArea = document.getElementById('message')\n" script+="var replyTextArea=document.getElementById('message')\n"
script+='replyTextArea.onFocus = function() {\n' script+='replyTextArea.onFocus=function() {\n'
script+=' focusOnMessage();' script+=' focusOnMessage();'
script+='}\n' script+='}\n'
return script return script
@ -2150,11 +2170,11 @@ def contentWarningScript() -> str:
"""Returns a script used for content warnings """Returns a script used for content warnings
""" """
script='function showContentWarning(postID) {\n' script='function showContentWarning(postID) {\n'
script+=' var x = document.getElementById(postID);\n' script+=' var x=document.getElementById(postID);\n'
script+=' if (x.style.display !== "block") {\n' script+=' if (x.style.display !== "block") {\n'
script+=' x.style.display = "block";\n' script+=' x.style.display="block";\n'
script+=' } else {\n' script+=' } else {\n'
script+=' x.style.display = "none";\n' script+=' x.style.display="none";\n'
script+=' }\n' script+=' }\n'
script+='}\n' script+='}\n'
return script return script
@ -2164,8 +2184,8 @@ def contentWarningScriptOpen() -> str:
The warning is open by default. This is used on blog replies. The warning is open by default. This is used on blog replies.
""" """
script='function showContentWarning(postID) {\n' script='function showContentWarning(postID) {\n'
script+=' var x = document.getElementById(postID);\n' script+=' var x=document.getElementById(postID);\n'
script+=' x.style.display = "block";\n' script+=' x.style.display="block";\n'
script+='}\n' script+='}\n'
return script return script
@ -2531,7 +2551,7 @@ def loadIndividualPostAsHtmlFromCache(baseDir: str,nickname: str,domain: str, \
while tries<3: while tries<3:
try: try:
with open(cachedPostFilename, 'r') as file: with open(cachedPostFilename, 'r') as file:
postHtml = file.read() postHtml=file.read()
break break
except Exception as e: except Exception as e:
print(e) print(e)
@ -2847,7 +2867,7 @@ def individualPostAsHtml(recentPostsCache: {},maxRecentPosts: int, \
avatarUrl=postActor+'/avatar.png' avatarUrl=postActor+'/avatar.png'
if fullDomain not in postActor: if fullDomain not in postActor:
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl2,displayName = \ inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl2,displayName= \
getPersonBox(baseDir,session,wfRequest,personCache, \ getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,nickname,domain,'outbox') projectVersion,httpPrefix,nickname,domain,'outbox')
if avatarUrl2: if avatarUrl2:
@ -3267,15 +3287,15 @@ def individualPostAsHtml(recentPostsCache: {},maxRecentPosts: int, \
publishedStr=postJsonObject['object']['published'] publishedStr=postJsonObject['object']['published']
if '.' not in publishedStr: if '.' not in publishedStr:
if '+' not in publishedStr: if '+' not in publishedStr:
datetimeObject = \ datetimeObject= \
datetime.strptime(publishedStr,"%Y-%m-%dT%H:%M:%SZ") datetime.strptime(publishedStr,"%Y-%m-%dT%H:%M:%SZ")
else: else:
datetimeObject = \ datetimeObject= \
datetime.strptime(publishedStr.split('+')[0]+'Z', \ datetime.strptime(publishedStr.split('+')[0]+'Z', \
"%Y-%m-%dT%H:%M:%SZ") "%Y-%m-%dT%H:%M:%SZ")
else: else:
publishedStr=publishedStr.replace('T',' ').split('.')[0] publishedStr=publishedStr.replace('T',' ').split('.')[0]
datetimeObject = parse(publishedStr) datetimeObject=parse(publishedStr)
publishedStr=datetimeObject.strftime("%a %b %d, %H:%M") publishedStr=datetimeObject.strftime("%a %b %d, %H:%M")
publishedLink=messageId publishedLink=messageId
@ -3459,7 +3479,7 @@ def htmlTimeline(defaultTimeline: str, \
bannerFile='banner.webp' bannerFile='banner.webp'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = \ profileStyle= \
cssFile.read().replace('banner.png', \ cssFile.read().replace('banner.png', \
'/users/'+nickname+'/'+bannerFile) '/users/'+nickname+'/'+bannerFile)
if httpPrefix!='https': if httpPrefix!='https':
@ -4069,7 +4089,7 @@ def htmlRemoveSharedItem(translate: {},baseDir: str,actor: str,shareName: str) -
if os.path.isfile(baseDir+'/follow.css'): if os.path.isfile(baseDir+'/follow.css'):
cssFilename=baseDir+'/follow.css' cssFilename=baseDir+'/follow.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
sharesStr=htmlHeader(cssFilename,profileStyle) sharesStr=htmlHeader(cssFilename,profileStyle)
sharesStr+='<div class="follow">' sharesStr+='<div class="follow">'
sharesStr+=' <div class="followAvatar">' sharesStr+=' <div class="followAvatar">'
@ -4127,7 +4147,7 @@ def htmlDeletePost(recentPostsCache: {},maxRecentPosts: int, \
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
profileStyle=profileStyle.replace('https://',httpPrefix+'://') profileStyle=profileStyle.replace('https://',httpPrefix+'://')
deletePostStr=htmlHeader(cssFilename,profileStyle) deletePostStr=htmlHeader(cssFilename,profileStyle)
@ -4191,7 +4211,7 @@ def htmlCalendarDeleteConfirm(translate: {},baseDir: str, \
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
if httpPrefix!='https': if httpPrefix!='https':
profileStyle=profileStyle.replace('https://',httpPrefix+'://') profileStyle=profileStyle.replace('https://',httpPrefix+'://')
deletePostStr=htmlHeader(cssFilename,profileStyle) deletePostStr=htmlHeader(cssFilename,profileStyle)
@ -4235,7 +4255,7 @@ def htmlFollowConfirm(translate: {},baseDir: str, \
if os.path.isfile(baseDir+'/follow.css'): if os.path.isfile(baseDir+'/follow.css'):
cssFilename=baseDir+'/follow.css' cssFilename=baseDir+'/follow.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
followStr=htmlHeader(cssFilename,profileStyle) followStr=htmlHeader(cssFilename,profileStyle)
followStr+='<div class="follow">' followStr+='<div class="follow">'
followStr+=' <div class="followAvatar">' followStr+=' <div class="followAvatar">'
@ -4277,7 +4297,7 @@ def htmlUnfollowConfirm(translate: {},baseDir: str, \
if os.path.isfile(baseDir+'/follow.css'): if os.path.isfile(baseDir+'/follow.css'):
cssFilename=baseDir+'/follow.css' cssFilename=baseDir+'/follow.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
followStr=htmlHeader(cssFilename,profileStyle) followStr=htmlHeader(cssFilename,profileStyle)
followStr+='<div class="follow">' followStr+='<div class="follow">'
followStr+=' <div class="followAvatar">' followStr+=' <div class="followAvatar">'
@ -4352,7 +4372,7 @@ def htmlPersonOptions(translate: {},baseDir: str, \
if os.path.isfile(baseDir+'/follow.css'): if os.path.isfile(baseDir+'/follow.css'):
cssFilename=baseDir+'/follow.css' cssFilename=baseDir+'/follow.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
# To snooze, or not to snooze? That is the question # To snooze, or not to snooze? That is the question
snoozeButtonStr='Snooze' snoozeButtonStr='Snooze'
@ -4438,7 +4458,7 @@ def htmlPersonOptions(translate: {},baseDir: str, \
# copyfile(baseDir+'/img/block-background.png',baseDir+'/accounts/block-background.png') # copyfile(baseDir+'/img/block-background.png',baseDir+'/accounts/block-background.png')
# #
# with open(baseDir+'/epicyon-follow.css', 'r') as cssFile: # with open(baseDir+'/epicyon-follow.css', 'r') as cssFile:
# profileStyle = cssFile.read() # profileStyle=cssFile.read()
# blockStr=htmlHeader(cssFilename,profileStyle) # blockStr=htmlHeader(cssFilename,profileStyle)
# blockStr+='<div class="block">' # blockStr+='<div class="block">'
# blockStr+=' <div class="blockAvatar">' # blockStr+=' <div class="blockAvatar">'
@ -4474,7 +4494,7 @@ def htmlUnblockConfirm(translate: {},baseDir: str, \
if os.path.isfile(baseDir+'/follow.css'): if os.path.isfile(baseDir+'/follow.css'):
cssFilename=baseDir+'/follow.css' cssFilename=baseDir+'/follow.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
blockStr=htmlHeader(cssFilename,profileStyle) blockStr=htmlHeader(cssFilename,profileStyle)
blockStr+='<div class="block">' blockStr+='<div class="block">'
blockStr+=' <div class="blockAvatar">' blockStr+=' <div class="blockAvatar">'
@ -4522,7 +4542,7 @@ def htmlSearchEmojiTextEntry(translate: {}, \
if os.path.isfile(baseDir+'/follow.css'): if os.path.isfile(baseDir+'/follow.css'):
cssFilename=baseDir+'/follow.css' cssFilename=baseDir+'/follow.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
emojiStr=htmlHeader(cssFilename,profileStyle) emojiStr=htmlHeader(cssFilename,profileStyle)
emojiStr+='<div class="follow">' emojiStr+='<div class="follow">'
emojiStr+=' <div class="followAvatar">' emojiStr+=' <div class="followAvatar">'
@ -4567,7 +4587,7 @@ def htmlCalendarDay(translate: {}, \
if os.path.isfile(baseDir+'/calendar.css'): if os.path.isfile(baseDir+'/calendar.css'):
cssFilename=baseDir+'/calendar.css' cssFilename=baseDir+'/calendar.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
calendarStyle = cssFile.read() calendarStyle=cssFile.read()
calendarStr=htmlHeader(cssFilename,calendarStyle) calendarStr=htmlHeader(cssFilename,calendarStyle)
calendarStr+='<main><table class="calendar">\n' calendarStr+='<main><table class="calendar">\n'
@ -4740,7 +4760,7 @@ def htmlCalendar(translate: {}, \
if os.path.isfile(baseDir+'/calendar.css'): if os.path.isfile(baseDir+'/calendar.css'):
cssFilename=baseDir+'/calendar.css' cssFilename=baseDir+'/calendar.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
calendarStyle = cssFile.read() calendarStyle=cssFile.read()
calendarStr=htmlHeader(cssFilename,calendarStyle) calendarStr=htmlHeader(cssFilename,calendarStyle)
calendarStr+='<main><table class="calendar">\n' calendarStr+='<main><table class="calendar">\n'
@ -4890,7 +4910,7 @@ def htmlSearch(translate: {}, \
if os.path.isfile(baseDir+'/follow.css'): if os.path.isfile(baseDir+'/follow.css'):
cssFilename=baseDir+'/follow.css' cssFilename=baseDir+'/follow.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
profileStyle = cssFile.read() profileStyle=cssFile.read()
followStr=htmlHeader(cssFilename,profileStyle) followStr=htmlHeader(cssFilename,profileStyle)
followStr+='<div class="follow">' followStr+='<div class="follow">'
followStr+=' <div class="followAvatar">' followStr+=' <div class="followAvatar">'
@ -4964,7 +4984,7 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
if os.path.isfile(baseDir+'/epicyon.css'): if os.path.isfile(baseDir+'/epicyon.css'):
cssFilename=baseDir+'/epicyon.css' cssFilename=baseDir+'/epicyon.css'
with open(cssFilename, 'r') as cssFile: with open(cssFilename, 'r') as cssFile:
wf = \ wf= \
webfingerHandle(session, \ webfingerHandle(session, \
searchNickname+'@'+searchDomainFull, \ searchNickname+'@'+searchDomainFull, \
httpPrefix,wfRequest, \ httpPrefix,wfRequest, \
@ -4977,21 +4997,23 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
if wf.get('errors'): if wf.get('errors'):
personUrl=httpPrefix+'://'+searchDomainFull+'/users/'+searchNickname personUrl=httpPrefix+'://'+searchDomainFull+'/users/'+searchNickname
asHeader = { asHeader={
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"' 'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
} }
if not personUrl: if not personUrl:
personUrl = getUserUrl(wf) personUrl=getUserUrl(wf)
if not personUrl: if not personUrl:
# try single user instance # try single user instance
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader={
personUrl=httpPrefix+'://'+searchDomainFull
profileJson = getJson(session,personUrl,asHeader,None,projectVersion,httpPrefix,domain)
if not profileJson:
asHeader = {
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' 'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
} }
profileJson = \ personUrl=httpPrefix+'://'+searchDomainFull
profileJson=getJson(session,personUrl,asHeader,None,projectVersion,httpPrefix,domain)
if not profileJson:
asHeader={
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
profileJson= \
getJson(session,personUrl,asHeader,None,projectVersion,httpPrefix,domain) getJson(session,personUrl,asHeader,None,projectVersion,httpPrefix,domain)
if not profileJson: if not profileJson:
if debug: if debug:
@ -5021,7 +5043,7 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
if profileJson['image'].get('url'): if profileJson['image'].get('url'):
profileBackgroundImage=profileJson['image']['url'] profileBackgroundImage=profileJson['image']['url']
profileStyle = cssFile.read().replace('image.png',profileBackgroundImage) profileStyle=cssFile.read().replace('image.png',profileBackgroundImage)
if httpPrefix!='https': if httpPrefix!='https':
profileStyle=profileStyle.replace('https://',httpPrefix+'://') profileStyle=profileStyle.replace('https://',httpPrefix+'://')
# url to return to # url to return to
@ -5074,8 +5096,8 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
profileStr+='<script>'+contentWarningScript()+'</script>' profileStr+='<script>'+contentWarningScript()+'</script>'
iconsDir=getIconsDir(baseDir) iconsDir=getIconsDir(baseDir)
result = [] result=[]
i = 0 i=0
for item in parseUserFeed(session,outboxUrl,asHeader, \ for item in parseUserFeed(session,outboxUrl,asHeader, \
projectVersion,httpPrefix,domain): projectVersion,httpPrefix,domain):
if not item.get('type'): if not item.get('type'):

14
xmpp.py
View File

@ -1,10 +1,10 @@
__filename__ = "xmpp.py" __filename__="xmpp.py"
__author__ = "Bob Mottram" __author__="Bob Mottram"
__license__ = "AGPL3+" __license__="AGPL3+"
__version__ = "1.1.0" __version__="1.1.0"
__maintainer__ = "Bob Mottram" __maintainer__="Bob Mottram"
__email__ = "bob@freedombone.net" __email__="bob@freedombone.net"
__status__ = "Production" __status__="Production"
import json import json