mirror of https://gitlab.com/bashrc2/epicyon
Tidying
parent
e5e6cad1b4
commit
d0884fa04d
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "acceptreject.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="acceptreject.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import json
|
||||
|
@ -39,7 +39,7 @@ def createAcceptReject(baseDir: str,federationList: [], \
|
|||
if ':' not in domain:
|
||||
domain=domain+':'+str(port)
|
||||
|
||||
newAccept = {
|
||||
newAccept={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': acceptType,
|
||||
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
|
||||
|
|
58
announce.py
58
announce.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "announce.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="announce.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
@ -162,7 +162,7 @@ def updateAnnounceCollection(recentPostsCache: {}, \
|
|||
if debug:
|
||||
print('DEBUG: Adding initial shares (announcements) to '+ \
|
||||
postUrl)
|
||||
announcementsJson = {
|
||||
announcementsJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'id': postUrl,
|
||||
'type': 'Collection',
|
||||
|
@ -236,10 +236,10 @@ def createAnnounce(session,baseDir: str,federationList: [], \
|
|||
if ':' not in domain:
|
||||
fullDomain=domain+':'+str(port)
|
||||
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
newAnnounceId= \
|
||||
httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber
|
||||
newAnnounce = {
|
||||
newAnnounce={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber,
|
||||
|
@ -254,7 +254,7 @@ def createAnnounce(session,baseDir: str,federationList: [], \
|
|||
if len(ccUrl)>0:
|
||||
newAnnounce['cc']=[ccUrl]
|
||||
if saveToFile:
|
||||
outboxDir = createOutboxDir(nickname,domain,baseDir)
|
||||
outboxDir=createOutboxDir(nickname,domain,baseDir)
|
||||
filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json'
|
||||
saveJson(newAnnounce,filename)
|
||||
|
||||
|
@ -291,8 +291,8 @@ def announcePublic(session,baseDir: str,federationList: [], \
|
|||
if ':' not in domain:
|
||||
fromDomain=domain+':'+str(port)
|
||||
|
||||
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers'
|
||||
toUrl='https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl=httpPrefix+'://'+fromDomain+'/users/'+nickname+'/followers'
|
||||
return createAnnounce(session,baseDir,federationList, \
|
||||
nickname,domain,port, \
|
||||
toUrl,ccUrl,httpPrefix, \
|
||||
|
@ -317,7 +317,7 @@ def repeatPost(session,baseDir: str,federationList: [], \
|
|||
if ':' not in announcedDomain:
|
||||
announcedDomain=announcedDomain+':'+str(announcePort)
|
||||
|
||||
objectUrl = announceHttpsPrefix + '://'+announcedDomain+'/users/'+ \
|
||||
objectUrl=announceHttpsPrefix+'://'+announcedDomain+'/users/'+ \
|
||||
announceNickname+'/statuses/'+str(announceStatusNumber)
|
||||
|
||||
return announcePublic(session,baseDir,federationList, \
|
||||
|
@ -352,7 +352,7 @@ def undoAnnounce(session,baseDir: str,federationList: [], \
|
|||
if ':' not in domain:
|
||||
fullDomain=domain+':'+str(port)
|
||||
|
||||
newUndoAnnounce = {
|
||||
newUndoAnnounce={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
'type': 'Undo',
|
||||
|
@ -403,8 +403,8 @@ def undoAnnouncePublic(session,baseDir: str,federationList: [], \
|
|||
if ':' not in domain:
|
||||
fromDomain=domain+':'+str(port)
|
||||
|
||||
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers'
|
||||
toUrl='https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl=httpPrefix+'://'+fromDomain+'/users/'+nickname+'/followers'
|
||||
return undoAnnounce(session,baseDir,federationList, \
|
||||
nickname,domain,port, \
|
||||
toUrl,ccUrl,httpPrefix, \
|
||||
|
@ -429,7 +429,7 @@ def undoRepeatPost(session,baseDir: str,federationList: [], \
|
|||
if ':' not in announcedDomain:
|
||||
announcedDomain=announcedDomain+':'+str(announcePort)
|
||||
|
||||
objectUrl = announceHttpsPrefix + '://'+announcedDomain+'/users/'+ \
|
||||
objectUrl=announceHttpsPrefix+'://'+announcedDomain+'/users/'+ \
|
||||
announceNickname+'/statuses/'+str(announceStatusNumber)
|
||||
|
||||
return undoAnnouncePublic(session,baseDir,federationList, \
|
||||
|
@ -459,14 +459,14 @@ def sendAnnounceViaServer(baseDir: str,session, \
|
|||
if ':' not in fromDomain:
|
||||
fromDomainFull=fromDomain+':'+str(fromPort)
|
||||
|
||||
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
toUrl='https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
newAnnounceId= \
|
||||
httpPrefix+'://'+fromDomainFull+'/users/'+ \
|
||||
fromNickname+'/statuses/'+statusNumber
|
||||
newAnnounceJson = {
|
||||
newAnnounceJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
'atomUri': newAnnounceId,
|
||||
|
@ -481,7 +481,7 @@ def sendAnnounceViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = \
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
|
@ -492,7 +492,7 @@ def sendAnnounceViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname,fromDomain,postToBox)
|
||||
|
||||
|
@ -507,10 +507,12 @@ def sendAnnounceViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newAnnounceJson,[],inboxUrl,headers,"inbox:write")
|
||||
|
||||
if debug:
|
||||
|
|
44
auth.py
44
auth.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "auth.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="auth.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
|
@ -16,24 +16,24 @@ import random
|
|||
def hashPassword(password: str) -> str:
|
||||
"""Hash a password for storing
|
||||
"""
|
||||
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512', \
|
||||
salt=hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
|
||||
pwdhash=hashlib.pbkdf2_hmac('sha512', \
|
||||
password.encode('utf-8'), \
|
||||
salt, 100000)
|
||||
pwdhash = binascii.hexlify(pwdhash)
|
||||
return (salt + pwdhash).decode('ascii')
|
||||
pwdhash=binascii.hexlify(pwdhash)
|
||||
return (salt+pwdhash).decode('ascii')
|
||||
|
||||
def verifyPassword(storedPassword: str,providedPassword: str) -> bool:
|
||||
"""Verify a stored password against one provided by user
|
||||
"""
|
||||
salt = storedPassword[:64]
|
||||
storedPassword = storedPassword[64:]
|
||||
pwdhash = hashlib.pbkdf2_hmac('sha512', \
|
||||
salt=storedPassword[:64]
|
||||
storedPassword=storedPassword[64:]
|
||||
pwdhash=hashlib.pbkdf2_hmac('sha512', \
|
||||
providedPassword.encode('utf-8'), \
|
||||
salt.encode('ascii'), \
|
||||
100000)
|
||||
pwdhash = binascii.hexlify(pwdhash).decode('ascii')
|
||||
return pwdhash == storedPassword
|
||||
pwdhash=binascii.hexlify(pwdhash).decode('ascii')
|
||||
return pwdhash==storedPassword
|
||||
|
||||
def createBasicAuthHeader(nickname: str,password: str) -> str:
|
||||
"""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')
|
||||
return False
|
||||
nicknameFromPath=pathUsersSection.split('/')[0]
|
||||
base64Str = authHeader.split(' ')[1].replace('\n','')
|
||||
plain = base64.b64decode(base64Str).decode('utf-8')
|
||||
base64Str=authHeader.split(' ')[1].replace('\n','')
|
||||
plain=base64.b64decode(base64Str).decode('utf-8')
|
||||
if ':' not in plain:
|
||||
if debug:
|
||||
print('DEBUG: Basic Auth header does not contain a ":" separator for username:password')
|
||||
return False
|
||||
nickname = plain.split(':')[0]
|
||||
nickname=plain.split(':')[0]
|
||||
if nickname!=nicknameFromPath:
|
||||
if debug:
|
||||
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:
|
||||
print('DEBUG: passwords file missing')
|
||||
return False
|
||||
providedPassword = plain.split(':')[1]
|
||||
passfile = open(passwordFile, "r")
|
||||
providedPassword=plain.split(':')[1]
|
||||
passfile=open(passwordFile, "r")
|
||||
for line in passfile:
|
||||
if line.startswith(nickname+':'):
|
||||
storedPassword=line.split(':')[1].replace('\n','')
|
||||
success = verifyPassword(storedPassword,providedPassword)
|
||||
success=verifyPassword(storedPassword,providedPassword)
|
||||
if not success:
|
||||
if debug:
|
||||
print('DEBUG: Password check failed for '+nickname)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "availability.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="availability.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import time
|
||||
|
@ -89,10 +89,10 @@ def sendAvailabilityViaServer(baseDir: str,session, \
|
|||
if ':' not in domain:
|
||||
domainFull=domain+':'+str(port)
|
||||
|
||||
toUrl = httpPrefix+'://'+domainFull+'/users/'+nickname
|
||||
ccUrl = httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers'
|
||||
toUrl=httpPrefix+'://'+domainFull+'/users/'+nickname
|
||||
ccUrl=httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers'
|
||||
|
||||
newAvailabilityJson = {
|
||||
newAvailabilityJson={
|
||||
'type': 'Availability',
|
||||
'actor': httpPrefix+'://'+domainFull+'/users/'+nickname,
|
||||
'object': '"'+status+'"',
|
||||
|
@ -103,8 +103,9 @@ def sendAvailabilityViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+domainFull+'/@'+nickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
domain,projectVersion)
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
domain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -113,7 +114,7 @@ def sendAvailabilityViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,nickname,domain,postToBox)
|
||||
|
||||
|
@ -128,10 +129,12 @@ def sendAvailabilityViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(Nickname,password)
|
||||
|
||||
headers = {'host': domain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': domain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newAvailabilityJson,[],inboxUrl,headers,"inbox:write")
|
||||
|
||||
if debug:
|
||||
|
|
51
blocking.py
51
blocking.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "blocking.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="blocking.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
from utils import isEvil
|
||||
|
@ -174,10 +174,10 @@ def sendBlockViaServer(baseDir: str,session, \
|
|||
|
||||
toUrl= 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl= \
|
||||
httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
|
||||
blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
|
||||
newBlockJson = {
|
||||
newBlockJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Block',
|
||||
'actor': blockActor,
|
||||
|
@ -189,8 +189,9 @@ def sendBlockViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -199,7 +200,7 @@ def sendBlockViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -215,10 +216,12 @@ def sendBlockViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
|
||||
|
||||
if debug:
|
||||
|
@ -246,10 +249,10 @@ def sendUndoBlockViaServer(baseDir: str,session, \
|
|||
|
||||
toUrl= 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl= \
|
||||
httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
|
||||
blockActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
|
||||
newBlockJson = {
|
||||
newBlockJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Undo',
|
||||
'actor': blockActor,
|
||||
|
@ -276,7 +279,7 @@ def sendUndoBlockViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -292,10 +295,12 @@ def sendUndoBlockViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newBlockJson,[],inboxUrl,headers,"inbox:write")
|
||||
|
||||
if debug:
|
||||
|
|
24
blog.py
24
blog.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "blog.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="blog.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import time
|
||||
|
@ -63,12 +63,12 @@ def noOfBlogReplies(baseDir: str,httpPrefix: str,translate: {}, \
|
|||
|
||||
replies=0
|
||||
with open(postFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for replyPostId in lines:
|
||||
replyPostId= \
|
||||
replyPostId.replace('\n','').replace('.json','').replace('.replies','')
|
||||
replies+= \
|
||||
1 + \
|
||||
1+ \
|
||||
noOfBlogReplies(baseDir,httpPrefix,translate, \
|
||||
nickname,domain,domainFull, \
|
||||
replyPostId,depth+1)
|
||||
|
@ -111,7 +111,7 @@ def getBlogReplies(baseDir: str,httpPrefix: str,translate: {}, \
|
|||
return ''
|
||||
|
||||
with open(postFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
repliesStr=''
|
||||
for replyPostId in lines:
|
||||
replyPostId= \
|
||||
|
@ -556,13 +556,13 @@ def htmlEditBlog(mediaInstance: bool,translate: {}, \
|
|||
|
||||
if os.path.isfile(baseDir+'/accounts/newpost.txt'):
|
||||
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'
|
||||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
editBlogCSS = cssFile.read()
|
||||
editBlogCSS=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
editBlogCSS=editBlogCSS.replace('https://',httpPrefix+'://')
|
||||
|
||||
|
|
125
blurhash.py
125
blurhash.py
|
@ -32,16 +32,16 @@ Very close port of the original Swift implementation by Dag Ågren.
|
|||
import math
|
||||
|
||||
# Alphabet for base 83
|
||||
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
|
||||
alphabet_values = dict(zip(alphabet, range(len(alphabet))))
|
||||
alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
|
||||
alphabet_values=dict(zip(alphabet,range(len(alphabet))))
|
||||
|
||||
def base83_decode(base83_str):
|
||||
"""
|
||||
Decodes a base83 string, as used in blurhash, to an integer.
|
||||
"""
|
||||
value = 0
|
||||
value=0
|
||||
for base83_char in base83_str:
|
||||
value = value * 83 + alphabet_values[base83_char]
|
||||
value=value*83+alphabet_values[base83_char]
|
||||
return value
|
||||
|
||||
def base83_encode(value, length):
|
||||
|
@ -54,9 +54,9 @@ def base83_encode(value, length):
|
|||
if int(value) // (83 ** (length)) != 0:
|
||||
raise ValueError("Specified length is too short to encode given value.")
|
||||
|
||||
result = ""
|
||||
for i in range(1, length + 1):
|
||||
digit = int(value) // (83 ** (length - i)) % 83
|
||||
result=""
|
||||
for i in range(1,length+1):
|
||||
digit=int(value) // (83 ** (length - i)) % 83
|
||||
result += alphabet[int(digit)]
|
||||
return result
|
||||
|
||||
|
@ -64,10 +64,10 @@ def srgb_to_linear(value):
|
|||
"""
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
|
@ -79,10 +79,10 @@ def linear_to_srgb(value):
|
|||
"""
|
||||
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:
|
||||
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(value*12.92*255+0.5)
|
||||
return int((1.055*math.pow(value, 1 / 2.4)-0.055)*255+0.5)
|
||||
|
||||
def blurhash_components(blurhash):
|
||||
"""
|
||||
|
@ -92,13 +92,13 @@ def blurhash_components(blurhash):
|
|||
raise ValueError("BlurHash must be at least 6 characters long.")
|
||||
|
||||
# Decode metadata
|
||||
size_info = base83_decode(blurhash[0])
|
||||
size_y = int(size_info / 9) + 1
|
||||
size_x = (size_info % 9) + 1
|
||||
size_info=base83_decode(blurhash[0])
|
||||
size_y=int(size_info / 9)+1
|
||||
size_x=(size_info % 9)+1
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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.")
|
||||
|
||||
# Decode metadata
|
||||
size_info = base83_decode(blurhash[0])
|
||||
size_y = int(size_info / 9) + 1
|
||||
size_x = (size_info % 9) + 1
|
||||
size_info=base83_decode(blurhash[0])
|
||||
size_y=int(size_info / 9)+1
|
||||
size_x=(size_info % 9)+1
|
||||
|
||||
quant_max_value = base83_decode(blurhash[1])
|
||||
real_max_value = (float(quant_max_value + 1) / 166.0) * punch
|
||||
quant_max_value=base83_decode(blurhash[1])
|
||||
real_max_value=(float(quant_max_value+1)/166.0)*punch
|
||||
|
||||
# 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.")
|
||||
|
||||
# Decode DC component
|
||||
dc_value = base83_decode(blurhash[2:6])
|
||||
colours = [(
|
||||
dc_value=base83_decode(blurhash[2:6])
|
||||
colours=[(
|
||||
srgb_to_linear(dc_value >> 16),
|
||||
srgb_to_linear((dc_value >> 8) & 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
|
||||
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((
|
||||
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,
|
||||
# consumable by something like numpy or PIL.
|
||||
pixels = []
|
||||
pixels=[]
|
||||
for y in range(height):
|
||||
pixel_row = []
|
||||
pixel_row=[]
|
||||
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 i in range(size_x):
|
||||
basis = math.cos(math.pi * float(x) * float(i) / float(width)) * \
|
||||
math.cos(math.pi * float(y) * float(j) / float(height))
|
||||
colour = colours[i + j * size_x]
|
||||
pixel[0] += colour[0] * basis
|
||||
pixel[1] += colour[1] * basis
|
||||
pixel[2] += colour[2] * basis
|
||||
basis= \
|
||||
math.cos(math.pi*float(x)*float(i)/float(width))* \
|
||||
math.cos(math.pi*float(y)*float(j)/float(height))
|
||||
colour=colours[i+j*size_x]
|
||||
pixel[0] += colour[0]*basis
|
||||
pixel[1] += colour[1]*basis
|
||||
pixel[2] += colour[2]*basis
|
||||
if linear == False:
|
||||
pixel_row.append([
|
||||
linear_to_srgb(pixel[0]),
|
||||
|
@ -172,7 +173,7 @@ def blurhash_decode(blurhash, width, height, punch = 1.0, linear = False):
|
|||
pixels.append(pixel_row)
|
||||
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.
|
||||
|
||||
|
@ -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:
|
||||
raise ValueError("x and y component counts must be between 1 and 9 inclusive.")
|
||||
height = float(len(image))
|
||||
width = float(len(image[0]))
|
||||
height=float(len(image))
|
||||
width=float(len(image[0]))
|
||||
|
||||
# Convert to linear if neeeded
|
||||
image_linear = []
|
||||
if linear == False:
|
||||
image_linear=[]
|
||||
if linear==False:
|
||||
for y in range(int(height)):
|
||||
image_linear_line = []
|
||||
image_linear_line=[]
|
||||
for x in range(int(width)):
|
||||
image_linear_line.append([
|
||||
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)
|
||||
else:
|
||||
image_linear = image
|
||||
image_linear=image
|
||||
|
||||
# Calculate components
|
||||
components = []
|
||||
max_ac_component = 0.0
|
||||
components=[]
|
||||
max_ac_component=0.0
|
||||
for j in range(components_y):
|
||||
for i in range(components_x):
|
||||
norm_factor = 1.0 if (i == 0 and j == 0) else 2.0
|
||||
component = [0.0, 0.0, 0.0]
|
||||
norm_factor=1.0 if (i==0 and j==0) else 2.0
|
||||
component=[0.0,0.0,0.0]
|
||||
for y in range(int(height)):
|
||||
for x in range(int(width)):
|
||||
basis = norm_factor * math.cos(math.pi * float(i) * float(x) / width) * \
|
||||
math.cos(math.pi * float(j) * float(y) / height)
|
||||
basis= \
|
||||
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[1] += basis * image_linear[y][x][1]
|
||||
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)
|
||||
components.append(component)
|
||||
|
||||
if not (i == 0 and j == 0):
|
||||
max_ac_component = max(max_ac_component, abs(component[0]), abs(component[1]), abs(component[2]))
|
||||
if not (i==0 and j==0):
|
||||
max_ac_component= \
|
||||
max(max_ac_component,abs(component[0]), \
|
||||
abs(component[1]),abs(component[2]))
|
||||
|
||||
# Encode components
|
||||
dc_value = (linear_to_srgb(components[0][0]) << 16) + \
|
||||
(linear_to_srgb(components[0][1]) << 8) + \
|
||||
linear_to_srgb(components[0][2])
|
||||
dc_value= \
|
||||
(linear_to_srgb(components[0][0]) << 16)+ \
|
||||
(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))))
|
||||
ac_component_norm_factor = float(quant_max_ac_component + 1) / 166.0
|
||||
quant_max_ac_component= \
|
||||
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:]:
|
||||
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(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(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(b / ac_component_norm_factor, 0.5) * 9.0 + 9.5))))
|
||||
)
|
||||
|
||||
# Build final blurhash
|
||||
blurhash = ""
|
||||
blurhash=""
|
||||
blurhash += base83_encode((components_x - 1) + (components_y - 1) * 9, 1)
|
||||
blurhash += base83_encode(quant_max_ac_component, 1)
|
||||
blurhash += base83_encode(dc_value, 4)
|
||||
|
|
58
bookmarks.py
58
bookmarks.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "bookmarks.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="bookmarks.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import json
|
||||
|
@ -159,7 +159,7 @@ def updateBookmarksCollection(recentPostsCache: {}, \
|
|||
if not postJsonObject['object'].get('bookmarks'):
|
||||
if debug:
|
||||
print('DEBUG: Adding initial bookmarks to '+objectUrl)
|
||||
bookmarksJson = {
|
||||
bookmarksJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'id': objectUrl,
|
||||
'type': 'Collection',
|
||||
|
@ -198,7 +198,7 @@ def updateBookmarksCollection(recentPostsCache: {}, \
|
|||
if bookmarkIndex not in open(bookmarksIndexFilename).read():
|
||||
try:
|
||||
with open(bookmarksIndexFilename, 'r+') as bookmarksIndexFile:
|
||||
content = bookmarksIndexFile.read()
|
||||
content=bookmarksIndexFile.read()
|
||||
bookmarksIndexFile.seek(0, 0)
|
||||
bookmarksIndexFile.write(bookmarkIndex+'\n'+content)
|
||||
if debug:
|
||||
|
@ -239,7 +239,7 @@ def bookmark(recentPostsCache: {}, \
|
|||
if '/statuses/' in objectUrl:
|
||||
bookmarkTo=[objectUrl.split('/statuses/')[0]]
|
||||
|
||||
newBookmarkJson = {
|
||||
newBookmarkJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Bookmark',
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
|
@ -348,7 +348,7 @@ def undoBookmark(recentPostsCache: {}, \
|
|||
if '/statuses/' in objectUrl:
|
||||
bookmarkTo=[objectUrl.split('/statuses/')[0]]
|
||||
|
||||
newUndoBookmarkJson = {
|
||||
newUndoBookmarkJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Undo',
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
|
@ -414,8 +414,8 @@ def undoBookmarkPost(session,baseDir: str,federationList: [], \
|
|||
if ':' not in bookmarkedomain:
|
||||
bookmarkedomain=bookmarkedomain+':'+str(bookmarkPort)
|
||||
|
||||
objectUrl = \
|
||||
httpPrefix + '://'+bookmarkedomain+'/users/'+bookmarkNickname+ \
|
||||
objectUrl= \
|
||||
httpPrefix+'://'+bookmarkedomain+'/users/'+bookmarkNickname+ \
|
||||
'/statuses/'+str(bookmarkStatusNumber)
|
||||
|
||||
ccUrl=httpPrefix+'://'+bookmarkedomain+'/users/'+bookmarkNickname
|
||||
|
@ -454,7 +454,7 @@ def sendBookmarkViaServer(baseDir: str,session, \
|
|||
if '/statuses/' in bookmarkUrl:
|
||||
toUrl=[bookmarkUrl.split('/statuses/')[0]]
|
||||
|
||||
newBookmarkJson = {
|
||||
newBookmarkJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Bookmark',
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
|
@ -474,7 +474,7 @@ def sendBookmarkViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -490,10 +490,12 @@ def sendBookmarkViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newBookmarkJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
@ -529,7 +531,7 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
|
|||
if '/statuses/' in bookmarkUrl:
|
||||
toUrl=[bookmarkUrl.split('/statuses/')[0]]
|
||||
|
||||
newUndoBookmarkJson = {
|
||||
newUndoBookmarkJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Undo',
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
|
@ -543,8 +545,8 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -553,7 +555,7 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -569,10 +571,12 @@ def sendUndoBookmarkViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newUndoBookmarkJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
|
14
cache.py
14
cache.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "cache.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="cache.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "capabilities.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="capabilities.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import datetime
|
||||
|
@ -94,7 +94,7 @@ def capabilitiesRequest(baseDir: str,httpPrefix: str,domain: str, \
|
|||
# which could be instance wide or for a particular person
|
||||
# This could also be added to a follow activity
|
||||
ocapId=createPassword(32)
|
||||
ocapRequest = {
|
||||
ocapRequest={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": httpPrefix+"://"+requestedDomain+"/caps/request/"+ocapId,
|
||||
"type": "Request",
|
||||
|
@ -140,7 +140,7 @@ def capabilitiesAccept(baseDir: str,httpPrefix: str, \
|
|||
ocapId=acceptedActorNickname+'@'+acceptedActorDomain+':'+str(acceptedActorPort)+'#'+createPassword(32)
|
||||
else:
|
||||
ocapId=acceptedActorNickname+'@'+acceptedActorDomain+'#'+createPassword(32)
|
||||
ocapAccept = {
|
||||
ocapAccept={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": httpPrefix+"://"+fullDomain+"/caps/"+ocapId,
|
||||
"type": "Capability",
|
||||
|
@ -196,7 +196,7 @@ def capabilitiesUpdate(baseDir: str,httpPrefix: str, \
|
|||
return None
|
||||
|
||||
# create an update activity
|
||||
ocapUpdate = {
|
||||
ocapUpdate={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Update',
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
|
|
16
config.py
16
config.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "config.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="config.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
@ -18,7 +18,7 @@ def createConfig(baseDir: str) -> None:
|
|||
configFilename=baseDir+'/config.json'
|
||||
if os.path.isfile(configFilename):
|
||||
return
|
||||
configJson = {
|
||||
configJson={
|
||||
}
|
||||
saveJson(configJson,configFilename)
|
||||
|
||||
|
|
23
content.py
23
content.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "content.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="content.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
@ -158,7 +158,8 @@ def addWebLinks(content: str) -> str:
|
|||
def validHashTag(hashtag: str) -> bool:
|
||||
"""Returns true if the give hashtag contains valid characters
|
||||
"""
|
||||
validChars = set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
validChars= \
|
||||
set('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
if set(hashtag).issubset(validChars):
|
||||
return True
|
||||
return False
|
||||
|
@ -417,7 +418,7 @@ def addHtmlTags(baseDir: str,httpPrefix: str, \
|
|||
if '@' in words:
|
||||
if os.path.isfile(followingFilename):
|
||||
with open(followingFilename, "r") as f:
|
||||
following = f.readlines()
|
||||
following=f.readlines()
|
||||
|
||||
# extract mentions and tags from words
|
||||
longWordsList=[]
|
||||
|
@ -569,7 +570,7 @@ def saveMediaInFormPOST(mediaBytes,debug: bool, \
|
|||
if os.path.isfile(possibleOtherFormat):
|
||||
os.remove(possibleOtherFormat)
|
||||
|
||||
fd = open(filename, 'wb')
|
||||
fd=open(filename, 'wb')
|
||||
fd.write(mediaBytes[startPos:])
|
||||
fd.close()
|
||||
|
||||
|
@ -579,7 +580,7 @@ def extractTextFieldsInPOST(postBytes,boundary,debug: bool) -> {}:
|
|||
"""Returns a dictionary containing the text fields of a http form POST
|
||||
The boundary argument comes from the http header
|
||||
"""
|
||||
msg = email.parser.BytesParser().parsebytes(postBytes)
|
||||
msg=email.parser.BytesParser().parsebytes(postBytes)
|
||||
if debug:
|
||||
print('DEBUG: POST arriving '+msg.get_payload(decode=True).decode('utf-8'))
|
||||
messageFields=msg.get_payload(decode=True).decode('utf-8').split(boundary)
|
||||
|
|
138
daemon.py
138
daemon.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "daemon.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="daemon.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
|
||||
#import socketserver
|
||||
|
@ -218,17 +218,17 @@ def readFollowList(filename: str) -> None:
|
|||
followlist=[]
|
||||
if not os.path.isfile(filename):
|
||||
return followlist
|
||||
followUsers = open(filename, "r")
|
||||
followUsers=open(filename, "r")
|
||||
for u in followUsers:
|
||||
if u not in followlist:
|
||||
nickname,domain = parseHandle(u)
|
||||
nickname,domain=parseHandle(u)
|
||||
if nickname:
|
||||
followlist.append(nickname+'@'+domain)
|
||||
followUsers.close()
|
||||
return followlist
|
||||
|
||||
class PubServer(BaseHTTPRequestHandler):
|
||||
protocol_version = 'HTTP/1.1'
|
||||
protocol_version='HTTP/1.1'
|
||||
|
||||
def _sendReplyToQuestion(self,nickname: str,messageId: str,answer: str) -> None:
|
||||
"""Sends a reply to a question
|
||||
|
@ -451,7 +451,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if os.path.isfile(mediaFilename+'.etag'):
|
||||
try:
|
||||
with open(mediaFilename+'.etag', 'r') as etagFile:
|
||||
etag = etagFile.read()
|
||||
etag=etagFile.read()
|
||||
except:
|
||||
pass
|
||||
if not etag:
|
||||
|
@ -771,7 +771,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
beginSaveTime=time.time()
|
||||
# save the json for later queue processing
|
||||
queueFilename = \
|
||||
queueFilename= \
|
||||
savePostToInboxQueue(self.server.baseDir,
|
||||
self.server.httpPrefix,
|
||||
nickname,
|
||||
|
@ -962,7 +962,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkGETtimings(GETstartTime,GETtimings,4)
|
||||
|
||||
# check authorization
|
||||
authorized = self._isAuthorized()
|
||||
authorized=self._isAuthorized()
|
||||
if self.server.debug:
|
||||
if authorized:
|
||||
print('GET Authorization granted')
|
||||
|
@ -1270,7 +1270,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
while tries<5:
|
||||
try:
|
||||
with open('epicyon-profile.css', 'r') as cssfile:
|
||||
css = cssfile.read()
|
||||
css=cssfile.read()
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
@ -1299,7 +1299,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
while tries<5:
|
||||
try:
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
@ -1324,7 +1324,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
while tries<5:
|
||||
try:
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
@ -1349,7 +1349,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
while tries<5:
|
||||
try:
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
@ -1383,7 +1383,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
else:
|
||||
mediaImageType='gif'
|
||||
with open(emojiFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
self._set_headers('image/'+mediaImageType,len(mediaBinary),cookie)
|
||||
self._write(mediaBinary)
|
||||
return
|
||||
|
@ -1439,7 +1439,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
currEtag=''
|
||||
try:
|
||||
with open(mediaFilename, 'r') as etagFile:
|
||||
currEtag = etagFile.read()
|
||||
currEtag=etagFile.read()
|
||||
except:
|
||||
pass
|
||||
if oldEtag==currEtag:
|
||||
|
@ -1447,7 +1447,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._304()
|
||||
return
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
self._set_headers_etag(mediaFilename,mediaFileType,mediaBinary,cookie)
|
||||
self._write(mediaBinary)
|
||||
return
|
||||
|
@ -1477,7 +1477,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
else:
|
||||
mediaFileType='gif'
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
self._set_headers('image/'+mediaFileType,len(mediaBinary),cookie)
|
||||
self._write(mediaBinary)
|
||||
return
|
||||
|
@ -1501,7 +1501,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
else:
|
||||
if os.path.isfile(mediaFilename):
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
self._set_headers('image/png',len(mediaBinary),cookie)
|
||||
self._write(mediaBinary)
|
||||
self.server.iconsCache[mediaStr]=mediaBinary
|
||||
|
@ -1518,7 +1518,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.baseDir+'/cache/'+self.path
|
||||
if os.path.isfile(mediaFilename):
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
if mediaFilename.endswith('.png'):
|
||||
self._set_headers('image/png',len(mediaBinary),cookie)
|
||||
elif mediaFilename.endswith('.jpg'):
|
||||
|
@ -1568,7 +1568,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
else:
|
||||
mediaImageType='webp'
|
||||
with open(avatarFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
self._set_headers('image/'+mediaImageType, \
|
||||
len(mediaBinary),cookie)
|
||||
self._write(mediaBinary)
|
||||
|
@ -1861,7 +1861,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.httpPrefix+'://'+self.server.domainFull+ \
|
||||
'/users/'+self.postToNickname
|
||||
unRepeatToStr='https://www.w3.org/ns/activitystreams#Public'
|
||||
newUndoAnnounce = {
|
||||
newUndoAnnounce={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'actor': undoAnnounceActor,
|
||||
'type': 'Undo',
|
||||
|
@ -2534,7 +2534,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
nickname+'#statuses#'+statusNumber+'.replies'
|
||||
if not os.path.isfile(postRepliesFilename):
|
||||
# There are no replies, so show empty collection
|
||||
repliesJson = {
|
||||
repliesJson={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'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',
|
||||
|
@ -2574,7 +2574,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return
|
||||
else:
|
||||
# replies exist. Itterate through the text file containing message ids
|
||||
repliesJson = {
|
||||
repliesJson={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': self.server.httpPrefix+'://'+self.server.domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'?page=true',
|
||||
'orderedItems': [
|
||||
|
@ -2637,7 +2637,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if actorJson:
|
||||
if actorJson.get('roles'):
|
||||
if self._requestHTTP():
|
||||
getPerson = \
|
||||
getPerson= \
|
||||
personLookup(self.server.domain, \
|
||||
self.path.replace('/roles',''), \
|
||||
self.server.baseDir)
|
||||
|
@ -2683,7 +2683,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if actorJson:
|
||||
if actorJson.get('skills'):
|
||||
if self._requestHTTP():
|
||||
getPerson = \
|
||||
getPerson= \
|
||||
personLookup(self.server.domain, \
|
||||
self.path.replace('/skills',''), \
|
||||
self.server.baseDir)
|
||||
|
@ -3525,7 +3525,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if pageNumberStr.isdigit():
|
||||
pageNumber=int(pageNumberStr)
|
||||
searchPath=self.path.split('?page=')[0]
|
||||
getPerson = \
|
||||
getPerson= \
|
||||
personLookup(self.server.domain, \
|
||||
searchPath.replace('/following',''), \
|
||||
self.server.baseDir)
|
||||
|
@ -3629,7 +3629,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkGETtimings(GETstartTime,GETtimings,52)
|
||||
|
||||
# look up a person
|
||||
getPerson = \
|
||||
getPerson= \
|
||||
personLookup(self.server.domain,self.path, \
|
||||
self.server.baseDir)
|
||||
if getPerson:
|
||||
|
@ -3687,7 +3687,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
filename=self.server.baseDir+self.path
|
||||
if os.path.isfile(filename):
|
||||
with open(filename, 'r', encoding='utf-8') as File:
|
||||
content = File.read()
|
||||
content=File.read()
|
||||
contentJson=json.loads(content)
|
||||
msg=json.dumps(contentJson,ensure_ascii=False).encode('utf-8')
|
||||
self._set_headers('application/json',len(msg),None)
|
||||
|
@ -3723,12 +3723,12 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if os.path.isfile(mediaFilename+'.etag'):
|
||||
try:
|
||||
with open(mediaFilename+'.etag', 'r') as etagFile:
|
||||
etag = etagFile.read()
|
||||
etag=etagFile.read()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mediaBinary=avFile.read()
|
||||
etag=sha1(mediaBinary).hexdigest()
|
||||
try:
|
||||
with open(mediaFilename+'.etag', 'w') as etagFile:
|
||||
|
@ -3760,10 +3760,10 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
postType: str,path: str,headers: {},
|
||||
length: int,postBytes,boundary: str) -> int:
|
||||
# Note: this needs to happen synchronously
|
||||
# 0 = this is not a new post
|
||||
# 1 = new post success
|
||||
# -1 = new post failed
|
||||
# 2 = new post canceled
|
||||
# 0=this is not a new post
|
||||
# 1=new post success
|
||||
# -1=new post failed
|
||||
# 2=new post canceled
|
||||
if self.server.debug:
|
||||
print('DEBUG: receiving POST')
|
||||
|
||||
|
@ -3776,7 +3776,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
nickname=nicknameStr.split('/')[0]
|
||||
else:
|
||||
return -1
|
||||
length = int(headers['Content-Length'])
|
||||
length=int(headers['Content-Length'])
|
||||
if length>self.server.maxPostLength:
|
||||
print('POST size too large')
|
||||
return -1
|
||||
|
@ -4229,7 +4229,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
headersWithoutCookie[dictEntryName]=headerLine
|
||||
print('New post headers: '+str(headersWithoutCookie))
|
||||
|
||||
length = int(headers['Content-Length'])
|
||||
length=int(headers['Content-Length'])
|
||||
if length>self.server.maxPostLength:
|
||||
print('POST size too large')
|
||||
return None
|
||||
|
@ -4304,7 +4304,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
cookie=self.headers['Cookie']
|
||||
|
||||
# check authorization
|
||||
authorized = self._isAuthorized()
|
||||
authorized=self._isAuthorized()
|
||||
if self.server.debug:
|
||||
if authorized:
|
||||
print('POST Authorization granted')
|
||||
|
@ -4320,7 +4320,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
if self.path.startswith('/login'):
|
||||
# get the contents of POST containing login credentials
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
if length>512:
|
||||
print('Login failed - credentials too long')
|
||||
self.send_response(401)
|
||||
|
@ -4368,7 +4368,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if os.path.isfile(saltFilename):
|
||||
try:
|
||||
with open(saltFilename, 'r') as fp:
|
||||
salt = fp.read()
|
||||
salt=fp.read()
|
||||
except Exception as e:
|
||||
print('WARN: Unable to read salt for '+ \
|
||||
loginNickname+' '+str(e))
|
||||
|
@ -4430,7 +4430,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._redirect_headers(actorStr,cookie)
|
||||
self.server.POSTbusy=False
|
||||
return
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
if length>self.server.maxPostLength:
|
||||
print('Maximum profile data length exceeded '+str(length))
|
||||
self._redirect_headers(actorStr,cookie)
|
||||
|
@ -4855,7 +4855,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
actorStr= \
|
||||
self.server.httpPrefix+'://'+self.server.domainFull+ \
|
||||
self.path.replace('/moderationaction','')
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
moderationParams=self.rfile.read(length).decode('utf-8')
|
||||
print('moderationParams: '+moderationParams)
|
||||
if '&' in moderationParams:
|
||||
|
@ -4995,7 +4995,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy=False
|
||||
return
|
||||
# get the parameters
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
questionParams=self.rfile.read(length).decode('utf-8')
|
||||
questionParams= \
|
||||
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.domainFull+ \
|
||||
self.path.replace('/searchhandle','')
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
searchParams=self.rfile.read(length).decode('utf-8')
|
||||
if 'submitBack=' in searchParams:
|
||||
# go back on search screen
|
||||
|
@ -5165,7 +5165,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
originPathStr= \
|
||||
self.server.httpPrefix+'://'+self.server.domainFull+ \
|
||||
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')
|
||||
if '&submitYes=' in removeShareConfirmParams:
|
||||
removeShareConfirmParams= \
|
||||
|
@ -5193,7 +5193,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
originPathStr= \
|
||||
self.server.httpPrefix+'://'+self.server.domainFull+ \
|
||||
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')
|
||||
if '&submitYes=' in removePostConfirmParams:
|
||||
removePostConfirmParams= \
|
||||
|
@ -5255,7 +5255,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.httpPrefix+'://'+self.server.domainFull+ \
|
||||
self.path.split('/followconfirm')[0]
|
||||
followerNickname=getNicknameFromActor(originPathStr)
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
followConfirmParams=self.rfile.read(length).decode('utf-8')
|
||||
if '&submitView=' in followConfirmParams:
|
||||
followingActor= \
|
||||
|
@ -5308,7 +5308,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.httpPrefix+'://'+self.server.domainFull+ \
|
||||
self.path.split('/unfollowconfirm')[0]
|
||||
followerNickname=getNicknameFromActor(originPathStr)
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
followConfirmParams=self.rfile.read(length).decode('utf-8')
|
||||
if '&submitYes=' in followConfirmParams:
|
||||
followingActor= \
|
||||
|
@ -5329,9 +5329,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.httpPrefix+'://'+ \
|
||||
self.server.domainFull+ \
|
||||
'/users/'+followerNickname
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
followId=followActor+'/statuses/'+str(statusNumber)
|
||||
unfollowJson = {
|
||||
unfollowJson={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': followId+'/undo',
|
||||
'type': 'Undo',
|
||||
|
@ -5363,7 +5363,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._redirect_headers(originPathStr,cookie)
|
||||
self.server.POSTbusy=False
|
||||
return
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
blockConfirmParams=self.rfile.read(length).decode('utf-8')
|
||||
if '&submitYes=' in blockConfirmParams:
|
||||
blockingActor= \
|
||||
|
@ -5410,7 +5410,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._redirect_headers(originPathStr,cookie)
|
||||
self.server.POSTbusy=False
|
||||
return
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
blockConfirmParams=self.rfile.read(length).decode('utf-8')
|
||||
if '&submitYes=' in blockConfirmParams:
|
||||
blockingActor= \
|
||||
|
@ -5462,7 +5462,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._redirect_headers(originPathStr,cookie)
|
||||
self.server.POSTbusy=False
|
||||
return
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
optionsConfirmParams= \
|
||||
self.rfile.read(length).decode('utf-8').replace('%3A',':').replace('%2F','/')
|
||||
# page number to return to
|
||||
|
@ -5671,7 +5671,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkPOSTtimings(POSTstartTime,POSTtimings,17)
|
||||
|
||||
# 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:
|
||||
print('DEBUG: content-length: '+str(length))
|
||||
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'])
|
||||
if self.server.debug:
|
||||
print(str(self.headers))
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['Content-length'])
|
||||
if length<self.server.maxPostLength:
|
||||
unknownPost=self.rfile.read(length).decode('utf-8')
|
||||
print(str(unknownPost))
|
||||
|
@ -5758,11 +5758,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if self.path == '/sharedInbox' or self.path == '/inbox':
|
||||
length=0
|
||||
if self.headers.get('Content-length'):
|
||||
length = int(self.headers['Content-length'])
|
||||
length=int(self.headers['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'):
|
||||
length = int(self.headers['content-length'])
|
||||
length=int(self.headers['content-length'])
|
||||
if length>10240:
|
||||
print('WARN: post to shared inbox is too long '+str(length)+' bytes')
|
||||
self._400()
|
||||
|
@ -5897,7 +5897,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy=False
|
||||
|
||||
class PubServerUnitTest(PubServer):
|
||||
protocol_version = 'HTTP/1.0'
|
||||
protocol_version='HTTP/1.0'
|
||||
|
||||
def runPostsQueue(baseDir: str,sendThreads: [],debug: bool) -> None:
|
||||
"""Manages the threads used to send posts
|
||||
|
@ -5952,7 +5952,7 @@ def loadTokens(baseDir: str,tokensDict: {},tokensLookup: {}) -> None:
|
|||
token=None
|
||||
try:
|
||||
with open(tokenFilename, 'r') as fp:
|
||||
token = fp.read()
|
||||
token=fp.read()
|
||||
except Exception as e:
|
||||
print('WARN: Unable to read token for '+nickname+' '+str(e))
|
||||
if not token:
|
||||
|
@ -5984,14 +5984,14 @@ def runDaemon(blogsInstance: bool,mediaInstance: bool, \
|
|||
return
|
||||
|
||||
if unitTest:
|
||||
serverAddress = (domain, proxyPort)
|
||||
pubHandler = partial(PubServerUnitTest)
|
||||
serverAddress=(domain, proxyPort)
|
||||
pubHandler=partial(PubServerUnitTest)
|
||||
else:
|
||||
serverAddress = ('', proxyPort)
|
||||
pubHandler = partial(PubServer)
|
||||
serverAddress=('', proxyPort)
|
||||
pubHandler=partial(PubServer)
|
||||
|
||||
try:
|
||||
httpd = ThreadingHTTPServer(serverAddress, pubHandler)
|
||||
httpd=ThreadingHTTPServer(serverAddress, pubHandler)
|
||||
except Exception as e:
|
||||
if e.errno==98:
|
||||
print('ERROR: HTTP server address is already in use. '+str(serverAddress))
|
||||
|
@ -6068,7 +6068,7 @@ def runDaemon(blogsInstance: bool,mediaInstance: bool, \
|
|||
httpd.personCache={}
|
||||
httpd.cachedWebfingers={}
|
||||
httpd.useTor=useTor
|
||||
httpd.session = None
|
||||
httpd.session=None
|
||||
httpd.sessionLastUpdate=0
|
||||
httpd.lastGET=0
|
||||
httpd.lastPOST=0
|
||||
|
|
47
delete.py
47
delete.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "delete.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="delete.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import json
|
||||
|
@ -46,10 +46,10 @@ def createDelete(session,baseDir: str,federationList: [], \
|
|||
if ':' not in domain:
|
||||
fullDomain=domain+':'+str(port)
|
||||
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
newDeleteId= \
|
||||
httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber
|
||||
newDelete = {
|
||||
newDelete={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber,
|
||||
|
@ -101,10 +101,10 @@ def sendDeleteViaServer(baseDir: str,session, \
|
|||
if ':' not in fromDomain:
|
||||
fromDomainFull=fromDomain+':'+str(fromPort)
|
||||
|
||||
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
toUrl='https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
|
||||
newDeleteJson = {
|
||||
newDeleteJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
'cc': [ccUrl],
|
||||
|
@ -116,8 +116,9 @@ def sendDeleteViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -126,7 +127,7 @@ def sendDeleteViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -142,10 +143,12 @@ def sendDeleteViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newDeleteJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
@ -171,8 +174,8 @@ def deletePublic(session,baseDir: str,federationList: [], \
|
|||
if ':' not in domain:
|
||||
fromDomain=domain+':'+str(port)
|
||||
|
||||
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl = httpPrefix + '://'+fromDomain+'/users/'+nickname+'/followers'
|
||||
toUrl='https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl=httpPrefix+'://'+fromDomain+'/users/'+nickname+'/followers'
|
||||
return createDelete(session,baseDir,federationList, \
|
||||
nickname,domain,port, \
|
||||
toUrl,ccUrl,httpPrefix, \
|
||||
|
@ -197,7 +200,7 @@ def deletePostPub(session,baseDir: str,federationList: [], \
|
|||
if ':' not in deletedDomain:
|
||||
deletedDomain=deletedDomain+':'+str(deletePort)
|
||||
|
||||
objectUrl = \
|
||||
objectUrl= \
|
||||
deleteHttpsPrefix + '://'+deletedDomain+'/users/'+ \
|
||||
deleteNickname+'/statuses/'+str(deleteStatusNumber)
|
||||
|
||||
|
|
14
donate.py
14
donate.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "donate.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="donate.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
|
||||
|
|
88
epicyon.py
88
epicyon.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "epicyon.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="epicyon.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
from person import createPerson
|
||||
from person import createGroup
|
||||
|
@ -93,7 +93,7 @@ def str2bool(v):
|
|||
else:
|
||||
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, \
|
||||
help='Nickname of the account to use')
|
||||
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='?', \
|
||||
const=True, default=False, \
|
||||
help="Reset the number of remaining registrations")
|
||||
args = parser.parse_args()
|
||||
args=parser.parse_args()
|
||||
|
||||
debug=False
|
||||
if args.debug:
|
||||
|
@ -365,9 +365,11 @@ if args.postsraw:
|
|||
sys.exit()
|
||||
|
||||
if args.json:
|
||||
session = createSession(False)
|
||||
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
|
||||
testJson = getJson(session,args.json,asHeader,None,__version__,httpPrefix,None)
|
||||
session=createSession(False)
|
||||
asHeader={
|
||||
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
testJson=getJson(session,args.json,asHeader,None,__version__,httpPrefix,None)
|
||||
pprint(testJson)
|
||||
sys.exit()
|
||||
|
||||
|
@ -533,7 +535,7 @@ if args.approve:
|
|||
if '@' not in args.approve:
|
||||
print('syntax: --approve nick@domain')
|
||||
sys.exit()
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
sendThreads=[]
|
||||
postLog=[]
|
||||
cachedWebfingers={}
|
||||
|
@ -557,7 +559,7 @@ if args.deny:
|
|||
if '@' not in args.deny:
|
||||
print('syntax: --deny nick@domain')
|
||||
sys.exit()
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
sendThreads=[]
|
||||
postLog=[]
|
||||
cachedWebfingers={}
|
||||
|
@ -599,7 +601,7 @@ if args.message:
|
|||
print('Specify a password with the --password option')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
if not args.sendto:
|
||||
print('Specify an account to sent to: --sendto [nickname@domain]')
|
||||
sys.exit()
|
||||
|
@ -634,8 +636,8 @@ if args.message:
|
|||
clientToServer=args.client
|
||||
attachedImageDescription=args.imageDescription
|
||||
useBlurhash=args.blurhash
|
||||
sendThreads = []
|
||||
postLog = []
|
||||
sendThreads=[]
|
||||
postLog=[]
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
subject=args.subject
|
||||
|
@ -671,7 +673,7 @@ if args.announce:
|
|||
print('Specify a password with the --password option')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
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')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
print('Sending shared item: '+args.itemName)
|
||||
|
@ -747,7 +749,7 @@ if args.undoItemName:
|
|||
print('Specify a nickname with the --nickname option')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
print('Sending undo of shared item: '+args.undoItemName)
|
||||
|
@ -773,7 +775,7 @@ if args.like:
|
|||
print('Specify a password with the --password option')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
print('Sending like of '+args.like)
|
||||
|
@ -798,7 +800,7 @@ if args.undolike:
|
|||
print('Specify a password with the --password option')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
print('Sending undo like of '+args.undolike)
|
||||
|
@ -823,7 +825,7 @@ if args.delete:
|
|||
print('Specify a password with the --password option')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
print('Sending delete request of '+args.delete)
|
||||
|
@ -857,7 +859,7 @@ if args.follow:
|
|||
sys.exit()
|
||||
followDomain,followPort=getDomainFromActor(args.follow)
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
followHttpPrefix=httpPrefix
|
||||
|
@ -895,7 +897,7 @@ if args.unfollow:
|
|||
sys.exit()
|
||||
followDomain,followPort=getDomainFromActor(args.unfollow)
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
followHttpPrefix=httpPrefix
|
||||
|
@ -994,24 +996,34 @@ if args.actor:
|
|||
else:
|
||||
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:
|
||||
personUrl = getUserUrl(wfRequest)
|
||||
personUrl=getUserUrl(wfRequest)
|
||||
if nickname==domain:
|
||||
personUrl=personUrl.replace('/users/','/actor/').replace('/channel/','/actor/').replace('/profile/','/actor/')
|
||||
if not personUrl:
|
||||
# try single user instance
|
||||
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:
|
||||
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:
|
||||
pprint(personJson)
|
||||
else:
|
||||
asHeader = {'Accept': 'application/jrd+json; profile="https://www.w3.org/ns/activitystreams"'}
|
||||
personJson = getJson(session,personUrl,asHeader,None,__version__,httpPrefix,None)
|
||||
asHeader={
|
||||
'Accept': 'application/jrd+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
personJson= \
|
||||
getJson(session,personUrl,asHeader,None,__version__,httpPrefix,None)
|
||||
if personJson:
|
||||
pprint(personJson)
|
||||
else:
|
||||
|
@ -1237,7 +1249,7 @@ if args.skill:
|
|||
print('Skill level should be a percentage in the range 0-100')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
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')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
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')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
print('Sending block of '+args.block)
|
||||
|
@ -1344,7 +1356,7 @@ if args.delegate:
|
|||
delegatedNickname=args.delegate.split('@')[0]
|
||||
args.delegate=blockedActor
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
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]
|
||||
args.undelegate=blockedActor
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
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')
|
||||
sys.exit()
|
||||
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
print('Sending undo block of '+args.unblock)
|
||||
|
|
14
filters.py
14
filters.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "filters.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="filters.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
|
||||
|
|
94
follow.py
94
follow.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "follow.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="follow.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import time
|
||||
|
@ -56,7 +56,7 @@ def removeFromFollowBase(baseDir: str, \
|
|||
return
|
||||
if acceptOrDenyHandle not in open(approveFollowsFilename).read():
|
||||
return
|
||||
approvefilenew = open(approveFollowsFilename+'.new', 'w+')
|
||||
approvefilenew=open(approveFollowsFilename+'.new', 'w+')
|
||||
with open(approveFollowsFilename, 'r') as approvefile:
|
||||
for approveHandle in approvefile:
|
||||
if not approveHandle.startswith(acceptOrDenyHandle):
|
||||
|
@ -137,8 +137,8 @@ def getFollowersOfPerson(baseDir: str, \
|
|||
return followers
|
||||
for subdir, dirs, files in os.walk(baseDir+'/accounts'):
|
||||
for account in dirs:
|
||||
filename = os.path.join(subdir, account)+'/'+followFile
|
||||
if account == handle or account.startswith('inbox@'):
|
||||
filename=os.path.join(subdir, account)+'/'+followFile
|
||||
if account==handle or account.startswith('inbox@'):
|
||||
continue
|
||||
if not os.path.isfile(filename):
|
||||
continue
|
||||
|
@ -195,7 +195,7 @@ def unfollowPerson(baseDir: str,nickname: str, domain: str, \
|
|||
print('DEBUG: handle to unfollow '+handleToUnfollow+' is not in '+filename)
|
||||
return
|
||||
with open(filename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
with open(filename, "w") as f:
|
||||
for line in lines:
|
||||
if line.strip("\n") != handleToUnfollow:
|
||||
|
@ -254,9 +254,9 @@ def getNoOfFollows(baseDir: str,nickname: str,domain: str, \
|
|||
filename=baseDir+'/accounts/'+handle+'/'+followFile
|
||||
if not os.path.isfile(filename):
|
||||
return 0
|
||||
ctr = 0
|
||||
ctr=0
|
||||
with open(filename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for line in lines:
|
||||
if '#' not in line:
|
||||
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)
|
||||
|
||||
if headerOnly:
|
||||
following = {
|
||||
following={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page=1',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile,
|
||||
'totalItems': getNoOfFollows(baseDir,nickname,domain,authenticated),
|
||||
'type': 'OrderedCollection'}
|
||||
'type': 'OrderedCollection'
|
||||
}
|
||||
return following
|
||||
|
||||
if not pageNumber:
|
||||
pageNumber=1
|
||||
|
||||
nextPageNumber=int(pageNumber+1)
|
||||
following = {
|
||||
following={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile+'?page='+str(pageNumber),
|
||||
'orderedItems': [],
|
||||
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+followFile,
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollectionPage'}
|
||||
'type': 'OrderedCollectionPage'
|
||||
}
|
||||
|
||||
handleDomain=domain
|
||||
if ':' in handleDomain:
|
||||
|
@ -346,15 +348,15 @@ def getFollowingFeed(baseDir: str,domain: str,port: int,path: str, \
|
|||
pageCtr=0
|
||||
totalCtr=0
|
||||
with open(filename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for line in lines:
|
||||
if '#' not in line:
|
||||
if '@' in line and not line.startswith('http'):
|
||||
pageCtr += 1
|
||||
totalCtr += 1
|
||||
if currPage==pageNumber:
|
||||
url = httpPrefix + '://' + line.lower().replace('\n','').split('@')[1] + \
|
||||
'/users/' + line.lower().replace('\n','').split('@')[0]
|
||||
url=httpPrefix+'://' + line.lower().replace('\n','').split('@')[1] + \
|
||||
'/users/'+line.lower().replace('\n','').split('@')[0]
|
||||
following['orderedItems'].append(url)
|
||||
elif (line.startswith('http') or line.startswith('dat')) and '/users/' in line:
|
||||
pageCtr += 1
|
||||
|
@ -411,7 +413,7 @@ def noOfFollowRequests(baseDir: str, \
|
|||
return 0
|
||||
ctr=0
|
||||
with open(approveFollowsFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
if followType != "onion":
|
||||
return len(lines)
|
||||
for l in lines:
|
||||
|
@ -590,7 +592,7 @@ def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \
|
|||
if approveHandle not in open(followersFilename).read():
|
||||
try:
|
||||
with open(followersFilename, 'r+') as followersFile:
|
||||
content = followersFile.read()
|
||||
content=followersFile.read()
|
||||
followersFile.seek(0, 0)
|
||||
followersFile.write(approveHandle+'\n'+content)
|
||||
except Exception as e:
|
||||
|
@ -746,7 +748,7 @@ def sendFollowRequest(session,baseDir: str, \
|
|||
if ':' not in followDomain:
|
||||
requestDomain=followDomain+':'+str(followPort)
|
||||
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
|
||||
if followNickname:
|
||||
followedId=followHttpPrefix+'://'+requestDomain+'/users/'+followNickname
|
||||
|
@ -758,7 +760,7 @@ def sendFollowRequest(session,baseDir: str, \
|
|||
singleUserNickname='dev'
|
||||
followHandle=singleUserNickname+'@'+requestDomain
|
||||
|
||||
newFollowJson = {
|
||||
newFollowJson={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': followActor+'/statuses/'+str(statusNumber),
|
||||
'type': 'Follow',
|
||||
|
@ -813,8 +815,8 @@ def sendFollowRequestViaServer(baseDir: str,session, \
|
|||
followActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
|
||||
followedId=httpPrefix+'://'+followDomainFull+'/users/'+followNickname
|
||||
|
||||
statusNumber,published = getStatusNumber()
|
||||
newFollowJson = {
|
||||
statusNumber,published=getStatusNumber()
|
||||
newFollowJson={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': followActor+'/statuses/'+str(statusNumber),
|
||||
'type': 'Follow',
|
||||
|
@ -825,8 +827,9 @@ def sendFollowRequestViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -835,7 +838,7 @@ def sendFollowRequestViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -851,10 +854,12 @@ def sendFollowRequestViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newFollowJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
@ -892,9 +897,9 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
|
|||
|
||||
followActor=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname
|
||||
followedId=httpPrefix+'://'+followDomainFull+'/users/'+followNickname
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
|
||||
unfollowJson = {
|
||||
unfollowJson={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': followActor+'/statuses/'+str(statusNumber)+'/undo',
|
||||
'type': 'Undo',
|
||||
|
@ -910,8 +915,9 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -920,7 +926,7 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -936,10 +942,12 @@ def sendUnfollowRequestViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,unfollowJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
@ -980,7 +988,7 @@ def getFollowersOfActor(baseDir :str,actor :str,debug: bool) -> {}:
|
|||
for subdir, dirs, files in os.walk(baseDir+'/accounts'):
|
||||
for account in dirs:
|
||||
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:
|
||||
print('DEBUG: examining follows of '+account)
|
||||
print(followingFilename)
|
||||
|
|
16
happening.py
16
happening.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "happening.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="happening.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import time
|
||||
|
@ -380,7 +380,7 @@ def removeCalendarEvent(baseDir: str,nickname: str,domain: str, \
|
|||
return
|
||||
lines=None
|
||||
with open(calendarFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
if not lines:
|
||||
return
|
||||
with open(calendarFilename, "w+") as f:
|
||||
|
|
65
httpsig.py
65
httpsig.py
|
@ -1,11 +1,11 @@
|
|||
__filename__ = "posts.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__credits__ = ['lamia']
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="posts.py"
|
||||
__author__="Bob Mottram"
|
||||
__credits__=['lamia']
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
# 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}',
|
||||
#})
|
||||
# build a digest for signing
|
||||
signedHeaderKeys = headers.keys()
|
||||
signedHeaderText = ''
|
||||
signedHeaderKeys=headers.keys()
|
||||
signedHeaderText=''
|
||||
for headerKey in signedHeaderKeys:
|
||||
signedHeaderText += f'{headerKey}: {headers[headerKey]}\n'
|
||||
#print(f'*********************signing: headerKey: {headerKey}: {headers[headerKey]}')
|
||||
signedHeaderText = signedHeaderText.strip()
|
||||
signedHeaderText=signedHeaderText.strip()
|
||||
#print('******************************Send: signedHeaderText: '+signedHeaderText)
|
||||
headerDigest = SHA256.new(signedHeaderText.encode('ascii'))
|
||||
headerDigest=SHA256.new(signedHeaderText.encode('ascii'))
|
||||
|
||||
# Sign the digest
|
||||
rawSignature = pkcs1_15.new(privateKeyPem).sign(headerDigest)
|
||||
signature = base64.b64encode(rawSignature).decode('ascii')
|
||||
rawSignature=pkcs1_15.new(privateKeyPem).sign(headerDigest)
|
||||
signature=base64.b64encode(rawSignature).decode('ascii')
|
||||
|
||||
# Put it into a valid HTTP signature format
|
||||
signatureDict = {
|
||||
signatureDict={
|
||||
'keyId': keyID,
|
||||
'algorithm': 'rsa-sha256',
|
||||
'headers': ' '.join(signedHeaderKeys),
|
||||
'signature': signature
|
||||
}
|
||||
signatureHeader = ','.join(
|
||||
signatureHeader=','.join(
|
||||
[f'{k}="{v}"' for k, v in signatureDict.items()])
|
||||
return signatureHeader
|
||||
|
||||
|
@ -104,8 +104,10 @@ def createSignedHeader(privateKeyPem: str,nickname: str, \
|
|||
|
||||
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
||||
if not withDigest:
|
||||
headers = {'(request-target)': f'post {path}','host': headerDomain,'date': dateStr}
|
||||
signatureHeader = \
|
||||
headers={
|
||||
'(request-target)': f'post {path}','host': headerDomain,'date': dateStr
|
||||
}
|
||||
signatureHeader= \
|
||||
signPostHeaders(dateStr,privateKeyPem,nickname, \
|
||||
domain,port,toDomain,toPort, \
|
||||
path,httpPrefix,None)
|
||||
|
@ -119,13 +121,20 @@ def createSignedHeader(privateKeyPem: str,nickname: str, \
|
|||
#print('***************************Send Content-type: '+contentType)
|
||||
#print('***************************Send Content-Length: '+str(len(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}
|
||||
signatureHeader = \
|
||||
headers={
|
||||
'(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, \
|
||||
domain,port, \
|
||||
toDomain,toPort, \
|
||||
path,httpPrefix,messageBodyJsonStr)
|
||||
headers['signature'] = signatureHeader
|
||||
headers['signature']=signatureHeader
|
||||
return headers
|
||||
|
||||
def verifyRecentSignature(signedDateStr: str) -> bool:
|
||||
|
@ -167,10 +176,10 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
|
|||
if debug:
|
||||
print('DEBUG: verifyPostHeaders '+method)
|
||||
|
||||
publicKeyPem = RSA.import_key(publicKeyPem)
|
||||
publicKeyPem=RSA.import_key(publicKeyPem)
|
||||
# Build a dictionary of the signature values
|
||||
signatureHeader = headers['signature']
|
||||
signatureDict = {
|
||||
signatureHeader=headers['signature']
|
||||
signatureDict={
|
||||
k: v[1:-1]
|
||||
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
|
||||
# body (if a digest was included)
|
||||
signedHeaderList = []
|
||||
signedHeaderList=[]
|
||||
for signedHeader in signatureDict['headers'].split(' '):
|
||||
if debug:
|
||||
print('DEBUG: verifyPostHeaders signedHeader='+signedHeader)
|
||||
|
@ -236,12 +245,12 @@ def verifyPostHeaders(httpPrefix: str,publicKeyPem: str,headers: dict, \
|
|||
if debug:
|
||||
print('DEBUG: signedHeaderList: '+str(signedHeaderList))
|
||||
# Now we have our header data digest
|
||||
signedHeaderText = '\n'.join(signedHeaderList)
|
||||
signedHeaderText='\n'.join(signedHeaderList)
|
||||
#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
|
||||
signature = base64.b64decode(signatureDict['signature'])
|
||||
signature=base64.b64decode(signatureDict['signature'])
|
||||
|
||||
try:
|
||||
pkcs1_15.new(publicKeyPem).verify(headerDigest, signature)
|
||||
|
|
38
inbox.py
38
inbox.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "inbox.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="inbox.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import os
|
||||
|
@ -101,7 +101,7 @@ def storeHashTags(baseDir: str,nickname: str,postJsonObject: {}) -> None:
|
|||
if postUrl not in open(tagsFilename).read():
|
||||
try:
|
||||
with open(tagsFilename, 'r+') as tagsFile:
|
||||
content = tagsFile.read()
|
||||
content=tagsFile.read()
|
||||
tagsFile.seek(0, 0)
|
||||
tagsFile.write(tagline+content)
|
||||
except Exception as e:
|
||||
|
@ -142,7 +142,7 @@ def validInbox(baseDir: str,nickname: str,domain: str) -> bool:
|
|||
return True
|
||||
for subdir, dirs, files in os.walk(inboxDir):
|
||||
for f in files:
|
||||
filename = os.path.join(subdir, f)
|
||||
filename=os.path.join(subdir, f)
|
||||
if not os.path.isfile(filename):
|
||||
print('filename: '+filename)
|
||||
return False
|
||||
|
@ -164,7 +164,7 @@ def validInboxFilenames(baseDir: str,nickname: str,domain: str, \
|
|||
expectedStr=expectedDomain+':'+str(expectedPort)
|
||||
for subdir, dirs, files in os.walk(inboxDir):
|
||||
for f in files:
|
||||
filename = os.path.join(subdir, f)
|
||||
filename=os.path.join(subdir, f)
|
||||
if not os.path.isfile(filename):
|
||||
print('filename: '+filename)
|
||||
return False
|
||||
|
@ -185,7 +185,7 @@ def getPersonPubKey(baseDir: str,session,personUrl: str, \
|
|||
if debug:
|
||||
print('DEBUG: Obtaining public key for shared inbox')
|
||||
personUrl=personUrl.replace('/users/inbox','/inbox')
|
||||
personJson = getPersonFromCache(baseDir,personUrl,personCache)
|
||||
personJson=getPersonFromCache(baseDir,personUrl,personCache)
|
||||
if not personJson:
|
||||
if debug:
|
||||
print('DEBUG: Obtaining public key for '+personUrl)
|
||||
|
@ -193,10 +193,10 @@ def getPersonPubKey(baseDir: str,session,personUrl: str, \
|
|||
if onionDomain:
|
||||
if '.onion/' in personUrl:
|
||||
personDomain=onionDomain
|
||||
asHeader = {
|
||||
asHeader={
|
||||
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
personJson = \
|
||||
personJson= \
|
||||
getJson(session,personUrl,asHeader,None,projectVersion, \
|
||||
httpPrefix,personDomain)
|
||||
if not personJson:
|
||||
|
@ -258,7 +258,7 @@ def inboxPermittedMessage(domain: str,messageJson: {},federationList: []) -> boo
|
|||
def validPublishedDate(published: str) -> bool:
|
||||
currTime=datetime.datetime.utcnow()
|
||||
pubDate=datetime.datetime.strptime(published,"%Y-%m-%dT%H:%M:%SZ")
|
||||
daysSincePublished = (currTime - pubTime).days
|
||||
daysSincePublished=(currTime - pubTime).days
|
||||
if daysSincePublished>30:
|
||||
return False
|
||||
return True
|
||||
|
@ -340,7 +340,7 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str, \
|
|||
postId=postJsonObject['id'].replace('/activity','').replace('/undo','')
|
||||
published=currTime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
if not postId:
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
if actor:
|
||||
postId=actor+'/statuses/'+statusNumber
|
||||
else:
|
||||
|
@ -372,7 +372,7 @@ def savePostToInboxQueue(baseDir: str,httpPrefix: str, \
|
|||
timeDiffStr='0'+timeDiffStr
|
||||
print('DIGEST|'+timeDiffStr+'|'+filename)
|
||||
|
||||
newQueueItem = {
|
||||
newQueueItem={
|
||||
'originalId': originalPostId,
|
||||
'id': postId,
|
||||
'actor': actor,
|
||||
|
@ -531,7 +531,7 @@ def inboxPostRecipients(baseDir :str,postJsonObject :{}, \
|
|||
domain=domain+':'+str(port)
|
||||
domainMatch='/'+domain+'/users/'
|
||||
|
||||
actor = postJsonObject['actor']
|
||||
actor=postJsonObject['actor']
|
||||
# first get any specific people which the post is addressed to
|
||||
|
||||
followerRecipients=False
|
||||
|
@ -1417,7 +1417,7 @@ def populateReplies(baseDir :str,httpPrefix :str,domain :str, \
|
|||
postRepliesFilename=postFilename.replace('.json','.replies')
|
||||
messageId=messageJson['id'].replace('/activity','').replace('/undo','')
|
||||
if os.path.isfile(postRepliesFilename):
|
||||
numLines = sum(1 for line in open(postRepliesFilename))
|
||||
numLines=sum(1 for line in open(postRepliesFilename))
|
||||
if numLines>maxReplies:
|
||||
return False
|
||||
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):
|
||||
try:
|
||||
with open(indexFilename, 'r+') as indexFile:
|
||||
content = indexFile.read()
|
||||
content=indexFile.read()
|
||||
indexFile.seek(0, 0)
|
||||
indexFile.write(destinationFilename+'\n'+content)
|
||||
return True
|
||||
|
|
57
like.py
57
like.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "like.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="like.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import json
|
||||
|
@ -135,7 +135,7 @@ def updateLikesCollection(recentPostsCache: {}, \
|
|||
if not postJsonObject['object'].get('likes'):
|
||||
if debug:
|
||||
print('DEBUG: Adding initial likes to '+objectUrl)
|
||||
likesJson = {
|
||||
likesJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'id': objectUrl,
|
||||
'type': 'Collection',
|
||||
|
@ -193,7 +193,7 @@ def like(recentPostsCache: {}, \
|
|||
if '/statuses/' in objectUrl:
|
||||
likeTo=[objectUrl.split('/statuses/')[0]]
|
||||
|
||||
newLikeJson = {
|
||||
newLikeJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Like',
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
|
@ -302,7 +302,7 @@ def undolike(recentPostsCache: {}, \
|
|||
if '/statuses/' in objectUrl:
|
||||
likeTo=[objectUrl.split('/statuses/')[0]]
|
||||
|
||||
newUndoLikeJson = {
|
||||
newUndoLikeJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Undo',
|
||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
||||
|
@ -368,8 +368,8 @@ def undoLikePost(recentPostsCache: {}, \
|
|||
if ':' not in likeDomain:
|
||||
likeDomain=likeDomain+':'+str(likePort)
|
||||
|
||||
objectUrl = \
|
||||
httpPrefix + '://'+likeDomain+'/users/'+likeNickname+ \
|
||||
objectUrl= \
|
||||
httpPrefix+'://'+likeDomain+'/users/'+likeNickname+ \
|
||||
'/statuses/'+str(likeStatusNumber)
|
||||
|
||||
ccUrl=httpPrefix+'://'+likeDomain+'/users/'+likeNickname
|
||||
|
@ -409,7 +409,7 @@ def sendLikeViaServer(baseDir: str,session, \
|
|||
if '/statuses/' in likeUrl:
|
||||
toUrl=[likeUrl.split('/statuses/')[0]]
|
||||
|
||||
newLikeJson = {
|
||||
newLikeJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Like',
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
|
@ -429,7 +429,7 @@ def sendLikeViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -445,10 +445,12 @@ def sendLikeViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newLikeJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
@ -484,7 +486,7 @@ def sendUndoLikeViaServer(baseDir: str,session, \
|
|||
if '/statuses/' in likeUrl:
|
||||
toUrl=[likeUrl.split('/statuses/')[0]]
|
||||
|
||||
newUndoLikeJson = {
|
||||
newUndoLikeJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Undo',
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
|
@ -498,8 +500,9 @@ def sendUndoLikeViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -508,7 +511,7 @@ def sendUndoLikeViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -524,10 +527,12 @@ def sendUndoLikeViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newUndoLikeJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "manualapprove.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="manualapprove.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import json
|
||||
|
@ -99,7 +99,7 @@ def manualApproveFollowRequest(session,baseDir: str, \
|
|||
print('Manual follow accept: '+approveHandle+' not in requests file '+approveFollowsFilename)
|
||||
return
|
||||
|
||||
approvefilenew = open(approveFollowsFilename+'.new', 'w+')
|
||||
approvefilenew=open(approveFollowsFilename+'.new', 'w+')
|
||||
updateApprovedFollowers=False
|
||||
followActivityfilename=None
|
||||
with open(approveFollowsFilename, 'r') as approvefile:
|
||||
|
@ -147,7 +147,7 @@ def manualApproveFollowRequest(session,baseDir: str, \
|
|||
if approveHandle not in open(followersFilename).read():
|
||||
try:
|
||||
with open(followersFilename, 'r+') as followersFile:
|
||||
content = followersFile.read()
|
||||
content=followersFile.read()
|
||||
followersFile.seek(0, 0)
|
||||
followersFile.write(approveHandle+'\n'+content)
|
||||
except Exception as e:
|
||||
|
|
14
matrix.py
14
matrix.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "matrix.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="matrix.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
|
||||
|
|
14
media.py
14
media.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "media.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="media.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
from blurhash import blurhash_encode as blurencode
|
||||
from PIL import Image
|
||||
|
|
57
metadata.py
57
metadata.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "metadata.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="metadata.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import json
|
||||
|
@ -18,7 +18,7 @@ def metaDataNodeInfo(baseDir: str,registration: bool,version: str) -> {}:
|
|||
activeAccounts=noOfAccounts(baseDir)
|
||||
activeAccountsMonthly=noOfActiveAccountsMonthly(baseDir,1)
|
||||
activeAccountsHalfYear=noOfActiveAccountsMonthly(baseDir,6)
|
||||
nodeinfo = {
|
||||
nodeinfo={
|
||||
'openRegistrations': registration,
|
||||
'protocols': ['activitypub'],
|
||||
'software': {
|
||||
|
@ -59,27 +59,28 @@ def metaDataInstance(instanceTitle: str, \
|
|||
if adminActor['type']!='Person':
|
||||
isBot=True
|
||||
|
||||
instance = {
|
||||
instance={
|
||||
'approval_required': False,
|
||||
'contact_account': {'acct': adminActor['preferredUsername'],
|
||||
'avatar': adminActor['icon']['url'],
|
||||
'avatar_static': adminActor['icon']['url'],
|
||||
'bot': isBot,
|
||||
'created_at': '2019-07-01T10:30:00Z',
|
||||
'display_name': adminActor['name'],
|
||||
'emojis': [],
|
||||
'fields': [],
|
||||
'followers_count': 1,
|
||||
'following_count': 1,
|
||||
'header': adminActor['image']['url'],
|
||||
'header_static': adminActor['image']['url'],
|
||||
'id': '1',
|
||||
'last_status_at': '2019-07-01T10:30:00Z',
|
||||
'locked': adminActor['manuallyApprovesFollowers'],
|
||||
'note': '<p>Admin of '+domain+'</p>',
|
||||
'statuses_count': 1,
|
||||
'url': httpPrefix+'://'+domainFull+'/@'+adminActor['preferredUsername'],
|
||||
'username': adminActor['preferredUsername']
|
||||
'contact_account': {
|
||||
'acct': adminActor['preferredUsername'],
|
||||
'avatar': adminActor['icon']['url'],
|
||||
'avatar_static': adminActor['icon']['url'],
|
||||
'bot': isBot,
|
||||
'created_at': '2019-07-01T10:30:00Z',
|
||||
'display_name': adminActor['name'],
|
||||
'emojis': [],
|
||||
'fields': [],
|
||||
'followers_count': 1,
|
||||
'following_count': 1,
|
||||
'header': adminActor['image']['url'],
|
||||
'header_static': adminActor['image']['url'],
|
||||
'id': '1',
|
||||
'last_status_at': '2019-07-01T10:30:00Z',
|
||||
'locked': adminActor['manuallyApprovesFollowers'],
|
||||
'note': '<p>Admin of '+domain+'</p>',
|
||||
'statuses_count': 1,
|
||||
'url': httpPrefix+'://'+domainFull+'/@'+adminActor['preferredUsername'],
|
||||
'username': adminActor['preferredUsername']
|
||||
},
|
||||
'description': instanceDescription,
|
||||
'email': 'admin@'+domain,
|
||||
|
|
14
migrate.py
14
migrate.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "migrate.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="migrate.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
|
||||
|
|
14
outbox.py
14
outbox.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "outbox.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="outbox.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import json
|
||||
|
|
162
person.py
162
person.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "person.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="person.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import time
|
||||
|
@ -42,9 +42,9 @@ from config import setConfigParam
|
|||
from config import getConfigParam
|
||||
|
||||
def generateRSAKey() -> (str,str):
|
||||
key = RSA.generate(2048)
|
||||
privateKeyPem = key.exportKey("PEM").decode("utf-8")
|
||||
publicKeyPem = key.publickey().exportKey("PEM").decode("utf-8")
|
||||
key=RSA.generate(2048)
|
||||
privateKeyPem=key.exportKey("PEM").decode("utf-8")
|
||||
publicKeyPem=key.publickey().exportKey("PEM").decode("utf-8")
|
||||
return privateKeyPem,publicKeyPem
|
||||
|
||||
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)
|
||||
if personJson:
|
||||
personJson[iconFilenameBase]['mediaType']=mediaType
|
||||
personJson[iconFilenameBase]['url']=httpPrefix+'://'+fullDomain+'/users/'+nickname+'/'+iconFilename
|
||||
personJson[iconFilenameBase]['url']= \
|
||||
httpPrefix+'://'+fullDomain+'/users/'+nickname+'/'+iconFilename
|
||||
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)
|
||||
removeMetaData(profileFilename,profileFilename)
|
||||
return True
|
||||
|
@ -180,61 +183,66 @@ def createPersonBase(baseDir: str,nickname: str,domain: str,port: int, \
|
|||
approveFollowers=True
|
||||
personType='Application'
|
||||
|
||||
newPerson = {'@context': ['https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
{'Emoji': 'toot:Emoji',
|
||||
'Hashtag': 'as:Hashtag',
|
||||
'IdentityProof': 'toot:IdentityProof',
|
||||
'PropertyValue': 'schema:PropertyValue',
|
||||
'alsoKnownAs': {'@id': 'as:alsoKnownAs', '@type': '@id'},
|
||||
'focalPoint': {'@container': '@list', '@id': 'toot:focalPoint'},
|
||||
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
|
||||
'movedTo': {'@id': 'as:movedTo', '@type': '@id'},
|
||||
'schema': 'http://schema.org#',
|
||||
'value': 'schema:value'}],
|
||||
'attachment': [],
|
||||
'alsoKnownAs': [],
|
||||
'discoverable': False,
|
||||
'endpoints': {
|
||||
'id': personId+'/endpoints',
|
||||
'sharedInbox': httpPrefix+'://'+domain+'/inbox',
|
||||
},
|
||||
'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new',
|
||||
'followers': personId+'/followers',
|
||||
'following': personId+'/following',
|
||||
'shares': personId+'/shares',
|
||||
'orgSchema': None,
|
||||
'skills': {},
|
||||
'roles': {},
|
||||
'availability': None,
|
||||
'icon': {'mediaType': 'image/png',
|
||||
'type': 'Image',
|
||||
'url': personId+'/avatar'+str(randint(10000000000000,99999999999999))+'.png'},
|
||||
'id': personId,
|
||||
'image': {'mediaType': 'image/png',
|
||||
'type': 'Image',
|
||||
'url': personId+'/image'+str(randint(10000000000000,99999999999999))+'.png'},
|
||||
'inbox': inboxStr,
|
||||
'manuallyApprovesFollowers': approveFollowers,
|
||||
'name': personName,
|
||||
'outbox': personId+'/outbox',
|
||||
'preferredUsername': personName,
|
||||
'summary': '',
|
||||
'publicKey': {
|
||||
'id': personId+'#main-key',
|
||||
'owner': personId,
|
||||
'publicKeyPem': publicKeyPem
|
||||
},
|
||||
'tag': [],
|
||||
'type': personType,
|
||||
'url': personUrl,
|
||||
'nomadicLocations': [{
|
||||
'id': personId,
|
||||
'type': 'nomadicLocation',
|
||||
'locationAddress':'acct:'+nickname+'@'+domain,
|
||||
'locationPrimary':True,
|
||||
'locationDeleted':False
|
||||
}]
|
||||
newPerson={
|
||||
'@context': ['https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
{'Emoji': 'toot:Emoji',
|
||||
'Hashtag': 'as:Hashtag',
|
||||
'IdentityProof': 'toot:IdentityProof',
|
||||
'PropertyValue': 'schema:PropertyValue',
|
||||
'alsoKnownAs': {'@id': 'as:alsoKnownAs', '@type': '@id'},
|
||||
'focalPoint': {'@container': '@list', '@id': 'toot:focalPoint'},
|
||||
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
|
||||
'movedTo': {'@id': 'as:movedTo', '@type': '@id'},
|
||||
'schema': 'http://schema.org#',
|
||||
'value': 'schema:value'}],
|
||||
'attachment': [],
|
||||
'alsoKnownAs': [],
|
||||
'discoverable': False,
|
||||
'endpoints': {
|
||||
'id': personId+'/endpoints',
|
||||
'sharedInbox': httpPrefix+'://'+domain+'/inbox',
|
||||
},
|
||||
'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new',
|
||||
'followers': personId+'/followers',
|
||||
'following': personId+'/following',
|
||||
'shares': personId+'/shares',
|
||||
'orgSchema': None,
|
||||
'skills': {},
|
||||
'roles': {},
|
||||
'availability': None,
|
||||
'icon': {
|
||||
'mediaType': 'image/png',
|
||||
'type': 'Image',
|
||||
'url': personId+'/avatar'+str(randint(10000000000000,99999999999999))+'.png'
|
||||
},
|
||||
'id': personId,
|
||||
'image': {
|
||||
'mediaType': 'image/png',
|
||||
'type': 'Image',
|
||||
'url': personId+'/image'+str(randint(10000000000000,99999999999999))+'.png'
|
||||
},
|
||||
'inbox': inboxStr,
|
||||
'manuallyApprovesFollowers': approveFollowers,
|
||||
'name': personName,
|
||||
'outbox': personId+'/outbox',
|
||||
'preferredUsername': personName,
|
||||
'summary': '',
|
||||
'publicKey': {
|
||||
'id': personId+'#main-key',
|
||||
'owner': personId,
|
||||
'publicKeyPem': publicKeyPem
|
||||
},
|
||||
'tag': [],
|
||||
'type': personType,
|
||||
'url': personUrl,
|
||||
'nomadicLocations': [{
|
||||
'id': personId,
|
||||
'type': 'nomadicLocation',
|
||||
'locationAddress':'acct:'+nickname+'@'+domain,
|
||||
'locationPrimary':True,
|
||||
'locationDeleted':False
|
||||
}]
|
||||
}
|
||||
|
||||
if nickname=='inbox':
|
||||
|
@ -344,7 +352,7 @@ def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
|
|||
if registrationsRemaining<=0:
|
||||
return None,None,None,None
|
||||
|
||||
privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint = \
|
||||
privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint= \
|
||||
createPersonBase(baseDir,nickname,domain,port,httpPrefix,saveToFile,password)
|
||||
if noOfAccounts(baseDir)==1:
|
||||
#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'
|
||||
if os.path.isfile(suspendedFilename):
|
||||
with open(suspendedFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
suspendedFile=open(suspendedFilename,"w+")
|
||||
for suspended in lines:
|
||||
if suspended.strip('\n')==nickname:
|
||||
|
@ -634,7 +642,7 @@ def unsuspendAccount(baseDir: str,nickname: str) -> None:
|
|||
suspendedFilename=baseDir+'/accounts/suspended.txt'
|
||||
if os.path.isfile(suspendedFilename):
|
||||
with open(suspendedFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
suspendedFile=open(suspendedFilename,"w+")
|
||||
for suspended in lines:
|
||||
if suspended.strip('\n')!=nickname:
|
||||
|
@ -653,7 +661,7 @@ def suspendAccount(baseDir: str,nickname: str,domain: str) -> None:
|
|||
moderatorsFile=baseDir+'/accounts/moderators.txt'
|
||||
if os.path.isfile(moderatorsFile):
|
||||
with open(moderatorsFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for moderator in lines:
|
||||
if moderator.strip('\n')==nickname:
|
||||
return
|
||||
|
@ -668,7 +676,7 @@ def suspendAccount(baseDir: str,nickname: str,domain: str) -> None:
|
|||
suspendedFilename=baseDir+'/accounts/suspended.txt'
|
||||
if os.path.isfile(suspendedFilename):
|
||||
with open(suspendedFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for suspended in lines:
|
||||
if suspended.strip('\n')==nickname:
|
||||
return
|
||||
|
@ -703,7 +711,7 @@ def canRemovePost(baseDir: str,nickname: str,domain: str,port: int,postId: str)
|
|||
moderatorsFile=baseDir+'/accounts/moderators.txt'
|
||||
if os.path.isfile(moderatorsFile):
|
||||
with open(moderatorsFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for moderator in lines:
|
||||
if domainFull+'/users/'+moderator.strip('\n')+'/' in postId:
|
||||
return False
|
||||
|
@ -720,10 +728,10 @@ def removeTagsForNickname(baseDir: str,nickname: str,domain: str,port: int) -> N
|
|||
if ':' not in domain:
|
||||
domainFull=domain+':'+str(port)
|
||||
matchStr=domainFull+'/users/'+nickname+'/'
|
||||
directory = os.fsencode(baseDir+'/tags/')
|
||||
directory=os.fsencode(baseDir+'/tags/')
|
||||
for f in os.scandir(directory):
|
||||
f=f.name
|
||||
filename = os.fsdecode(f)
|
||||
filename=os.fsdecode(f)
|
||||
if not filename.endswith(".txt"):
|
||||
continue
|
||||
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():
|
||||
continue
|
||||
with open(tagFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
tagFile=open(tagFilename,"w+")
|
||||
if tagFile:
|
||||
for tagline in lines:
|
||||
|
@ -752,7 +760,7 @@ def removeAccount(baseDir: str,nickname: str,domain: str,port: int) -> bool:
|
|||
moderatorsFile=baseDir+'/accounts/moderators.txt'
|
||||
if os.path.isfile(moderatorsFile):
|
||||
with open(moderatorsFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for moderator in lines:
|
||||
if moderator.strip('\n')==nickname:
|
||||
return False
|
||||
|
|
14
pgp.py
14
pgp.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "pgp.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="pgp.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
|
||||
|
|
261
posts.py
261
posts.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "posts.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="posts.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
@ -68,7 +68,7 @@ def isModerator(baseDir: str,nickname: str) -> bool:
|
|||
return False
|
||||
|
||||
with open(moderatorsFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
if len(lines)==0:
|
||||
if getConfigParam(baseDir,'admin')==nickname:
|
||||
return True
|
||||
|
@ -116,7 +116,7 @@ def getPersonKey(nickname: str,domain: str,baseDir: str,keyType='public', \
|
|||
return keyPem
|
||||
|
||||
def cleanHtml(rawHtml: str) -> str:
|
||||
#text = BeautifulSoup(rawHtml, 'html.parser').get_text()
|
||||
#text=BeautifulSoup(rawHtml, 'html.parser').get_text()
|
||||
text=rawHtml
|
||||
return html.unescape(text)
|
||||
|
||||
|
@ -134,8 +134,8 @@ def getUserUrl(wfRequest: {}) -> str:
|
|||
|
||||
def parseUserFeed(session,feedUrl: str,asHeader: {}, \
|
||||
projectVersion: str,httpPrefix: str,domain: str) -> None:
|
||||
feedJson = getJson(session,feedUrl,asHeader,None, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
feedJson=getJson(session,feedUrl,asHeader,None, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
if not feedJson:
|
||||
return
|
||||
|
||||
|
@ -143,11 +143,11 @@ def parseUserFeed(session,feedUrl: str,asHeader: {}, \
|
|||
for item in feedJson['orderedItems']:
|
||||
yield item
|
||||
|
||||
nextUrl = None
|
||||
nextUrl=None
|
||||
if 'first' in feedJson:
|
||||
nextUrl = feedJson['first']
|
||||
nextUrl=feedJson['first']
|
||||
elif 'next' in feedJson:
|
||||
nextUrl = feedJson['next']
|
||||
nextUrl=feedJson['next']
|
||||
|
||||
if nextUrl:
|
||||
if isinstance(nextUrl, str):
|
||||
|
@ -165,29 +165,37 @@ def getPersonBox(baseDir: str,session,wfRequest: {},personCache: {}, \
|
|||
projectVersion: str,httpPrefix: str, \
|
||||
nickname: str,domain: 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'):
|
||||
personUrl = getUserUrl(wfRequest)
|
||||
personUrl=getUserUrl(wfRequest)
|
||||
else:
|
||||
if nickname=='dev':
|
||||
# try single user instance
|
||||
print('getPersonBox: Trying single user instance with ld+json')
|
||||
personUrl = httpPrefix+'://'+domain
|
||||
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
|
||||
personUrl=httpPrefix+'://'+domain
|
||||
asHeader={
|
||||
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
else:
|
||||
personUrl = httpPrefix+'://'+domain+'/users/'+nickname
|
||||
personUrl=httpPrefix+'://'+domain+'/users/'+nickname
|
||||
if not personUrl:
|
||||
return None,None,None,None,None,None,None,None
|
||||
personJson = getPersonFromCache(baseDir,personUrl,personCache)
|
||||
personJson=getPersonFromCache(baseDir,personUrl,personCache)
|
||||
if not personJson:
|
||||
if '/channel/' in personUrl:
|
||||
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
|
||||
personJson = getJson(session,personUrl,asHeader,None, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
asHeader={
|
||||
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
personJson=getJson(session,personUrl,asHeader,None, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
if not personJson:
|
||||
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
|
||||
personJson = getJson(session,personUrl,asHeader,None, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
asHeader={
|
||||
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
personJson=getJson(session,personUrl,asHeader,None, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
if not personJson:
|
||||
print('Unable to get actor')
|
||||
return None,None,None,None,None,None,None,None
|
||||
|
@ -246,12 +254,16 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
|
|||
personPosts={}
|
||||
if not outboxUrl:
|
||||
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:
|
||||
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:
|
||||
result = []
|
||||
i = 0
|
||||
result=[]
|
||||
i=0
|
||||
userFeed=parseUserFeed(session,outboxUrl,asHeader, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
for item in userFeed:
|
||||
|
@ -262,7 +274,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
|
|||
pprint(result)
|
||||
return None
|
||||
|
||||
i = 0
|
||||
i=0
|
||||
userFeed=parseUserFeed(session,outboxUrl,asHeader, \
|
||||
projectVersion,httpPrefix,domain)
|
||||
for item in userFeed:
|
||||
|
@ -291,7 +303,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
|
|||
print('No published attribute')
|
||||
continue
|
||||
#pprint(item)
|
||||
published = item['object']['published']
|
||||
published=item['object']['published']
|
||||
if not personPosts.get(item['id']):
|
||||
# check that this is a public post
|
||||
# #Public should appear in the "to" list
|
||||
|
@ -304,7 +316,7 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
|
|||
if not isPublic:
|
||||
continue
|
||||
|
||||
content = item['object']['content'].replace(''',"'")
|
||||
content=item['object']['content'].replace(''',"'")
|
||||
|
||||
mentions=[]
|
||||
emoji={}
|
||||
|
@ -337,12 +349,12 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
|
|||
print('max emojis reached')
|
||||
continue
|
||||
|
||||
summary = ''
|
||||
summary=''
|
||||
if item['object'].get('summary'):
|
||||
if item['object']['summary']:
|
||||
summary = item['object']['summary']
|
||||
summary=item['object']['summary']
|
||||
|
||||
inReplyTo = ''
|
||||
inReplyTo=''
|
||||
if item['object'].get('inReplyTo'):
|
||||
if item['object']['inReplyTo']:
|
||||
# No replies to non-permitted domains
|
||||
|
@ -352,17 +364,17 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
|
|||
if debug:
|
||||
print('url not permitted '+item['object']['inReplyTo'])
|
||||
continue
|
||||
inReplyTo = item['object']['inReplyTo']
|
||||
inReplyTo=item['object']['inReplyTo']
|
||||
|
||||
conversation = ''
|
||||
conversation=''
|
||||
if item['object'].get('conversation'):
|
||||
if item['object']['conversation']:
|
||||
# no conversations originated in non-permitted domains
|
||||
if urlPermitted(item['object']['conversation'], \
|
||||
federationList,"objects:read"):
|
||||
conversation = item['object']['conversation']
|
||||
conversation=item['object']['conversation']
|
||||
|
||||
attachment = []
|
||||
attachment=[]
|
||||
if item['object'].get('attachment'):
|
||||
if item['object']['attachment']:
|
||||
for attach in item['object']['attachment']:
|
||||
|
@ -376,15 +388,15 @@ def getPosts(session,outboxUrl: str,maxPosts: int, \
|
|||
if debug:
|
||||
print('url not permitted '+attach['url'])
|
||||
|
||||
sensitive = False
|
||||
sensitive=False
|
||||
if item['object'].get('sensitive'):
|
||||
sensitive = item['object']['sensitive']
|
||||
sensitive=item['object']['sensitive']
|
||||
|
||||
if simple:
|
||||
print(cleanHtml(content)+'\n')
|
||||
else:
|
||||
pprint(item)
|
||||
personPosts[item['id']] = {
|
||||
personPosts[item['id']]={
|
||||
"sensitive": sensitive,
|
||||
"inreplyto": inReplyTo,
|
||||
"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':
|
||||
return
|
||||
boxDir = createPersonDir(nickname,domain,baseDir,boxname)
|
||||
boxDir=createPersonDir(nickname,domain,baseDir,boxname)
|
||||
for deleteFilename in os.scandir(boxDir):
|
||||
deleteFilename=deleteFilename.name
|
||||
filePath = os.path.join(boxDir, deleteFilename)
|
||||
filePath=os.path.join(boxDir,deleteFilename)
|
||||
try:
|
||||
if os.path.isfile(filePath):
|
||||
os.unlink(filePath)
|
||||
|
@ -431,7 +443,7 @@ def savePostToBox(baseDir: str,httpPrefix: str,postId: str, \
|
|||
domain=domain.split(':')[0]
|
||||
|
||||
if not postId:
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
postId= \
|
||||
httpPrefix+'://'+originalDomain+'/users/'+nickname+ \
|
||||
'/statuses/'+statusNumber
|
||||
|
@ -441,7 +453,7 @@ def savePostToBox(baseDir: str,httpPrefix: str,postId: str, \
|
|||
postJsonObject['object']['id']=postId
|
||||
postJsonObject['object']['atomUri']=postId
|
||||
|
||||
boxDir = createPersonDir(nickname,domain,baseDir,boxname)
|
||||
boxDir=createPersonDir(nickname,domain,baseDir,boxname)
|
||||
filename=boxDir+'/'+postId.replace('/','#')+'.json'
|
||||
saveJson(postJsonObject,filename)
|
||||
return filename
|
||||
|
@ -472,7 +484,7 @@ def updateHashtagsIndex(baseDir: str,tag: {},newPostId: str) -> None:
|
|||
if tagline not in open(tagsFilename).read():
|
||||
try:
|
||||
with open(tagsFilename, 'r+') as tagsFile:
|
||||
content = tagsFile.read()
|
||||
content=tagsFile.read()
|
||||
tagsFile.seek(0, 0)
|
||||
tagsFile.write(tagline+content)
|
||||
except Exception as e:
|
||||
|
@ -491,7 +503,7 @@ def addSchedulePost(baseDir: str,nickname: str,domain: str, \
|
|||
if indexStr not in open(scheduleIndexFilename).read():
|
||||
try:
|
||||
with open(scheduleIndexFilename, 'r+') as scheduleFile:
|
||||
content = scheduleFile.read()
|
||||
content=scheduleFile.read()
|
||||
scheduleFile.seek(0, 0)
|
||||
scheduleFile.write(indexStr+'\n'+content)
|
||||
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:
|
||||
del hashtagsDict[tagName]
|
||||
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
postTo='https://www.w3.org/ns/activitystreams#Public'
|
||||
postCC=httpPrefix+'://'+domain+'/users/'+nickname+'/followers'
|
||||
if followersOnly:
|
||||
|
@ -659,7 +671,7 @@ def createPostBase(baseDir: str,nickname: str,domain: str,port: int, \
|
|||
if oc:
|
||||
if oc.get('id'):
|
||||
capabilityIdList=[oc['id']]
|
||||
newPost = {
|
||||
newPost={
|
||||
"@context": postContext,
|
||||
'id': newPostId+'/activity',
|
||||
'capability': capabilityIdList,
|
||||
|
@ -704,7 +716,7 @@ def createPostBase(baseDir: str,nickname: str,domain: str,port: int, \
|
|||
newPost['object'],attachImageFilename, \
|
||||
mediaType,imageDescription,useBlurhash)
|
||||
else:
|
||||
newPost = {
|
||||
newPost={
|
||||
"@context": postContext,
|
||||
'id': newPostId,
|
||||
'type': 'Note',
|
||||
|
@ -788,16 +800,16 @@ def outboxMessageCreateWrap(httpPrefix: str, \
|
|||
if port!=80 and port!=443:
|
||||
if ':' not in domain:
|
||||
domain=domain+':'+str(port)
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
if messageJson.get('published'):
|
||||
published = messageJson['published']
|
||||
published=messageJson['published']
|
||||
newPostId=httpPrefix+'://'+domain+'/users/'+nickname+'/statuses/'+statusNumber
|
||||
cc=[]
|
||||
if messageJson.get('cc'):
|
||||
cc=messageJson['cc']
|
||||
# TODO
|
||||
capabilityUrl=[]
|
||||
newPost = {
|
||||
newPost={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'id': newPostId+'/activity',
|
||||
'capability': capabilityUrl,
|
||||
|
@ -1207,7 +1219,7 @@ def threadSendPost(session,postJsonStr: str,federationList: [],\
|
|||
postResult=None
|
||||
unauthorized=False
|
||||
try:
|
||||
postResult,unauthorized = \
|
||||
postResult,unauthorized= \
|
||||
postJsonString(session,postJsonStr,federationList, \
|
||||
inboxUrl,signatureHeaderJson, \
|
||||
"inbox:write",debug)
|
||||
|
@ -1272,8 +1284,8 @@ def sendPost(projectVersion: str, \
|
|||
handle=httpPrefix+'://'+toDomain+'/@'+toNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
domain,projectVersion)
|
||||
wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
domain,projectVersion)
|
||||
if not wfRequest:
|
||||
return 1
|
||||
|
||||
|
@ -1285,7 +1297,7 @@ def sendPost(projectVersion: str, \
|
|||
postToBox='tlblogs'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix, \
|
||||
nickname,domain,postToBox)
|
||||
|
@ -1305,7 +1317,7 @@ def sendPost(projectVersion: str, \
|
|||
return 5
|
||||
# sharedInbox and capabilities are optional
|
||||
|
||||
postJsonObject = \
|
||||
postJsonObject= \
|
||||
createPostBase(baseDir,nickname,domain,port, \
|
||||
toPersonId,cc,httpPrefix,content, \
|
||||
followersOnly,saveToFile,clientToServer, \
|
||||
|
@ -1328,7 +1340,7 @@ def sendPost(projectVersion: str, \
|
|||
postJsonStr=json.dumps(postJsonObject)
|
||||
|
||||
# construct the http header, including the message body digest
|
||||
signatureHeaderJson = \
|
||||
signatureHeaderJson= \
|
||||
createSignedHeader(privateKeyPem,nickname,domain,port, \
|
||||
toDomain,toPort, \
|
||||
postPath,httpPrefix,withDigest,postJsonStr)
|
||||
|
@ -1339,7 +1351,7 @@ def sendPost(projectVersion: str, \
|
|||
sendThreads[0].kill()
|
||||
sendThreads.pop(0)
|
||||
print('WARN: thread killed')
|
||||
thr = \
|
||||
thr= \
|
||||
threadWithTrace(target=threadSendPost, \
|
||||
args=(session, \
|
||||
postJsonStr, \
|
||||
|
@ -1378,7 +1390,7 @@ def sendPostViaServer(projectVersion: str, \
|
|||
handle=httpPrefix+'://'+fromDomain+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = \
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
|
@ -1391,7 +1403,7 @@ def sendPostViaServer(projectVersion: str, \
|
|||
postToBox='tlblogs'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,fromNickname, \
|
||||
fromDomain,postToBox)
|
||||
|
@ -1431,7 +1443,7 @@ def sendPostViaServer(projectVersion: str, \
|
|||
toDomainFull=toDomain+':'+str(toPort)
|
||||
toPersonId=httpPrefix+'://'+toDomainFull+'/users/'+toNickname
|
||||
|
||||
postJsonObject = \
|
||||
postJsonObject= \
|
||||
createPostBase(baseDir, \
|
||||
fromNickname,fromDomain,fromPort, \
|
||||
toPersonId,cc,httpPrefix,content, \
|
||||
|
@ -1444,9 +1456,11 @@ def sendPostViaServer(projectVersion: str, \
|
|||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
if attachImageFilename:
|
||||
headers = {'host': fromDomain, \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postImage(session,attachImageFilename,[], \
|
||||
inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
|
@ -1454,10 +1468,12 @@ def sendPostViaServer(projectVersion: str, \
|
|||
# print('DEBUG: Failed to upload image')
|
||||
# return 9
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJsonString(session,json.dumps(postJsonObject),[], \
|
||||
inboxUrl,headers,"inbox:write",debug)
|
||||
#if not postResult:
|
||||
|
@ -1578,7 +1594,7 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,nickname,domain,postToBox)
|
||||
|
||||
|
@ -1633,7 +1649,7 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
|
|||
postJsonStr=json.dumps(postJsonObject)
|
||||
|
||||
# construct the http header, including the message body digest
|
||||
signatureHeaderJson = \
|
||||
signatureHeaderJson= \
|
||||
createSignedHeader(privateKeyPem,nickname,domain,port, \
|
||||
toDomain,toPort, \
|
||||
postPath,httpPrefix,withDigest,postJsonStr)
|
||||
|
@ -1648,14 +1664,15 @@ def sendSignedJson(postJsonObject: {},session,baseDir: str, \
|
|||
if debug:
|
||||
print('DEBUG: starting thread to send post')
|
||||
pprint(postJsonObject)
|
||||
thr = threadWithTrace(target=threadSendPost, \
|
||||
args=(session, \
|
||||
postJsonStr, \
|
||||
federationList, \
|
||||
inboxUrl,baseDir, \
|
||||
signatureHeaderJson.copy(), \
|
||||
postLog,
|
||||
debug),daemon=True)
|
||||
thr= \
|
||||
threadWithTrace(target=threadSendPost, \
|
||||
args=(session, \
|
||||
postJsonStr, \
|
||||
federationList, \
|
||||
inboxUrl,baseDir, \
|
||||
signatureHeaderJson.copy(), \
|
||||
postLog,
|
||||
debug),daemon=True)
|
||||
sendThreads.append(thr)
|
||||
#thr.start()
|
||||
return 0
|
||||
|
@ -2030,7 +2047,7 @@ def createModeration(baseDir: str,nickname: str,domain: str,port: int, \
|
|||
httpPrefix: str, \
|
||||
itemsPerPage: int,headerOnly: bool, \
|
||||
ocapAlways: bool,pageNumber=None) -> {}:
|
||||
boxDir = createPersonDir(nickname,domain,baseDir,'inbox')
|
||||
boxDir=createPersonDir(nickname,domain,baseDir,'inbox')
|
||||
boxname='moderation'
|
||||
|
||||
if port:
|
||||
|
@ -2042,24 +2059,28 @@ def createModeration(baseDir: str,nickname: str,domain: str,port: int, \
|
|||
pageNumber=1
|
||||
|
||||
pageStr='?page='+str(pageNumber)
|
||||
boxHeader = {'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollection'}
|
||||
boxItems = {'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr,
|
||||
'orderedItems': [
|
||||
],
|
||||
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'type': 'OrderedCollectionPage'}
|
||||
boxHeader={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollection'
|
||||
}
|
||||
boxItems={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr,
|
||||
'orderedItems': [
|
||||
],
|
||||
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'type': 'OrderedCollectionPage'
|
||||
}
|
||||
|
||||
if isModerator(baseDir,nickname):
|
||||
moderationIndexFile=baseDir+'/accounts/moderation.txt'
|
||||
if os.path.isfile(moderationIndexFile):
|
||||
with open(moderationIndexFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
boxHeader['totalItems']=len(lines)
|
||||
if headerOnly:
|
||||
return boxHeader
|
||||
|
@ -2237,7 +2258,7 @@ def createSharedInboxIndex(baseDir: str,sharedBoxDir: str, \
|
|||
# is the actor followed by this account?
|
||||
if not followingHandles:
|
||||
with open(followingFilename, 'r') as followingFile:
|
||||
followingHandles = followingFile.read()
|
||||
followingHandles=followingFile.read()
|
||||
if actorNickname+'@'+actorDomain not in followingHandles:
|
||||
continue
|
||||
|
||||
|
@ -2304,7 +2325,7 @@ def addPostToTimeline(filePath: str,boxname: str, \
|
|||
""" Reads a post from file and decides whether it is valid
|
||||
"""
|
||||
with open(filePath, 'r') as postFile:
|
||||
postStr = postFile.read()
|
||||
postStr=postFile.read()
|
||||
return addPostStringToTimeline(postStr,boxname,postsInBox,boxActor)
|
||||
return False
|
||||
|
||||
|
@ -2327,17 +2348,17 @@ def createBoxIndexed(recentPostsCache: {}, \
|
|||
if boxname!='dm' and boxname!='tlreplies' and \
|
||||
boxname!='tlmedia' and boxname!='tlblogs' and \
|
||||
boxname!='tlbookmarks':
|
||||
boxDir = createPersonDir(nickname,domain,baseDir,boxname)
|
||||
boxDir=createPersonDir(nickname,domain,baseDir,boxname)
|
||||
else:
|
||||
# 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
|
||||
|
||||
sharedBoxDir=None
|
||||
if boxname=='inbox' or boxname=='tlreplies' or \
|
||||
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
|
||||
indexBoxName=boxname
|
||||
|
@ -2365,18 +2386,22 @@ def createBoxIndexed(recentPostsCache: {}, \
|
|||
pageStr='?page='+str(pageNumber)
|
||||
except:
|
||||
pass
|
||||
boxHeader = {'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollection'}
|
||||
boxItems = {'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr,
|
||||
'orderedItems': [
|
||||
],
|
||||
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'type': 'OrderedCollectionPage'}
|
||||
boxHeader={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'last': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+'?page=true',
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollection'
|
||||
}
|
||||
boxItems={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname+pageStr,
|
||||
'orderedItems': [
|
||||
],
|
||||
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/'+boxname,
|
||||
'type': 'OrderedCollectionPage'
|
||||
}
|
||||
|
||||
postsInBox=[]
|
||||
|
||||
|
@ -2528,7 +2553,7 @@ def archivePostsForPerson(httpPrefix: str,nickname: str,domain: str,baseDir: str
|
|||
if archiveDir:
|
||||
if not os.path.isdir(archiveDir):
|
||||
os.mkdir(archiveDir)
|
||||
boxDir = createPersonDir(nickname,domain,baseDir,boxname)
|
||||
boxDir=createPersonDir(nickname,domain,baseDir,boxname)
|
||||
postsInBox=os.scandir(boxDir)
|
||||
noOfPosts=0
|
||||
for f in postsInBox:
|
||||
|
@ -2624,7 +2649,7 @@ def getPublicPostsOfPerson(baseDir: str,nickname: str,domain: str, \
|
|||
debug: bool,projectVersion: str) -> None:
|
||||
""" This is really just for test purposes
|
||||
"""
|
||||
session = createSession(useTor)
|
||||
session=createSession(useTor)
|
||||
personCache={}
|
||||
cachedWebfingers={}
|
||||
federationList=[]
|
||||
|
@ -2635,7 +2660,7 @@ def getPublicPostsOfPerson(baseDir: str,nickname: str,domain: str, \
|
|||
if ':' not in domain:
|
||||
domainFull=domain+':'+str(port)
|
||||
handle=httpPrefix+"://"+domainFull+"/@"+nickname
|
||||
wfRequest = \
|
||||
wfRequest= \
|
||||
webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
domain,projectVersion)
|
||||
if not wfRequest:
|
||||
|
@ -2644,12 +2669,12 @@ def getPublicPostsOfPerson(baseDir: str,nickname: str,domain: str, \
|
|||
personUrl,pubKeyId,pubKey,personId,shaedInbox,capabilityAcquisition,avatarUrl,displayName= \
|
||||
getPersonBox(baseDir,session,wfRequest,personCache, \
|
||||
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
|
||||
maxEmoji=10
|
||||
maxAttachments=5
|
||||
userPosts = \
|
||||
userPosts= \
|
||||
getPosts(session,personUrl,30,maxMentions,maxEmoji, \
|
||||
maxAttachments,federationList, \
|
||||
personCache,raw,simple,debug, \
|
||||
|
|
18
question.py
18
question.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "question.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="question.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
from utils import locatePost
|
||||
|
@ -76,7 +76,7 @@ def questionUpdateVotes(baseDir: str,nickname: str,domain: str,replyJson: {}) ->
|
|||
else:
|
||||
# change an entry in the voters file
|
||||
with open(votersFilename, "r") as votersFile:
|
||||
lines = votersFile.readlines()
|
||||
lines=votersFile.readlines()
|
||||
newlines=[]
|
||||
saveVotersFile=False
|
||||
for voteLine in lines:
|
||||
|
@ -101,7 +101,7 @@ def questionUpdateVotes(baseDir: str,nickname: str,domain: str,replyJson: {}) ->
|
|||
continue
|
||||
totalItems=0
|
||||
with open(votersFilename, "r") as votersFile:
|
||||
lines = votersFile.readlines()
|
||||
lines=votersFile.readlines()
|
||||
for voteLine in lines:
|
||||
if voteLine.endswith(votersFileSeparator+possibleAnswer['name']+'\n'):
|
||||
totalItems+=1
|
||||
|
|
47
roles.py
47
roles.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "roles.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="roles.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import os
|
||||
|
@ -23,10 +23,10 @@ def clearModeratorStatus(baseDir: str) -> None:
|
|||
This could be slow if there are many users, but only happens
|
||||
rarely when moderators are appointed or removed
|
||||
"""
|
||||
directory = os.fsencode(baseDir+'/accounts/')
|
||||
directory=os.fsencode(baseDir+'/accounts/')
|
||||
for f in os.scandir(directory):
|
||||
f=f.name
|
||||
filename = os.fsdecode(f)
|
||||
filename=os.fsdecode(f)
|
||||
if filename.endswith(".json") and '@' in filename:
|
||||
filename=os.path.join(baseDir+'/accounts/', filename)
|
||||
if '"moderator"' in open(filename).read():
|
||||
|
@ -46,7 +46,7 @@ def addModerator(baseDir: str,nickname: str,domain: str) -> None:
|
|||
if os.path.isfile(moderatorsFile):
|
||||
# is this nickname already in the file?
|
||||
with open(moderatorsFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for moderator in lines:
|
||||
moderator=moderator.strip('\n')
|
||||
if line==nickname:
|
||||
|
@ -70,7 +70,7 @@ def removeModerator(baseDir: str,nickname: str):
|
|||
if not os.path.isfile(moderatorsFile):
|
||||
return
|
||||
with open(moderatorsFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
with open(moderatorsFile, "w") as f:
|
||||
for moderator in lines:
|
||||
moderator=moderator.strip('\n')
|
||||
|
@ -229,14 +229,17 @@ def sendRoleViaServer(baseDir: str,session, \
|
|||
if ':' not in delegatorDomain:
|
||||
delegatorDomainFull=delegatorDomain+':'+str(fromPort)
|
||||
|
||||
toUrl = httpPrefix+'://'+delegatorDomainFull+'/users/'+nickname
|
||||
ccUrl = httpPrefix+'://'+delegatorDomainFull+'/users/'+delegatorNickname+'/followers'
|
||||
toUrl= \
|
||||
httpPrefix+'://'+delegatorDomainFull+'/users/'+nickname
|
||||
ccUrl= \
|
||||
httpPrefix+'://'+delegatorDomainFull+'/users/'+ \
|
||||
delegatorNickname+'/followers'
|
||||
|
||||
if role:
|
||||
roleStr=project.lower()+';'+role.lower()
|
||||
else:
|
||||
roleStr=project.lower()+';'
|
||||
newRoleJson = {
|
||||
newRoleJson={
|
||||
'type': 'Delegate',
|
||||
'actor': httpPrefix+'://'+delegatorDomainFull+'/users/'+delegatorNickname,
|
||||
'object': {
|
||||
|
@ -253,8 +256,8 @@ def sendRoleViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+delegatorDomainFull+'/@'+delegatorNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
delegatorDomain,projectVersion)
|
||||
wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
delegatorDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -263,7 +266,7 @@ def sendRoleViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix, \
|
||||
delegatorNickname,delegatorDomain,postToBox)
|
||||
|
@ -279,10 +282,12 @@ def sendRoleViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(delegatorNickname,password)
|
||||
|
||||
headers = {'host': delegatorDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': delegatorDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newRoleJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
|
16
schedule.py
16
schedule.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "schedule.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="schedule.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
@ -70,7 +70,7 @@ def updatePostSchedule(baseDir: str,handle: str,httpd,maxScheduledPosts: int) ->
|
|||
# set the published time
|
||||
# If this is not recent then http checks on the receiving side
|
||||
# will reject it
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
if postJsonObject.get('published'):
|
||||
postJsonObject['published']=published
|
||||
if postJsonObject.get('object'):
|
||||
|
|
31
session.py
31
session.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "session.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="session.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -15,11 +15,11 @@ import json
|
|||
baseDirectory=None
|
||||
|
||||
def createSession(onionRoute: bool):
|
||||
session = requests.session()
|
||||
session=requests.session()
|
||||
if onionRoute:
|
||||
session.proxies = {}
|
||||
session.proxies['http'] = 'socks5h://localhost:9050'
|
||||
session.proxies['https'] = 'socks5h://localhost:9050'
|
||||
session.proxies={}
|
||||
session.proxies['http']='socks5h://localhost:9050'
|
||||
session.proxies['https']='socks5h://localhost:9050'
|
||||
return session
|
||||
|
||||
def getJson(session,url: str,headers: {},params: {}, \
|
||||
|
@ -94,11 +94,12 @@ def postJsonString(session,postJsonStr: str, \
|
|||
print('postJson: '+inboxUrl+' not permitted by capabilities')
|
||||
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==400:
|
||||
# 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):
|
||||
# return True
|
||||
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
|
||||
|
||||
with open(attachImageFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
postResult = session.post(url=inboxUrl, data=mediaBinary, headers=headers)
|
||||
mediaBinary=avFile.read()
|
||||
postResult=session.post(url=inboxUrl,data=mediaBinary,headers=headers)
|
||||
if postResult:
|
||||
return postResult.text
|
||||
return None
|
||||
|
|
78
shares.py
78
shares.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "shares.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="shares.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import os
|
||||
|
@ -142,7 +142,7 @@ def addShare(baseDir: str, \
|
|||
os.remove(imageFilename)
|
||||
imageUrl=httpPrefix+'://'+domainFull+'/sharefiles/'+nickname+'/'+itemID+'.gif'
|
||||
|
||||
sharesJson[itemID] = {
|
||||
sharesJson[itemID]={
|
||||
"displayName": displayName,
|
||||
"summary": summary,
|
||||
"imageUrl": imageUrl,
|
||||
|
@ -261,25 +261,27 @@ def getSharesFeedForPerson(baseDir: str, \
|
|||
sharesJson=loadJson(sharesFilename)
|
||||
if sharesJson:
|
||||
noOfShares=len(sharesJson.items())
|
||||
shares = {
|
||||
shares={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page=1',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares',
|
||||
'totalItems': str(noOfShares),
|
||||
'type': 'OrderedCollection'}
|
||||
'type': 'OrderedCollection'
|
||||
}
|
||||
return shares
|
||||
|
||||
if not pageNumber:
|
||||
pageNumber=1
|
||||
|
||||
nextPageNumber=int(pageNumber+1)
|
||||
shares = {
|
||||
shares={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page='+str(pageNumber),
|
||||
'orderedItems': [],
|
||||
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/shares',
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollectionPage'}
|
||||
'type': 'OrderedCollectionPage'
|
||||
}
|
||||
|
||||
if not os.path.isfile(sharesFilename):
|
||||
print("test5")
|
||||
|
@ -332,10 +334,10 @@ def sendShareViaServer(baseDir,session, \
|
|||
if ':' not in fromDomain:
|
||||
fromDomainFull=fromDomain+':'+str(fromPort)
|
||||
|
||||
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
toUrl='https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
|
||||
newShareJson = {
|
||||
newShareJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Add',
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
|
@ -358,8 +360,8 @@ def sendShareViaServer(baseDir,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -368,7 +370,7 @@ def sendShareViaServer(baseDir,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix, \
|
||||
fromNickname,fromDomain,postToBox)
|
||||
|
@ -385,15 +387,19 @@ def sendShareViaServer(baseDir,session, \
|
|||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
if imageFilename:
|
||||
headers = {'host': fromDomain, \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postImage(session,imageFilename,[],inboxUrl.replace('/'+postToBox,'/shares'),headers,"inbox:write")
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newShareJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
@ -424,10 +430,10 @@ def sendUndoShareViaServer(baseDir: str,session, \
|
|||
if ':' not in fromDomain:
|
||||
fromDomainFull=fromDomain+':'+str(fromPort)
|
||||
|
||||
toUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl = httpPrefix + '://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
toUrl='https://www.w3.org/ns/activitystreams#Public'
|
||||
ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
|
||||
|
||||
undoShareJson = {
|
||||
undoShareJson={
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
'type': 'Remove',
|
||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
||||
|
@ -445,8 +451,8 @@ def sendUndoShareViaServer(baseDir: str,session, \
|
|||
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
fromDomain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -455,7 +461,7 @@ def sendUndoShareViaServer(baseDir: str,session, \
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix, \
|
||||
fromNickname,fromDomain,postToBox)
|
||||
|
@ -471,10 +477,12 @@ def sendUndoShareViaServer(baseDir: str,session, \
|
|||
|
||||
authHeader=createBasicAuthHeader(fromNickname,password)
|
||||
|
||||
headers = {'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': fromDomain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,undoShareJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
|
36
skills.py
36
skills.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "skills.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="skills.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import os
|
||||
|
@ -109,14 +109,14 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
|
|||
if ':' not in domain:
|
||||
domainFull=domain+':'+str(port)
|
||||
|
||||
toUrl = httpPrefix+'://'+domainFull+'/users/'+nickname
|
||||
ccUrl = httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers'
|
||||
toUrl=httpPrefix+'://'+domainFull+'/users/'+nickname
|
||||
ccUrl=httpPrefix+'://'+domainFull+'/users/'+nickname+'/followers'
|
||||
|
||||
if skillLevelPercent:
|
||||
skillStr=skill+';'+str(skillLevelPercent)
|
||||
else:
|
||||
skillStr=skill+';0'
|
||||
newSkillJson = {
|
||||
newSkillJson={
|
||||
'type': 'Skill',
|
||||
'actor': httpPrefix+'://'+domainFull+'/users/'+nickname,
|
||||
'object': '"'+skillStr+'"',
|
||||
|
@ -127,8 +127,8 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
|
|||
handle=httpPrefix+'://'+domainFull+'/@'+nickname
|
||||
|
||||
# lookup the inbox for the To handle
|
||||
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
domain,projectVersion)
|
||||
wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
|
||||
domain,projectVersion)
|
||||
if not wfRequest:
|
||||
if debug:
|
||||
print('DEBUG: announce webfinger failed for '+handle)
|
||||
|
@ -137,7 +137,7 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
|
|||
postToBox='outbox'
|
||||
|
||||
# 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, \
|
||||
projectVersion,httpPrefix,nickname,domain,postToBox)
|
||||
|
||||
|
@ -152,10 +152,12 @@ def sendSkillViaServer(baseDir: str,session,nickname: str,password: str,
|
|||
|
||||
authHeader=createBasicAuthHeader(Nickname,password)
|
||||
|
||||
headers = {'host': domain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader}
|
||||
postResult = \
|
||||
headers={
|
||||
'host': domain, \
|
||||
'Content-type': 'application/json', \
|
||||
'Authorization': authHeader
|
||||
}
|
||||
postResult= \
|
||||
postJson(session,newSkillJson,[],inboxUrl,headers,"inbox:write")
|
||||
#if not postResult:
|
||||
# if debug:
|
||||
|
|
14
ssb.py
14
ssb.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "ssb.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="ssb.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
|
||||
|
|
137
tests.py
137
tests.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "tests.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="tests.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import base64
|
||||
import time
|
||||
|
@ -77,9 +77,9 @@ from content import addHtmlTags
|
|||
from content import removeLongWords
|
||||
from theme import setCSSparam
|
||||
|
||||
testServerAliceRunning = False
|
||||
testServerBobRunning = False
|
||||
testServerEveRunning = False
|
||||
testServerAliceRunning=False
|
||||
testServerBobRunning=False
|
||||
testServerEveRunning=False
|
||||
thrAlice=None
|
||||
thrBob=None
|
||||
thrEve=None
|
||||
|
@ -103,7 +103,11 @@ def testHttpsigBase(withDigest):
|
|||
privateKeyPem,publicKeyPem,person,wfEndpoint= \
|
||||
createPerson(path,nickname,domain,port,httpPrefix,False,password)
|
||||
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)
|
||||
|
||||
headersDomain=domain
|
||||
|
@ -115,23 +119,33 @@ def testHttpsigBase(withDigest):
|
|||
dateStr=strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
|
||||
boxpath='/inbox'
|
||||
if not withDigest:
|
||||
headers = {'host': headersDomain,'date': dateStr,'content-type': 'application/json'}
|
||||
signatureHeader = \
|
||||
headers={
|
||||
'host': headersDomain,
|
||||
'date': dateStr,
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
signatureHeader= \
|
||||
signPostHeaders(dateStr,privateKeyPem, nickname, \
|
||||
domain, port, \
|
||||
domain, port, \
|
||||
boxpath, httpPrefix, None)
|
||||
else:
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
bodyDigest=messageContentDigest(messageBodyJsonStr)
|
||||
contentLength=len(messageBodyJsonStr)
|
||||
headers = {'host': headersDomain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType,'content-length': str(contentLength)}
|
||||
signatureHeader = \
|
||||
headers={
|
||||
'host': headersDomain,
|
||||
'date': dateStr,
|
||||
'digest': f'SHA-256={bodyDigest}',
|
||||
'content-type': contentType,
|
||||
'content-length': str(contentLength)
|
||||
}
|
||||
signatureHeader= \
|
||||
signPostHeaders(dateStr,privateKeyPem, nickname, \
|
||||
domain, port, \
|
||||
domain, port, \
|
||||
boxpath, httpPrefix, messageBodyJsonStr)
|
||||
|
||||
headers['signature'] = signatureHeader
|
||||
headers['signature']=signatureHeader
|
||||
assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
|
||||
boxpath,False,None, \
|
||||
messageBodyJsonStr,False)
|
||||
|
@ -149,14 +163,24 @@ def testHttpsigBase(withDigest):
|
|||
messageBodyJsonStr,False) == False
|
||||
if not withDigest:
|
||||
# fake domain
|
||||
headers = {'host': 'bogon.domain','date': dateStr,'content-type': 'application/json'}
|
||||
headers={
|
||||
'host': 'bogon.domain',
|
||||
'date': dateStr,
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
else:
|
||||
# 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)
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
headers = {'host': domain,'date': dateStr,'digest': f'SHA-256={bodyDigest}','content-type': contentType,'content-length': str(contentLength)}
|
||||
headers['signature'] = signatureHeader
|
||||
bodyDigest=messageContentDigest(messageBodyJsonStr)
|
||||
headers={
|
||||
'host': domain,
|
||||
'date': dateStr,
|
||||
'digest': f'SHA-256={bodyDigest}',
|
||||
'content-type': contentType,
|
||||
'content-length': str(contentLength)
|
||||
}
|
||||
headers['signature']=signatureHeader
|
||||
assert verifyPostHeaders(httpPrefix,publicKeyPem,headers, \
|
||||
boxpath,True,None, \
|
||||
messageBodyJsonStr,False) == False
|
||||
|
@ -184,7 +208,8 @@ def testThreadsFunction(param: str):
|
|||
|
||||
def testThreads():
|
||||
print('testThreads')
|
||||
thr = threadWithTrace(target=testThreadsFunction,args=('test',),daemon=True)
|
||||
thr= \
|
||||
threadWithTrace(target=testThreadsFunction,args=('test',),daemon=True)
|
||||
thr.start()
|
||||
assert thr.isAlive()==True
|
||||
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", \
|
||||
False, True, clientToServer,None,None,useBlurhash)
|
||||
global testServerAliceRunning
|
||||
testServerAliceRunning = True
|
||||
testServerAliceRunning=True
|
||||
maxMentions=10
|
||||
maxEmoji=10
|
||||
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", \
|
||||
False, True, clientToServer,None,None,useBlurhash)
|
||||
global testServerBobRunning
|
||||
testServerBobRunning = True
|
||||
testServerBobRunning=True
|
||||
maxMentions=10
|
||||
maxEmoji=10
|
||||
onionDomain=None
|
||||
|
@ -329,7 +354,7 @@ def createServerEve(path: str,domain: str,port: int,federationList: [], \
|
|||
deleteAllPosts(path,nickname,domain,'inbox')
|
||||
deleteAllPosts(path,nickname,domain,'outbox')
|
||||
global testServerEveRunning
|
||||
testServerEveRunning = True
|
||||
testServerEveRunning=True
|
||||
maxMentions=10
|
||||
maxEmoji=10
|
||||
onionDomain=None
|
||||
|
@ -346,8 +371,8 @@ def testPostMessageBetweenServers():
|
|||
|
||||
global testServerAliceRunning
|
||||
global testServerBobRunning
|
||||
testServerAliceRunning = False
|
||||
testServerBobRunning = False
|
||||
testServerAliceRunning=False
|
||||
testServerBobRunning=False
|
||||
|
||||
httpPrefix='http'
|
||||
useTor=False
|
||||
|
@ -380,7 +405,7 @@ def testPostMessageBetweenServers():
|
|||
time.sleep(1)
|
||||
thrAlice.kill()
|
||||
|
||||
thrAlice = \
|
||||
thrAlice= \
|
||||
threadWithTrace(target=createServerAlice, \
|
||||
args=(aliceDir,aliceDomain,alicePort,bobAddress, \
|
||||
federationList,False,False, \
|
||||
|
@ -393,7 +418,7 @@ def testPostMessageBetweenServers():
|
|||
time.sleep(1)
|
||||
thrBob.kill()
|
||||
|
||||
thrBob = \
|
||||
thrBob= \
|
||||
threadWithTrace(target=createServerBob, \
|
||||
args=(bobDir,bobDomain,bobPort,aliceAddress, \
|
||||
federationList,False,False, \
|
||||
|
@ -413,11 +438,11 @@ def testPostMessageBetweenServers():
|
|||
print('\n\n*******************************************************')
|
||||
print('Alice sends to Bob')
|
||||
os.chdir(aliceDir)
|
||||
sessionAlice = createSession(useTor)
|
||||
sessionAlice=createSession(useTor)
|
||||
inReplyTo=None
|
||||
inReplyToAtomUri=None
|
||||
subject=None
|
||||
alicePostLog = []
|
||||
alicePostLog=[]
|
||||
followersOnly=False
|
||||
saveToFile=True
|
||||
clientToServer=False
|
||||
|
@ -433,7 +458,7 @@ def testPostMessageBetweenServers():
|
|||
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
|
||||
|
||||
sendResult = \
|
||||
sendResult= \
|
||||
sendPost(__version__, \
|
||||
sessionAlice,aliceDir,'alice',aliceDomain,alicePort, \
|
||||
'bob',bobDomain,bobPort,ccUrl,httpPrefix, \
|
||||
|
@ -486,8 +511,8 @@ def testPostMessageBetweenServers():
|
|||
followPerson(aliceDir,'alice',aliceDomain,'bob', \
|
||||
bobDomain+':'+str(bobPort),federationList,False)
|
||||
|
||||
sessionBob = createSession(useTor)
|
||||
bobPostLog = []
|
||||
sessionBob=createSession(useTor)
|
||||
bobPostLog=[]
|
||||
bobPersonCache={}
|
||||
bobCachedWebfingers={}
|
||||
statusNumber=None
|
||||
|
@ -563,8 +588,8 @@ def testFollowBetweenServers():
|
|||
|
||||
global testServerAliceRunning
|
||||
global testServerBobRunning
|
||||
testServerAliceRunning = False
|
||||
testServerBobRunning = False
|
||||
testServerAliceRunning=False
|
||||
testServerBobRunning=False
|
||||
|
||||
httpPrefix='http'
|
||||
useTor=False
|
||||
|
@ -597,7 +622,7 @@ def testFollowBetweenServers():
|
|||
time.sleep(1)
|
||||
thrAlice.kill()
|
||||
|
||||
thrAlice = \
|
||||
thrAlice= \
|
||||
threadWithTrace(target=createServerAlice, \
|
||||
args=(aliceDir,aliceDomain,alicePort,bobAddress, \
|
||||
federationList,False,False, \
|
||||
|
@ -610,7 +635,7 @@ def testFollowBetweenServers():
|
|||
time.sleep(1)
|
||||
thrBob.kill()
|
||||
|
||||
thrBob = \
|
||||
thrBob= \
|
||||
threadWithTrace(target=createServerBob, \
|
||||
args=(bobDir,bobDomain,bobPort,aliceAddress, \
|
||||
federationList,False,False, \
|
||||
|
@ -638,11 +663,11 @@ def testFollowBetweenServers():
|
|||
print('*********************************************************')
|
||||
print('Alice sends a follow request to Bob')
|
||||
os.chdir(aliceDir)
|
||||
sessionAlice = createSession(useTor)
|
||||
sessionAlice=createSession(useTor)
|
||||
inReplyTo=None
|
||||
inReplyToAtomUri=None
|
||||
subject=None
|
||||
alicePostLog = []
|
||||
alicePostLog=[]
|
||||
followersOnly=False
|
||||
saveToFile=True
|
||||
clientToServer=False
|
||||
|
@ -650,7 +675,7 @@ def testFollowBetweenServers():
|
|||
alicePersonCache={}
|
||||
aliceCachedWebfingers={}
|
||||
alicePostLog=[]
|
||||
sendResult = \
|
||||
sendResult= \
|
||||
sendFollowRequest(sessionAlice,aliceDir, \
|
||||
'alice',aliceDomain,alicePort,httpPrefix, \
|
||||
'bob',bobDomain,bobPort,httpPrefix, \
|
||||
|
@ -671,13 +696,13 @@ def testFollowBetweenServers():
|
|||
|
||||
print('\n\n*********************************************************')
|
||||
print('Alice sends a message to Bob')
|
||||
alicePostLog = []
|
||||
alicePostLog=[]
|
||||
alicePersonCache={}
|
||||
aliceCachedWebfingers={}
|
||||
alicePostLog=[]
|
||||
useBlurhash=False
|
||||
isArticle=False
|
||||
sendResult = \
|
||||
sendResult= \
|
||||
sendPost(__version__, \
|
||||
sessionAlice,aliceDir,'alice',aliceDomain,alicePort, \
|
||||
'bob',bobDomain,bobPort,ccUrl, \
|
||||
|
@ -860,7 +885,7 @@ def testFollows():
|
|||
followPerson(baseDir,nickname,domain,'batman','mesh.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
|
||||
for followingDomain in f:
|
||||
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,'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:
|
||||
testDomain=followerDomain.split('@')[1].replace('\n','')
|
||||
if testDomain not in federationList:
|
||||
|
@ -949,7 +974,7 @@ def testDelegateRoles():
|
|||
httpPrefix='http'
|
||||
project='artechoke'
|
||||
role='delegator'
|
||||
newRoleJson = {
|
||||
newRoleJson={
|
||||
'type': 'Delegate',
|
||||
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
|
||||
'object': {
|
||||
|
@ -970,7 +995,7 @@ def testDelegateRoles():
|
|||
assert '"delegator"' in open(baseDir+'/accounts/'+nickname+'@'+domain+'.json').read()
|
||||
assert '"delegator"' in open(baseDir+'/accounts/'+nicknameDelegated+'@'+domain+'.json').read()
|
||||
|
||||
newRoleJson = {
|
||||
newRoleJson={
|
||||
'type': 'Delegate',
|
||||
'actor': httpPrefix+'://'+domain+'/users/'+nicknameDelegated,
|
||||
'object': {
|
||||
|
@ -1030,8 +1055,8 @@ def testClientToServer():
|
|||
|
||||
global testServerAliceRunning
|
||||
global testServerBobRunning
|
||||
testServerAliceRunning = False
|
||||
testServerBobRunning = False
|
||||
testServerAliceRunning=False
|
||||
testServerBobRunning=False
|
||||
|
||||
httpPrefix='http'
|
||||
useTor=False
|
||||
|
@ -1064,7 +1089,7 @@ def testClientToServer():
|
|||
time.sleep(1)
|
||||
thrAlice.kill()
|
||||
|
||||
thrAlice = \
|
||||
thrAlice= \
|
||||
threadWithTrace(target=createServerAlice, \
|
||||
args=(aliceDir,aliceDomain,alicePort,bobAddress, \
|
||||
federationList,False,False, \
|
||||
|
@ -1077,7 +1102,7 @@ def testClientToServer():
|
|||
time.sleep(1)
|
||||
thrBob.kill()
|
||||
|
||||
thrBob = \
|
||||
thrBob= \
|
||||
threadWithTrace(target=createServerBob, \
|
||||
args=(bobDir,bobDomain,bobPort,aliceAddress, \
|
||||
federationList,False,False, \
|
||||
|
@ -1103,7 +1128,7 @@ def testClientToServer():
|
|||
print('\n\n*******************************************************')
|
||||
print('Alice sends to Bob via c2s')
|
||||
|
||||
sessionAlice = createSession(useTor)
|
||||
sessionAlice=createSession(useTor)
|
||||
followersOnly=False
|
||||
attachedImageFilename=baseDir+'/img/logo.png'
|
||||
mediaType=getAttachmentMediaType(attachedImageFilename)
|
||||
|
@ -1214,7 +1239,7 @@ def testClientToServer():
|
|||
|
||||
|
||||
print('\n\nBob likes the post')
|
||||
sessionBob = createSession(useTor)
|
||||
sessionBob=createSession(useTor)
|
||||
password='bobpass'
|
||||
outboxPath=bobDir+'/accounts/bob@'+bobDomain+'/outbox'
|
||||
inboxPath=aliceDir+'/accounts/alice@'+aliceDomain+'/inbox'
|
||||
|
@ -1262,7 +1287,7 @@ def testClientToServer():
|
|||
|
||||
inboxPath=bobDir+'/accounts/bob@'+bobDomain+'/inbox'
|
||||
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))
|
||||
password='alicepass'
|
||||
sendDeleteViaServer(aliceDir,sessionAlice,'alice',password,
|
||||
|
@ -1418,7 +1443,7 @@ def testGetStatusNumber():
|
|||
print('testGetStatusNumber')
|
||||
prevStatusNumber=None
|
||||
for i in range(1,20):
|
||||
statusNumber,published = getStatusNumber()
|
||||
statusNumber,published=getStatusNumber()
|
||||
if prevStatusNumber:
|
||||
assert len(statusNumber) == 18
|
||||
assert int(statusNumber) > prevStatusNumber
|
||||
|
|
14
theme.py
14
theme.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "theme.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="theme.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
from utils import loadJson
|
||||
|
|
28
threads.py
28
threads.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "threads.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="threads.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import threading
|
||||
import os
|
||||
|
@ -20,9 +20,9 @@ class threadWithTrace(threading.Thread):
|
|||
tries=0
|
||||
while tries<3:
|
||||
try:
|
||||
self._args, self._keywords = args, keywords
|
||||
threading.Thread.__init__(self, *self._args, **self._keywords)
|
||||
self.killed = False
|
||||
self._args,self._keywords=args,keywords
|
||||
threading.Thread.__init__(self,*self._args,**self._keywords)
|
||||
self.killed=False
|
||||
break
|
||||
except Exception as e:
|
||||
print('ERROR: threads.py/__init__ failed - '+str(e))
|
||||
|
@ -33,8 +33,8 @@ class threadWithTrace(threading.Thread):
|
|||
tries=0
|
||||
while tries<3:
|
||||
try:
|
||||
self.__run_backup = self.run
|
||||
self.run = self.__run
|
||||
self.__run_backup=self.run
|
||||
self.run=self.__run
|
||||
threading.Thread.start(self)
|
||||
break
|
||||
except Exception as e:
|
||||
|
@ -47,7 +47,7 @@ class threadWithTrace(threading.Thread):
|
|||
def __run(self):
|
||||
sys.settrace(self.globaltrace)
|
||||
self.__run_backup()
|
||||
self.run = self.__run_backup
|
||||
self.run=self.__run_backup
|
||||
|
||||
def globaltrace(self, frame, event, arg):
|
||||
if event == 'call':
|
||||
|
@ -62,7 +62,7 @@ class threadWithTrace(threading.Thread):
|
|||
return self.localtrace
|
||||
|
||||
def kill(self):
|
||||
self.killed = True
|
||||
self.killed=True
|
||||
|
||||
def clone(self,fn):
|
||||
return threadWithTrace(target=fn, \
|
||||
|
|
14
tox.py
14
tox.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "tox.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="tox.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
|
||||
|
|
42
utils.py
42
utils.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "utils.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="utils.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
@ -198,17 +198,21 @@ def getDomainFromActor(actor: str) -> (str,int):
|
|||
"""
|
||||
port=None
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
domain=domain.split('/')[0]
|
||||
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:
|
||||
portStr=domain.split(':')[1]
|
||||
if not portStr.isdigit():
|
||||
|
@ -252,7 +256,7 @@ def followPerson(baseDir: str,nickname: str, domain: str, \
|
|||
# remove them from the unfollowed file
|
||||
newLines=''
|
||||
with open(unfollowedFilename, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
for line in lines:
|
||||
if handleToFollow not in line:
|
||||
newLines+=line
|
||||
|
@ -271,7 +275,7 @@ def followPerson(baseDir: str,nickname: str, domain: str, \
|
|||
# prepend to follow file
|
||||
try:
|
||||
with open(filename, 'r+') as followFile:
|
||||
content = followFile.read()
|
||||
content=followFile.read()
|
||||
followFile.seek(0, 0)
|
||||
followFile.write(handleToFollow+'\n'+content)
|
||||
if debug:
|
||||
|
@ -347,7 +351,7 @@ def removeModerationPostFromIndex(baseDir: str,postUrl: str,debug: bool) -> None
|
|||
postId=postUrl.replace('/activity','')
|
||||
if postId in open(moderationIndexFile).read():
|
||||
with open(moderationIndexFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
with open(moderationIndexFile, "w+") as f:
|
||||
for line in lines:
|
||||
if line.strip("\n") != postId:
|
||||
|
@ -485,7 +489,7 @@ def noOfActiveAccountsMonthly(baseDir: str,months: int) -> bool:
|
|||
lastUsedFilename=baseDir+'/accounts/'+account+'/.lastUsed'
|
||||
if os.path.isfile(lastUsedFilename):
|
||||
with open(lastUsedFilename, 'r') as lastUsedFile:
|
||||
lastUsed = lastUsedFile.read()
|
||||
lastUsed=lastUsedFile.read()
|
||||
if lastUsed.isdigit():
|
||||
timeDiff=(currTime-int(lastUsed))
|
||||
if timeDiff<monthSeconds:
|
||||
|
@ -525,8 +529,8 @@ def copytree(src: str, dst: str, symlinks=False, ignore=None):
|
|||
"""Copy a directory
|
||||
"""
|
||||
for item in os.listdir(src):
|
||||
s = os.path.join(src, item)
|
||||
d = os.path.join(dst, item)
|
||||
s=os.path.join(src, item)
|
||||
d=os.path.join(dst, item)
|
||||
if os.path.isdir(s):
|
||||
shutil.copytree(s, d, symlinks, ignore)
|
||||
else:
|
||||
|
@ -609,7 +613,7 @@ def updateRecentPostsCache(recentPostsCache: {},maxRecentPosts: int, \
|
|||
def fileLastModified(filename: str) -> str:
|
||||
"""Returns the date when a file was last modified
|
||||
"""
|
||||
t = os.path.getmtime(filename)
|
||||
t=os.path.getmtime(filename)
|
||||
modifiedTime=datetime.datetime.fromtimestamp(t)
|
||||
return modifiedTime.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
|
@ -624,7 +628,7 @@ def daysInMonth(year: int,monthNumber: int) -> int:
|
|||
def mergeDicts(dict1: {}, dict2: {}) -> {}:
|
||||
"""Merges two dictionaries
|
||||
"""
|
||||
res = {**dict1, **dict2}
|
||||
res={**dict1,**dict2}
|
||||
return res
|
||||
|
||||
def isBlogPost(postJsonObject: {}) -> bool:
|
||||
|
|
50
webfinger.py
50
webfinger.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "webfinger.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="webfinger.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import base64
|
||||
try:
|
||||
|
@ -26,21 +26,21 @@ from utils import saveJson
|
|||
|
||||
def parseHandle(handle: str) -> (str,str):
|
||||
if '.' not in handle:
|
||||
return None, None
|
||||
return None,None
|
||||
if '/@' in handle:
|
||||
domain, nickname = \
|
||||
domain,nickname= \
|
||||
handle.replace('https://','').replace('http://','').replace('dat://','').replace('i2p://','').split('/@')
|
||||
else:
|
||||
if '/users/' in handle:
|
||||
domain, nickname = \
|
||||
domain,nickname= \
|
||||
handle.replace('https://','').replace('http://','').replace('i2p://','').replace('dat://','').split('/users/')
|
||||
else:
|
||||
if '@' in handle:
|
||||
nickname, domain = handle.split('@')
|
||||
nickname,domain=handle.split('@')
|
||||
else:
|
||||
return None, None
|
||||
return None,None
|
||||
|
||||
return nickname, domain
|
||||
return nickname,domain
|
||||
|
||||
def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \
|
||||
fromDomain: str,projectVersion: str) -> {}:
|
||||
|
@ -48,7 +48,7 @@ def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \
|
|||
print('WARN: No session specified for webfingerHandle')
|
||||
return None
|
||||
|
||||
nickname, domain = parseHandle(handle)
|
||||
nickname,domain=parseHandle(handle)
|
||||
if not nickname:
|
||||
return None
|
||||
wfDomain=domain
|
||||
|
@ -61,11 +61,15 @@ def webfingerHandle(session,handle: str,httpPrefix: str,cachedWebfingers: {}, \
|
|||
wf=getWebfingerFromCache(nickname+'@'+wfDomain,cachedWebfingers)
|
||||
if wf:
|
||||
return wf
|
||||
url = '{}://{}/.well-known/webfinger'.format(httpPrefix,domain)
|
||||
par = {'resource': 'acct:{}'.format(nickname+'@'+wfDomain)}
|
||||
hdr = {'Accept': 'application/jrd+json'}
|
||||
url='{}://{}/.well-known/webfinger'.format(httpPrefix,domain)
|
||||
par={
|
||||
'resource': 'acct:{}'.format(nickname+'@'+wfDomain)
|
||||
}
|
||||
hdr={
|
||||
'Accept': 'application/jrd+json'
|
||||
}
|
||||
try:
|
||||
result = getJson(session,url,hdr,par,projectVersion,httpPrefix,fromDomain)
|
||||
result=getJson(session,url,hdr,par,projectVersion,httpPrefix,fromDomain)
|
||||
except Exception as e:
|
||||
print("Unable to webfinger " + url)
|
||||
print('nickname: '+str(nickname))
|
||||
|
@ -81,9 +85,9 @@ def generateMagicKey(publicKeyPem) -> str:
|
|||
"""See magic_key method in
|
||||
https://github.com/tootsuite/mastodon/blob/707ddf7808f90e3ab042d7642d368c2ce8e95e6f/app/models/account.rb
|
||||
"""
|
||||
privkey = RSA.importKey(publicKeyPem)
|
||||
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")
|
||||
privkey=RSA.importKey(publicKeyPem)
|
||||
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")
|
||||
return f"data:application/magic-public-key,RSA.{mod}.{pubexp}"
|
||||
|
||||
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
|
||||
profilePageHref=httpPrefix+'://'+domain+'/about/more?instance_actor=true'
|
||||
|
||||
account = {
|
||||
account={
|
||||
"aliases": [
|
||||
httpPrefix+"://"+domain+"/@"+personName,
|
||||
personId
|
||||
|
@ -160,7 +164,7 @@ def createWebfingerEndpoint(nickname: str,domain: str,port: int, \
|
|||
def webfingerNodeInfo(httpPrefix: str,domainFull: str) -> {}:
|
||||
""" /.well-known/nodeinfo endpoint
|
||||
"""
|
||||
nodeinfo = {
|
||||
nodeinfo={
|
||||
'links': [
|
||||
{
|
||||
'href': httpPrefix+'://'+domainFull+'/nodeinfo/2.0',
|
||||
|
|
252
webinterface.py
252
webinterface.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "webinterface.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="webinterface.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
import time
|
||||
|
@ -81,17 +81,25 @@ def updateAvatarImageCache(session,baseDir: str,httpPrefix: str, \
|
|||
actorStr=actor.replace('/','-')
|
||||
avatarImagePath=baseDir+'/cache/avatars/'+actorStr
|
||||
if avatarUrl.endswith('.png') or '.png?' in avatarUrl:
|
||||
sessionHeaders = {'Accept': 'image/png'}
|
||||
sessionHeaders={
|
||||
'Accept': 'image/png'
|
||||
}
|
||||
avatarImageFilename=avatarImagePath+'.png'
|
||||
elif avatarUrl.endswith('.jpg') or avatarUrl.endswith('.jpeg') or \
|
||||
'.jpg?' in avatarUrl or '.jpeg?' in avatarUrl:
|
||||
sessionHeaders = {'Accept': 'image/jpeg'}
|
||||
sessionHeaders={
|
||||
'Accept': 'image/jpeg'
|
||||
}
|
||||
avatarImageFilename=avatarImagePath+'.jpg'
|
||||
elif avatarUrl.endswith('.gif') or '.gif?' in avatarUrl:
|
||||
sessionHeaders = {'Accept': 'image/gif'}
|
||||
sessionHeaders={
|
||||
'Accept': 'image/gif'
|
||||
}
|
||||
avatarImageFilename=avatarImagePath+'.gif'
|
||||
elif avatarUrl.endswith('.webp') or '.webp?' in avatarUrl:
|
||||
sessionHeaders = {'Accept': 'image/webp'}
|
||||
sessionHeaders={
|
||||
'Accept': 'image/webp'
|
||||
}
|
||||
avatarImageFilename=avatarImagePath+'.webp'
|
||||
else:
|
||||
return None
|
||||
|
@ -114,14 +122,14 @@ def updateAvatarImageCache(session,baseDir: str,httpPrefix: str, \
|
|||
print('Failed to download avatar image: '+str(avatarUrl))
|
||||
print(e)
|
||||
if '/channel/' not in actor:
|
||||
sessionHeaders = {
|
||||
sessionHeaders={
|
||||
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
else:
|
||||
sessionHeaders = {
|
||||
sessionHeaders={
|
||||
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
personJson = \
|
||||
personJson= \
|
||||
getJson(session,actor,sessionHeaders,None,__version__, \
|
||||
httpPrefix,None)
|
||||
if personJson:
|
||||
|
@ -147,7 +155,7 @@ def updateAvatarImageCache(session,baseDir: str,httpPrefix: str, \
|
|||
def getPersonAvatarUrl(baseDir: str,personUrl: str,personCache: {}) -> str:
|
||||
"""Returns the avatar url for the person
|
||||
"""
|
||||
personJson = getPersonFromCache(baseDir,personUrl,personCache)
|
||||
personJson=getPersonFromCache(baseDir,personUrl,personCache)
|
||||
if not personJson:
|
||||
return None
|
||||
# get from locally stored image
|
||||
|
@ -204,10 +212,10 @@ def htmlSearchEmoji(translate: {},baseDir: str,httpPrefix: str, \
|
|||
results={}
|
||||
for emojiName,filename in emojiJson.items():
|
||||
if searchStr in emojiName:
|
||||
results[emojiName] = filename+'.png'
|
||||
results[emojiName]=filename+'.png'
|
||||
for emojiName,filename in emojiJson.items():
|
||||
if emojiName in searchStr:
|
||||
results[emojiName] = filename+'.png'
|
||||
results[emojiName]=filename+'.png'
|
||||
headingShown=False
|
||||
emojiForm+='<center>'
|
||||
for emojiName,filename in results.items():
|
||||
|
@ -382,7 +390,7 @@ def htmlModerationInfo(translate: {},baseDir: str,httpPrefix: str) -> str:
|
|||
suspendedFilename=baseDir+'/accounts/suspended.txt'
|
||||
if os.path.isfile(suspendedFilename):
|
||||
with open(suspendedFilename, "r") as f:
|
||||
suspendedStr = f.read()
|
||||
suspendedStr=f.read()
|
||||
infoForm+='<div class="container">'
|
||||
infoForm+=' <br><b>'+translate['Suspended accounts']+'</b>'
|
||||
infoForm+=' <br>'+translate['These are currently suspended']
|
||||
|
@ -395,7 +403,7 @@ def htmlModerationInfo(translate: {},baseDir: str,httpPrefix: str) -> str:
|
|||
blockingFilename=baseDir+'/accounts/blocking.txt'
|
||||
if os.path.isfile(blockingFilename):
|
||||
with open(blockingFilename, "r") as f:
|
||||
blockedStr = f.read()
|
||||
blockedStr=f.read()
|
||||
infoForm+='<div class="container">'
|
||||
infoForm+= \
|
||||
' <br><b>'+translate['Blocked accounts and hashtags']+'</b>'
|
||||
|
@ -437,14 +445,14 @@ def htmlHashtagSearch(nickname: str,domain: str,port: int, \
|
|||
|
||||
# read the index
|
||||
with open(hashtagIndexFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
lines=f.readlines()
|
||||
|
||||
# read the css
|
||||
cssFilename=baseDir+'/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
hashtagSearchCSS = cssFile.read()
|
||||
hashtagSearchCSS=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
hashtagSearchCSS= \
|
||||
hashtagSearchCSS.replace('https://',httpPrefix+'://')
|
||||
|
@ -546,7 +554,7 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
|
|||
continue
|
||||
if f.startswith('inbox@'):
|
||||
continue
|
||||
actorFilename = os.path.join(subdir, f)
|
||||
actorFilename=os.path.join(subdir, f)
|
||||
actorJson=loadJson(actorFilename)
|
||||
if actorJson:
|
||||
if actorJson.get('id') and \
|
||||
|
@ -556,17 +564,19 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
|
|||
actor=actorJson['id']
|
||||
for skillName,skillLevel in actorJson['skills'].items():
|
||||
skillName=skillName.lower()
|
||||
if skillName in skillsearch or skillsearch in skillName:
|
||||
skillLevelStr=str(skillLevel)
|
||||
if skillLevel<100:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
if skillLevel<10:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
indexStr= \
|
||||
skillLevelStr+';'+actor+';'+actorJson['name']+ \
|
||||
';'+actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
if not (skillName in skillsearch or \
|
||||
skillsearch in skillName):
|
||||
continue
|
||||
skillLevelStr=str(skillLevel)
|
||||
if skillLevel<100:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
if skillLevel<10:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
indexStr= \
|
||||
skillLevelStr+';'+actor+';'+actorJson['name']+ \
|
||||
';'+actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
if not instanceOnly:
|
||||
# search actor cache
|
||||
for subdir, dirs, files in os.walk(baseDir+'/cache/actors/'):
|
||||
|
@ -577,7 +587,7 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
|
|||
continue
|
||||
if f.startswith('inbox@'):
|
||||
continue
|
||||
actorFilename = os.path.join(subdir, f)
|
||||
actorFilename=os.path.join(subdir, f)
|
||||
cachedActorJson=loadJson(actorFilename)
|
||||
if cachedActorJson:
|
||||
if cachedActorJson.get('actor'):
|
||||
|
@ -589,17 +599,19 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
|
|||
actor=actorJson['id']
|
||||
for skillName,skillLevel in actorJson['skills'].items():
|
||||
skillName=skillName.lower()
|
||||
if skillName in skillsearch or skillsearch in skillName:
|
||||
skillLevelStr=str(skillLevel)
|
||||
if skillLevel<100:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
if skillLevel<10:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
indexStr= \
|
||||
skillLevelStr+';'+actor+';'+actorJson['name']+ \
|
||||
';'+actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
if not (skillName in skillsearch or \
|
||||
skillsearch in skillName):
|
||||
continue
|
||||
skillLevelStr=str(skillLevel)
|
||||
if skillLevel<100:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
if skillLevel<10:
|
||||
skillLevelStr='0'+skillLevelStr
|
||||
indexStr= \
|
||||
skillLevelStr+';'+actor+';'+actorJson['name']+ \
|
||||
';'+actorJson['icon']['url']
|
||||
if indexStr not in results:
|
||||
results.append(indexStr)
|
||||
|
||||
results.sort(reverse=True)
|
||||
|
||||
|
@ -607,7 +619,7 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
|
|||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
skillSearchCSS = cssFile.read()
|
||||
skillSearchCSS=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
skillSearchCSS=skillSearchCSS.replace('https://',httpPrefix+'://')
|
||||
skillSearchForm=htmlHeader(cssFilename,skillSearchCSS)
|
||||
|
@ -622,17 +634,20 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
|
|||
ctr=0
|
||||
for skillMatch in results:
|
||||
skillMatchFields=skillMatch.split(';')
|
||||
if len(skillMatchFields)==4:
|
||||
actor=skillMatchFields[1]
|
||||
actorName=skillMatchFields[2]
|
||||
avatarUrl=skillMatchFields[3]
|
||||
skillSearchForm+='<div class="search-result""><a href="'+actor+'/skills">'
|
||||
skillSearchForm+= \
|
||||
'<img loading="lazy" src="'+avatarUrl+ \
|
||||
'"/><span class="search-result-text">'+actorName+'</span></a></div>'
|
||||
ctr+=1
|
||||
if ctr>=postsPerPage:
|
||||
break
|
||||
if len(skillMatchFields)!=4:
|
||||
continue
|
||||
actor=skillMatchFields[1]
|
||||
actorName=skillMatchFields[2]
|
||||
avatarUrl=skillMatchFields[3]
|
||||
skillSearchForm+= \
|
||||
'<div class="search-result""><a href="'+actor+'/skills">'
|
||||
skillSearchForm+= \
|
||||
'<img loading="lazy" src="'+avatarUrl+ \
|
||||
'"/><span class="search-result-text">'+actorName+ \
|
||||
'</span></a></div>'
|
||||
ctr+=1
|
||||
if ctr>=postsPerPage:
|
||||
break
|
||||
skillSearchForm+='</center>'
|
||||
skillSearchForm+=htmlFooter()
|
||||
return skillSearchForm
|
||||
|
@ -640,7 +655,8 @@ def htmlSkillsSearch(translate: {},baseDir: str, \
|
|||
def scheduledPostsExist(baseDir: str,nickname: str,domain: str) -> bool:
|
||||
"""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):
|
||||
return False
|
||||
if '#users#' in open(scheduleIndexFilename).read():
|
||||
|
@ -653,7 +669,8 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
|
|||
"""
|
||||
imageFormats='.png, .jpg, .jpeg, .gif, .webp'
|
||||
pathOriginal=path
|
||||
path=path.replace('/inbox','').replace('/outbox','').replace('/shares','')
|
||||
path= \
|
||||
path.replace('/inbox','').replace('/outbox','').replace('/shares','')
|
||||
nickname=getNicknameFromActor(path)
|
||||
if not nickname:
|
||||
return ''
|
||||
|
@ -730,13 +747,15 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
|
|||
switchStr=switchfile.read()
|
||||
|
||||
blockedStr=''
|
||||
blockedFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
|
||||
blockedFilename= \
|
||||
baseDir+'/accounts/'+nickname+'@'+domain+'/blocking.txt'
|
||||
if os.path.isfile(blockedFilename):
|
||||
with open(blockedFilename, 'r') as blockedfile:
|
||||
blockedStr=blockedfile.read()
|
||||
|
||||
allowedInstancesStr=''
|
||||
allowedInstancesFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/allowedinstances.txt'
|
||||
allowedInstancesFilename= \
|
||||
baseDir+'/accounts/'+nickname+'@'+domain+'/allowedinstances.txt'
|
||||
if os.path.isfile(allowedInstancesFilename):
|
||||
with open(allowedInstancesFilename, 'r') as allowedInstancesFile:
|
||||
allowedInstancesStr=allowedInstancesFile.read()
|
||||
|
@ -766,7 +785,7 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
|
|||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
editProfileCSS = cssFile.read()
|
||||
editProfileCSS=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
editProfileCSS=editProfileCSS.replace('https://',httpPrefix+'://')
|
||||
|
||||
|
@ -804,7 +823,7 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
|
|||
moderatorsFile=baseDir+'/accounts/moderators.txt'
|
||||
if os.path.isfile(moderatorsFile):
|
||||
with open(moderatorsFile, "r") as f:
|
||||
moderators = f.read()
|
||||
moderators=f.read()
|
||||
moderatorsStr='<div class="container">'
|
||||
moderatorsStr+=' <b>'+translate['Moderators']+'</b><br>'
|
||||
moderatorsStr+=' '+translate['A list of moderator nicknames. One per line.']
|
||||
|
@ -928,7 +947,8 @@ def htmlEditProfile(translate: {},baseDir: str,path: str, \
|
|||
editProfileForm+=htmlFooter()
|
||||
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
|
||||
"""
|
||||
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'):
|
||||
# custom login message
|
||||
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'
|
||||
if os.path.isfile(baseDir+'/login.css'):
|
||||
cssFilename=baseDir+'/login.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
loginCSS = cssFile.read()
|
||||
loginCSS=cssFile.read()
|
||||
|
||||
# show the register button
|
||||
registerButtonStr=''
|
||||
|
@ -1065,7 +1085,7 @@ def htmlLogin(translate: {},baseDir: str,autocomplete=True) -> str:
|
|||
def htmlTermsOfService(baseDir: str,httpPrefix: str,domainFull: str) -> str:
|
||||
"""Show the terms of service screen
|
||||
"""
|
||||
adminNickname = getConfigParam(baseDir,'admin')
|
||||
adminNickname=getConfigParam(baseDir,'admin')
|
||||
if not os.path.isfile(baseDir+'/accounts/tos.txt'):
|
||||
copyfile(baseDir+'/default_tos.txt',baseDir+'/accounts/tos.txt')
|
||||
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.'
|
||||
if os.path.isfile(baseDir+'/accounts/tos.txt'):
|
||||
with open(baseDir+'/accounts/tos.txt', 'r') as file:
|
||||
TOSText = file.read()
|
||||
TOSText=file.read()
|
||||
|
||||
TOSForm=''
|
||||
cssFilename=baseDir+'/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
termsCSS = cssFile.read()
|
||||
termsCSS=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
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:
|
||||
"""Show the about screen
|
||||
"""
|
||||
adminNickname = getConfigParam(baseDir,'admin')
|
||||
adminNickname=getConfigParam(baseDir,'admin')
|
||||
if not os.path.isfile(baseDir+'/accounts/about.txt'):
|
||||
copyfile(baseDir+'/default_about.txt',baseDir+'/accounts/about.txt')
|
||||
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.'
|
||||
if os.path.isfile(baseDir+'/accounts/about.txt'):
|
||||
with open(baseDir+'/accounts/about.txt', 'r') as file:
|
||||
aboutText = file.read()
|
||||
aboutText=file.read()
|
||||
|
||||
aboutForm=''
|
||||
cssFilename=baseDir+'/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
termsCSS = cssFile.read()
|
||||
termsCSS=cssFile.read()
|
||||
if httpPrefix!='http':
|
||||
termsCSS=termsCSS.replace('https://',httpPrefix+'://')
|
||||
|
||||
|
@ -1231,13 +1251,13 @@ def htmlNewPost(mediaInstance: bool,translate: {}, \
|
|||
|
||||
if os.path.isfile(baseDir+'/accounts/newpost.txt'):
|
||||
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'
|
||||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
newPostCSS = cssFile.read()
|
||||
newPostCSS=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
newPostCSS=newPostCSS.replace('https://',httpPrefix+'://')
|
||||
|
||||
|
@ -2017,7 +2037,7 @@ def htmlProfile(defaultTimeline: str, \
|
|||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = \
|
||||
profileStyle= \
|
||||
cssFile.read().replace('image.png', \
|
||||
profileJson['image']['url'])
|
||||
|
||||
|
@ -2085,7 +2105,7 @@ def individualFollowAsHtml(translate: {}, \
|
|||
if not avatarUrl:
|
||||
avatarUrl=followUrl+'/avatar.png'
|
||||
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, \
|
||||
projectVersion,httpPrefix,nickname,domain,'outbox')
|
||||
if avatarUrl2:
|
||||
|
@ -2131,8 +2151,8 @@ def cursorToEndOfMessageScript() -> str:
|
|||
This avoids the cursor being in the wrong position when replying
|
||||
"""
|
||||
script='function focusOnMessage() {\n'
|
||||
script+=" var replyTextArea = document.getElementById('message');\n"
|
||||
script+=' val = replyTextArea.value;\n'
|
||||
script+=" var replyTextArea=document.getElementById('message');\n"
|
||||
script+=' val=replyTextArea.value;\n'
|
||||
script+=' if ((val.length>0) && (val.charAt(val.length-1) != " ")) {\n'
|
||||
script+=' val += " ";\n'
|
||||
script+=' }\n'
|
||||
|
@ -2140,8 +2160,8 @@ def cursorToEndOfMessageScript() -> str:
|
|||
script+=' replyTextArea.value="";\n'
|
||||
script+=' replyTextArea.value=val;\n'
|
||||
script+='}\n'
|
||||
script+="var replyTextArea = document.getElementById('message')\n"
|
||||
script+='replyTextArea.onFocus = function() {\n'
|
||||
script+="var replyTextArea=document.getElementById('message')\n"
|
||||
script+='replyTextArea.onFocus=function() {\n'
|
||||
script+=' focusOnMessage();'
|
||||
script+='}\n'
|
||||
return script
|
||||
|
@ -2150,11 +2170,11 @@ def contentWarningScript() -> str:
|
|||
"""Returns a script used for content warnings
|
||||
"""
|
||||
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+=' x.style.display = "block";\n'
|
||||
script+=' x.style.display="block";\n'
|
||||
script+=' } else {\n'
|
||||
script+=' x.style.display = "none";\n'
|
||||
script+=' x.style.display="none";\n'
|
||||
script+=' }\n'
|
||||
script+='}\n'
|
||||
return script
|
||||
|
@ -2164,8 +2184,8 @@ def contentWarningScriptOpen() -> str:
|
|||
The warning is open by default. This is used on blog replies.
|
||||
"""
|
||||
script='function showContentWarning(postID) {\n'
|
||||
script+=' var x = document.getElementById(postID);\n'
|
||||
script+=' x.style.display = "block";\n'
|
||||
script+=' var x=document.getElementById(postID);\n'
|
||||
script+=' x.style.display="block";\n'
|
||||
script+='}\n'
|
||||
return script
|
||||
|
||||
|
@ -2531,7 +2551,7 @@ def loadIndividualPostAsHtmlFromCache(baseDir: str,nickname: str,domain: str, \
|
|||
while tries<3:
|
||||
try:
|
||||
with open(cachedPostFilename, 'r') as file:
|
||||
postHtml = file.read()
|
||||
postHtml=file.read()
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
@ -2847,7 +2867,7 @@ def individualPostAsHtml(recentPostsCache: {},maxRecentPosts: int, \
|
|||
avatarUrl=postActor+'/avatar.png'
|
||||
|
||||
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, \
|
||||
projectVersion,httpPrefix,nickname,domain,'outbox')
|
||||
if avatarUrl2:
|
||||
|
@ -3267,15 +3287,15 @@ def individualPostAsHtml(recentPostsCache: {},maxRecentPosts: int, \
|
|||
publishedStr=postJsonObject['object']['published']
|
||||
if '.' not in publishedStr:
|
||||
if '+' not in publishedStr:
|
||||
datetimeObject = \
|
||||
datetimeObject= \
|
||||
datetime.strptime(publishedStr,"%Y-%m-%dT%H:%M:%SZ")
|
||||
else:
|
||||
datetimeObject = \
|
||||
datetimeObject= \
|
||||
datetime.strptime(publishedStr.split('+')[0]+'Z', \
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
else:
|
||||
publishedStr=publishedStr.replace('T',' ').split('.')[0]
|
||||
datetimeObject = parse(publishedStr)
|
||||
datetimeObject=parse(publishedStr)
|
||||
publishedStr=datetimeObject.strftime("%a %b %d, %H:%M")
|
||||
|
||||
publishedLink=messageId
|
||||
|
@ -3459,7 +3479,7 @@ def htmlTimeline(defaultTimeline: str, \
|
|||
bannerFile='banner.webp'
|
||||
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = \
|
||||
profileStyle= \
|
||||
cssFile.read().replace('banner.png', \
|
||||
'/users/'+nickname+'/'+bannerFile)
|
||||
if httpPrefix!='https':
|
||||
|
@ -4069,7 +4089,7 @@ def htmlRemoveSharedItem(translate: {},baseDir: str,actor: str,shareName: str) -
|
|||
if os.path.isfile(baseDir+'/follow.css'):
|
||||
cssFilename=baseDir+'/follow.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
sharesStr=htmlHeader(cssFilename,profileStyle)
|
||||
sharesStr+='<div class="follow">'
|
||||
sharesStr+=' <div class="followAvatar">'
|
||||
|
@ -4127,7 +4147,7 @@ def htmlDeletePost(recentPostsCache: {},maxRecentPosts: int, \
|
|||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
profileStyle=profileStyle.replace('https://',httpPrefix+'://')
|
||||
deletePostStr=htmlHeader(cssFilename,profileStyle)
|
||||
|
@ -4191,7 +4211,7 @@ def htmlCalendarDeleteConfirm(translate: {},baseDir: str, \
|
|||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
if httpPrefix!='https':
|
||||
profileStyle=profileStyle.replace('https://',httpPrefix+'://')
|
||||
deletePostStr=htmlHeader(cssFilename,profileStyle)
|
||||
|
@ -4235,7 +4255,7 @@ def htmlFollowConfirm(translate: {},baseDir: str, \
|
|||
if os.path.isfile(baseDir+'/follow.css'):
|
||||
cssFilename=baseDir+'/follow.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
followStr=htmlHeader(cssFilename,profileStyle)
|
||||
followStr+='<div class="follow">'
|
||||
followStr+=' <div class="followAvatar">'
|
||||
|
@ -4277,7 +4297,7 @@ def htmlUnfollowConfirm(translate: {},baseDir: str, \
|
|||
if os.path.isfile(baseDir+'/follow.css'):
|
||||
cssFilename=baseDir+'/follow.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
followStr=htmlHeader(cssFilename,profileStyle)
|
||||
followStr+='<div class="follow">'
|
||||
followStr+=' <div class="followAvatar">'
|
||||
|
@ -4352,7 +4372,7 @@ def htmlPersonOptions(translate: {},baseDir: str, \
|
|||
if os.path.isfile(baseDir+'/follow.css'):
|
||||
cssFilename=baseDir+'/follow.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
|
||||
# To snooze, or not to snooze? That is the question
|
||||
snoozeButtonStr='Snooze'
|
||||
|
@ -4438,7 +4458,7 @@ def htmlPersonOptions(translate: {},baseDir: str, \
|
|||
# copyfile(baseDir+'/img/block-background.png',baseDir+'/accounts/block-background.png')
|
||||
#
|
||||
# with open(baseDir+'/epicyon-follow.css', 'r') as cssFile:
|
||||
# profileStyle = cssFile.read()
|
||||
# profileStyle=cssFile.read()
|
||||
# blockStr=htmlHeader(cssFilename,profileStyle)
|
||||
# blockStr+='<div class="block">'
|
||||
# blockStr+=' <div class="blockAvatar">'
|
||||
|
@ -4474,7 +4494,7 @@ def htmlUnblockConfirm(translate: {},baseDir: str, \
|
|||
if os.path.isfile(baseDir+'/follow.css'):
|
||||
cssFilename=baseDir+'/follow.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
blockStr=htmlHeader(cssFilename,profileStyle)
|
||||
blockStr+='<div class="block">'
|
||||
blockStr+=' <div class="blockAvatar">'
|
||||
|
@ -4522,7 +4542,7 @@ def htmlSearchEmojiTextEntry(translate: {}, \
|
|||
if os.path.isfile(baseDir+'/follow.css'):
|
||||
cssFilename=baseDir+'/follow.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
emojiStr=htmlHeader(cssFilename,profileStyle)
|
||||
emojiStr+='<div class="follow">'
|
||||
emojiStr+=' <div class="followAvatar">'
|
||||
|
@ -4567,7 +4587,7 @@ def htmlCalendarDay(translate: {}, \
|
|||
if os.path.isfile(baseDir+'/calendar.css'):
|
||||
cssFilename=baseDir+'/calendar.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
calendarStyle = cssFile.read()
|
||||
calendarStyle=cssFile.read()
|
||||
|
||||
calendarStr=htmlHeader(cssFilename,calendarStyle)
|
||||
calendarStr+='<main><table class="calendar">\n'
|
||||
|
@ -4740,7 +4760,7 @@ def htmlCalendar(translate: {}, \
|
|||
if os.path.isfile(baseDir+'/calendar.css'):
|
||||
cssFilename=baseDir+'/calendar.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
calendarStyle = cssFile.read()
|
||||
calendarStyle=cssFile.read()
|
||||
|
||||
calendarStr=htmlHeader(cssFilename,calendarStyle)
|
||||
calendarStr+='<main><table class="calendar">\n'
|
||||
|
@ -4890,7 +4910,7 @@ def htmlSearch(translate: {}, \
|
|||
if os.path.isfile(baseDir+'/follow.css'):
|
||||
cssFilename=baseDir+'/follow.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
profileStyle = cssFile.read()
|
||||
profileStyle=cssFile.read()
|
||||
followStr=htmlHeader(cssFilename,profileStyle)
|
||||
followStr+='<div class="follow">'
|
||||
followStr+=' <div class="followAvatar">'
|
||||
|
@ -4964,7 +4984,7 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
|
|||
if os.path.isfile(baseDir+'/epicyon.css'):
|
||||
cssFilename=baseDir+'/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
wf = \
|
||||
wf= \
|
||||
webfingerHandle(session, \
|
||||
searchNickname+'@'+searchDomainFull, \
|
||||
httpPrefix,wfRequest, \
|
||||
|
@ -4977,21 +4997,23 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
|
|||
if wf.get('errors'):
|
||||
personUrl=httpPrefix+'://'+searchDomainFull+'/users/'+searchNickname
|
||||
|
||||
asHeader = {
|
||||
asHeader={
|
||||
'Accept': 'application/activity+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||
}
|
||||
if not personUrl:
|
||||
personUrl = getUserUrl(wf)
|
||||
personUrl=getUserUrl(wf)
|
||||
if not personUrl:
|
||||
# try single user instance
|
||||
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
|
||||
personUrl=httpPrefix+'://'+searchDomainFull
|
||||
profileJson = getJson(session,personUrl,asHeader,None,projectVersion,httpPrefix,domain)
|
||||
if not profileJson:
|
||||
asHeader = {
|
||||
asHeader={
|
||||
'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)
|
||||
if not profileJson:
|
||||
if debug:
|
||||
|
@ -5021,7 +5043,7 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
|
|||
if profileJson['image'].get('url'):
|
||||
profileBackgroundImage=profileJson['image']['url']
|
||||
|
||||
profileStyle = cssFile.read().replace('image.png',profileBackgroundImage)
|
||||
profileStyle=cssFile.read().replace('image.png',profileBackgroundImage)
|
||||
if httpPrefix!='https':
|
||||
profileStyle=profileStyle.replace('https://',httpPrefix+'://')
|
||||
# url to return to
|
||||
|
@ -5074,8 +5096,8 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
|
|||
profileStr+='<script>'+contentWarningScript()+'</script>'
|
||||
|
||||
iconsDir=getIconsDir(baseDir)
|
||||
result = []
|
||||
i = 0
|
||||
result=[]
|
||||
i=0
|
||||
for item in parseUserFeed(session,outboxUrl,asHeader, \
|
||||
projectVersion,httpPrefix,domain):
|
||||
if not item.get('type'):
|
||||
|
|
14
xmpp.py
14
xmpp.py
|
@ -1,10 +1,10 @@
|
|||
__filename__ = "xmpp.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__filename__="xmpp.py"
|
||||
__author__="Bob Mottram"
|
||||
__license__="AGPL3+"
|
||||
__version__="1.1.0"
|
||||
__maintainer__="Bob Mottram"
|
||||
__email__="bob@freedombone.net"
|
||||
__status__="Production"
|
||||
|
||||
import json
|
||||
|
||||
|
|
Loading…
Reference in New Issue