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

View File

@ -1,10 +1,10 @@
__filename__ = "acceptreject.py"
__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,

View File

@ -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
View File

@ -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)

View File

@ -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:

View File

@ -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
View File

@ -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+'://')

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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
View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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('&apos;',"'")
content=item['object']['content'].replace('&apos;',"'")
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, \

View File

@ -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

View File

@ -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:

View File

@ -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'):

View File

@ -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

View File

@ -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:

View File

@ -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
View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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:

View File

@ -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',

View File

@ -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
View File

@ -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