mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
commit
0aed47f090
|
|
@ -15,7 +15,7 @@ from utils import domainPermitted
|
||||||
from utils import followPerson
|
from utils import followPerson
|
||||||
|
|
||||||
|
|
||||||
def createAcceptReject(baseDir: str, federationList: [],
|
def _createAcceptReject(baseDir: str, federationList: [],
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
toUrl: str, ccUrl: str, httpPrefix: str,
|
toUrl: str, ccUrl: str, httpPrefix: str,
|
||||||
objectJson: {}, acceptType: str) -> {}:
|
objectJson: {}, acceptType: str) -> {}:
|
||||||
|
|
@ -51,7 +51,7 @@ def createAccept(baseDir: str, federationList: [],
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
toUrl: str, ccUrl: str, httpPrefix: str,
|
toUrl: str, ccUrl: str, httpPrefix: str,
|
||||||
objectJson: {}) -> {}:
|
objectJson: {}) -> {}:
|
||||||
return createAcceptReject(baseDir, federationList,
|
return _createAcceptReject(baseDir, federationList,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
toUrl, ccUrl, httpPrefix,
|
toUrl, ccUrl, httpPrefix,
|
||||||
objectJson, 'Accept')
|
objectJson, 'Accept')
|
||||||
|
|
@ -61,13 +61,13 @@ def createReject(baseDir: str, federationList: [],
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
toUrl: str, ccUrl: str, httpPrefix: str,
|
toUrl: str, ccUrl: str, httpPrefix: str,
|
||||||
objectJson: {}) -> {}:
|
objectJson: {}) -> {}:
|
||||||
return createAcceptReject(baseDir, federationList,
|
return _createAcceptReject(baseDir, federationList,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
toUrl, ccUrl,
|
toUrl, ccUrl,
|
||||||
httpPrefix, objectJson, 'Reject')
|
httpPrefix, objectJson, 'Reject')
|
||||||
|
|
||||||
|
|
||||||
def acceptFollow(baseDir: str, domain: str, messageJson: {},
|
def _acceptFollow(baseDir: str, domain: str, messageJson: {},
|
||||||
federationList: [], debug: bool) -> None:
|
federationList: [], debug: bool) -> None:
|
||||||
"""Receiving a follow Accept activity
|
"""Receiving a follow Accept activity
|
||||||
"""
|
"""
|
||||||
|
|
@ -204,7 +204,7 @@ def receiveAcceptReject(session, baseDir: str,
|
||||||
' does not contain a nickname. ' +
|
' does not contain a nickname. ' +
|
||||||
'Assuming single user instance.')
|
'Assuming single user instance.')
|
||||||
# receive follow accept
|
# receive follow accept
|
||||||
acceptFollow(baseDir, domain, messageJson, federationList, debug)
|
_acceptFollow(baseDir, domain, messageJson, federationList, debug)
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: Uh, ' + messageJson['type'] + ', I guess')
|
print('DEBUG: Uh, ' + messageJson['type'] + ', I guess')
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
129
announce.py
129
announce.py
|
|
@ -183,135 +183,6 @@ def announcePublic(session, baseDir: str, federationList: [],
|
||||||
debug, projectVersion)
|
debug, projectVersion)
|
||||||
|
|
||||||
|
|
||||||
def repeatPost(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
|
||||||
announceNickname: str, announceDomain: str,
|
|
||||||
announcePort: int, announceHttpsPrefix: str,
|
|
||||||
announceStatusNumber: int, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool, projectVersion: str) -> {}:
|
|
||||||
"""Repeats a given status post
|
|
||||||
"""
|
|
||||||
announcedDomain = getFullDomain(announceDomain, announcePort)
|
|
||||||
|
|
||||||
objectUrl = announceHttpsPrefix + '://' + announcedDomain + '/users/' + \
|
|
||||||
announceNickname + '/statuses/' + str(announceStatusNumber)
|
|
||||||
|
|
||||||
return announcePublic(session, baseDir, federationList,
|
|
||||||
nickname, domain, port, httpPrefix,
|
|
||||||
objectUrl, clientToServer,
|
|
||||||
sendThreads, postLog,
|
|
||||||
personCache, cachedWebfingers,
|
|
||||||
debug, projectVersion)
|
|
||||||
|
|
||||||
|
|
||||||
def undoAnnounce(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int,
|
|
||||||
toUrl: str, ccUrl: str, httpPrefix: str,
|
|
||||||
objectUrl: str, saveToFile: bool,
|
|
||||||
clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool) -> {}:
|
|
||||||
"""Undoes an announce message
|
|
||||||
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
|
|
||||||
and ccUrl might be a specific person whose post was repeated and the
|
|
||||||
objectUrl is typically the url of the message which was repeated,
|
|
||||||
corresponding to url or atomUri in createPostBase
|
|
||||||
"""
|
|
||||||
if not urlPermitted(objectUrl, federationList):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if ':' in domain:
|
|
||||||
domain = domain.split(':')[0]
|
|
||||||
fullDomain = getFullDomain(domain, port)
|
|
||||||
|
|
||||||
newUndoAnnounce = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
|
||||||
'type': 'Undo',
|
|
||||||
'cc': [],
|
|
||||||
'to': [toUrl],
|
|
||||||
'object': {
|
|
||||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
|
||||||
'cc': [],
|
|
||||||
'object': objectUrl,
|
|
||||||
'to': [toUrl],
|
|
||||||
'type': 'Announce'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ccUrl:
|
|
||||||
if len(ccUrl) > 0:
|
|
||||||
newUndoAnnounce['object']['cc'] = [ccUrl]
|
|
||||||
|
|
||||||
announceNickname = None
|
|
||||||
announceDomain = None
|
|
||||||
announcePort = None
|
|
||||||
if '/users/' in objectUrl or \
|
|
||||||
'/accounts/' in objectUrl or \
|
|
||||||
'/channel/' in objectUrl or \
|
|
||||||
'/profile/' in objectUrl:
|
|
||||||
announceNickname = getNicknameFromActor(objectUrl)
|
|
||||||
announceDomain, announcePort = getDomainFromActor(objectUrl)
|
|
||||||
|
|
||||||
if announceNickname and announceDomain:
|
|
||||||
sendSignedJson(newUndoAnnounce, session, baseDir,
|
|
||||||
nickname, domain, port,
|
|
||||||
announceNickname, announceDomain, announcePort,
|
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
|
||||||
httpPrefix, True, clientToServer, federationList,
|
|
||||||
sendThreads, postLog, cachedWebfingers,
|
|
||||||
personCache, debug)
|
|
||||||
|
|
||||||
return newUndoAnnounce
|
|
||||||
|
|
||||||
|
|
||||||
def undoAnnouncePublic(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
|
||||||
objectUrl: str, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool) -> {}:
|
|
||||||
"""Undoes a public announcement
|
|
||||||
"""
|
|
||||||
fromDomain = getFullDomain(domain, port)
|
|
||||||
|
|
||||||
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,
|
|
||||||
objectUrl, True, clientToServer,
|
|
||||||
sendThreads, postLog,
|
|
||||||
personCache, cachedWebfingers,
|
|
||||||
debug)
|
|
||||||
|
|
||||||
|
|
||||||
def undoRepeatPost(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
|
||||||
announceNickname: str, announceDomain: str,
|
|
||||||
announcePort: int, announceHttpsPrefix: str,
|
|
||||||
announceStatusNumber: int, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool) -> {}:
|
|
||||||
"""Undoes a status post repeat
|
|
||||||
"""
|
|
||||||
announcedDomain = getFullDomain(announceDomain, announcePort)
|
|
||||||
|
|
||||||
objectUrl = announceHttpsPrefix + '://' + announcedDomain + '/users/' + \
|
|
||||||
announceNickname + '/statuses/' + str(announceStatusNumber)
|
|
||||||
|
|
||||||
return undoAnnouncePublic(session, baseDir, federationList,
|
|
||||||
nickname, domain, port, httpPrefix,
|
|
||||||
objectUrl, clientToServer,
|
|
||||||
sendThreads, postLog,
|
|
||||||
personCache, cachedWebfingers,
|
|
||||||
debug)
|
|
||||||
|
|
||||||
|
|
||||||
def sendAnnounceViaServer(baseDir: str, session,
|
def sendAnnounceViaServer(baseDir: str, session,
|
||||||
fromNickname: str, password: str,
|
fromNickname: str, password: str,
|
||||||
fromDomain: str, fromPort: int,
|
fromDomain: str, fromPort: int,
|
||||||
|
|
|
||||||
12
auth.py
12
auth.py
|
|
@ -14,7 +14,7 @@ import secrets
|
||||||
from utils import isSystemAccount
|
from utils import isSystemAccount
|
||||||
|
|
||||||
|
|
||||||
def hashPassword(password: str) -> str:
|
def _hashPassword(password: str) -> str:
|
||||||
"""Hash a password for storing
|
"""Hash a password for storing
|
||||||
"""
|
"""
|
||||||
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
|
salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii')
|
||||||
|
|
@ -25,7 +25,7 @@ def hashPassword(password: str) -> str:
|
||||||
return (salt + pwdhash).decode('ascii')
|
return (salt + pwdhash).decode('ascii')
|
||||||
|
|
||||||
|
|
||||||
def getPasswordHash(salt: str, providedPassword: str) -> str:
|
def _getPasswordHash(salt: str, providedPassword: str) -> str:
|
||||||
"""Returns the hash of a password
|
"""Returns the hash of a password
|
||||||
"""
|
"""
|
||||||
pwdhash = hashlib.pbkdf2_hmac('sha512',
|
pwdhash = hashlib.pbkdf2_hmac('sha512',
|
||||||
|
|
@ -57,7 +57,7 @@ def constantTimeStringCheck(string1: str, string2: str) -> bool:
|
||||||
return matched
|
return matched
|
||||||
|
|
||||||
|
|
||||||
def verifyPassword(storedPassword: str, providedPassword: str) -> bool:
|
def _verifyPassword(storedPassword: str, providedPassword: str) -> bool:
|
||||||
"""Verify a stored password against one provided by user
|
"""Verify a stored password against one provided by user
|
||||||
"""
|
"""
|
||||||
if not storedPassword:
|
if not storedPassword:
|
||||||
|
|
@ -66,7 +66,7 @@ def verifyPassword(storedPassword: str, providedPassword: str) -> bool:
|
||||||
return False
|
return False
|
||||||
salt = storedPassword[:64]
|
salt = storedPassword[:64]
|
||||||
storedPassword = storedPassword[64:]
|
storedPassword = storedPassword[64:]
|
||||||
pwHash = getPasswordHash(salt, providedPassword)
|
pwHash = _getPasswordHash(salt, providedPassword)
|
||||||
return constantTimeStringCheck(pwHash, storedPassword)
|
return constantTimeStringCheck(pwHash, storedPassword)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ def authorizeBasic(baseDir: str, path: str, authHeader: str,
|
||||||
if line.startswith(nickname+':'):
|
if line.startswith(nickname+':'):
|
||||||
storedPassword = \
|
storedPassword = \
|
||||||
line.split(':')[1].replace('\n', '').replace('\r', '')
|
line.split(':')[1].replace('\n', '').replace('\r', '')
|
||||||
success = verifyPassword(storedPassword, providedPassword)
|
success = _verifyPassword(storedPassword, providedPassword)
|
||||||
if not success:
|
if not success:
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: Password check failed for ' + nickname)
|
print('DEBUG: Password check failed for ' + nickname)
|
||||||
|
|
@ -159,7 +159,7 @@ def storeBasicCredentials(baseDir: str, nickname: str, password: str) -> bool:
|
||||||
os.mkdir(baseDir + '/accounts')
|
os.mkdir(baseDir + '/accounts')
|
||||||
|
|
||||||
passwordFile = baseDir + '/accounts/passwords'
|
passwordFile = baseDir + '/accounts/passwords'
|
||||||
storeStr = nickname + ':' + hashPassword(password)
|
storeStr = nickname + ':' + _hashPassword(password)
|
||||||
if os.path.isfile(passwordFile):
|
if os.path.isfile(passwordFile):
|
||||||
if nickname + ':' in open(passwordFile).read():
|
if nickname + ':' in open(passwordFile).read():
|
||||||
with open(passwordFile, "r") as fin:
|
with open(passwordFile, "r") as fin:
|
||||||
|
|
|
||||||
62
blog.py
62
blog.py
|
|
@ -26,7 +26,7 @@ from newswire import rss2Header
|
||||||
from newswire import rss2Footer
|
from newswire import rss2Footer
|
||||||
|
|
||||||
|
|
||||||
def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
def _noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||||
nickname: str, domain: str, domainFull: str,
|
nickname: str, domain: str, domainFull: str,
|
||||||
postId: str, depth=0) -> int:
|
postId: str, depth=0) -> int:
|
||||||
"""Returns the number of replies on the post
|
"""Returns the number of replies on the post
|
||||||
|
|
@ -66,7 +66,8 @@ def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||||
replyPostId = replyPostId.replace('.json', '')
|
replyPostId = replyPostId.replace('.json', '')
|
||||||
if locatePost(baseDir, nickname, domain, replyPostId):
|
if locatePost(baseDir, nickname, domain, replyPostId):
|
||||||
replyPostId = replyPostId.replace('.replies', '')
|
replyPostId = replyPostId.replace('.replies', '')
|
||||||
replies += 1 + noOfBlogReplies(baseDir, httpPrefix, translate,
|
replies += \
|
||||||
|
1 + _noOfBlogReplies(baseDir, httpPrefix, translate,
|
||||||
nickname, domain, domainFull,
|
nickname, domain, domainFull,
|
||||||
replyPostId, depth+1)
|
replyPostId, depth+1)
|
||||||
else:
|
else:
|
||||||
|
|
@ -86,7 +87,7 @@ def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||||
return replies
|
return replies
|
||||||
|
|
||||||
|
|
||||||
def getBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
def _getBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||||
nickname: str, domain: str, domainFull: str,
|
nickname: str, domain: str, domainFull: str,
|
||||||
postId: str, depth=0) -> str:
|
postId: str, depth=0) -> str:
|
||||||
"""Returns a string containing html blog posts
|
"""Returns a string containing html blog posts
|
||||||
|
|
@ -136,7 +137,7 @@ def getBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||||
continue
|
continue
|
||||||
with open(postFilename, "r") as postFile:
|
with open(postFilename, "r") as postFile:
|
||||||
repliesStr += postFile.read() + '\n'
|
repliesStr += postFile.read() + '\n'
|
||||||
rply = getBlogReplies(baseDir, httpPrefix, translate,
|
rply = _getBlogReplies(baseDir, httpPrefix, translate,
|
||||||
nickname, domain, domainFull,
|
nickname, domain, domainFull,
|
||||||
replyPostId, depth+1)
|
replyPostId, depth+1)
|
||||||
if rply not in repliesStr:
|
if rply not in repliesStr:
|
||||||
|
|
@ -152,7 +153,7 @@ def getBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def htmlBlogPostContent(authorized: bool,
|
def _htmlBlogPostContent(authorized: bool,
|
||||||
baseDir: str, httpPrefix: str, translate: {},
|
baseDir: str, httpPrefix: str, translate: {},
|
||||||
nickname: str, domain: str, domainFull: str,
|
nickname: str, domain: str, domainFull: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
|
|
@ -269,7 +270,7 @@ def htmlBlogPostContent(authorized: bool,
|
||||||
'/users/' + nickname + '">' + translate['About the author'] + \
|
'/users/' + nickname + '">' + translate['About the author'] + \
|
||||||
'</a></p>\n'
|
'</a></p>\n'
|
||||||
|
|
||||||
replies = noOfBlogReplies(baseDir, httpPrefix, translate,
|
replies = _noOfBlogReplies(baseDir, httpPrefix, translate,
|
||||||
nickname, domain, domainFull,
|
nickname, domain, domainFull,
|
||||||
postJsonObject['object']['id'])
|
postJsonObject['object']['id'])
|
||||||
|
|
||||||
|
|
@ -288,11 +289,11 @@ def htmlBlogPostContent(authorized: bool,
|
||||||
else:
|
else:
|
||||||
blogStr += blogSeparator + '<h1>' + translate['Replies'] + '</h1>\n'
|
blogStr += blogSeparator + '<h1>' + translate['Replies'] + '</h1>\n'
|
||||||
if not titleStr:
|
if not titleStr:
|
||||||
blogStr += getBlogReplies(baseDir, httpPrefix, translate,
|
blogStr += _getBlogReplies(baseDir, httpPrefix, translate,
|
||||||
nickname, domain, domainFull,
|
nickname, domain, domainFull,
|
||||||
postJsonObject['object']['id'])
|
postJsonObject['object']['id'])
|
||||||
else:
|
else:
|
||||||
blogRepliesStr = getBlogReplies(baseDir, httpPrefix, translate,
|
blogRepliesStr = _getBlogReplies(baseDir, httpPrefix, translate,
|
||||||
nickname, domain, domainFull,
|
nickname, domain, domainFull,
|
||||||
postJsonObject['object']['id'])
|
postJsonObject['object']['id'])
|
||||||
blogStr += blogRepliesStr.replace('>' + titleStr + '<', '')
|
blogStr += blogRepliesStr.replace('>' + titleStr + '<', '')
|
||||||
|
|
@ -300,7 +301,7 @@ def htmlBlogPostContent(authorized: bool,
|
||||||
return blogStr
|
return blogStr
|
||||||
|
|
||||||
|
|
||||||
def htmlBlogPostRSS2(authorized: bool,
|
def _htmlBlogPostRSS2(authorized: bool,
|
||||||
baseDir: str, httpPrefix: str, translate: {},
|
baseDir: str, httpPrefix: str, translate: {},
|
||||||
nickname: str, domain: str, domainFull: str,
|
nickname: str, domain: str, domainFull: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
|
|
@ -331,7 +332,7 @@ def htmlBlogPostRSS2(authorized: bool,
|
||||||
return rssStr
|
return rssStr
|
||||||
|
|
||||||
|
|
||||||
def htmlBlogPostRSS3(authorized: bool,
|
def _htmlBlogPostRSS3(authorized: bool,
|
||||||
baseDir: str, httpPrefix: str, translate: {},
|
baseDir: str, httpPrefix: str, translate: {},
|
||||||
nickname: str, domain: str, domainFull: str,
|
nickname: str, domain: str, domainFull: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
|
|
@ -359,7 +360,7 @@ def htmlBlogPostRSS3(authorized: bool,
|
||||||
return rssStr
|
return rssStr
|
||||||
|
|
||||||
|
|
||||||
def htmlBlogRemoveCwButton(blogStr: str, translate: {}) -> str:
|
def _htmlBlogRemoveCwButton(blogStr: str, translate: {}) -> str:
|
||||||
"""Removes the CW button from blog posts, where the
|
"""Removes the CW button from blog posts, where the
|
||||||
summary field is instead used as the blog title
|
summary field is instead used as the blog title
|
||||||
"""
|
"""
|
||||||
|
|
@ -383,9 +384,9 @@ def htmlBlogPost(authorized: bool,
|
||||||
if os.path.isfile(baseDir + '/blog.css'):
|
if os.path.isfile(baseDir + '/blog.css'):
|
||||||
cssFilename = baseDir + '/blog.css'
|
cssFilename = baseDir + '/blog.css'
|
||||||
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
htmlBlogRemoveCwButton(blogStr, translate)
|
_htmlBlogRemoveCwButton(blogStr, translate)
|
||||||
|
|
||||||
blogStr += htmlBlogPostContent(authorized, baseDir,
|
blogStr += _htmlBlogPostContent(authorized, baseDir,
|
||||||
httpPrefix, translate,
|
httpPrefix, translate,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
domainFull, postJsonObject,
|
domainFull, postJsonObject,
|
||||||
|
|
@ -428,7 +429,7 @@ def htmlBlogPage(authorized: bool, session,
|
||||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||||
cssFilename = baseDir + '/epicyon.css'
|
cssFilename = baseDir + '/epicyon.css'
|
||||||
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
htmlBlogRemoveCwButton(blogStr, translate)
|
_htmlBlogRemoveCwButton(blogStr, translate)
|
||||||
|
|
||||||
blogsIndex = baseDir + '/accounts/' + \
|
blogsIndex = baseDir + '/accounts/' + \
|
||||||
nickname + '@' + domain + '/tlblogs.index'
|
nickname + '@' + domain + '/tlblogs.index'
|
||||||
|
|
@ -472,7 +473,7 @@ def htmlBlogPage(authorized: bool, session,
|
||||||
if item['type'] != 'Create':
|
if item['type'] != 'Create':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
blogStr += htmlBlogPostContent(authorized, baseDir,
|
blogStr += _htmlBlogPostContent(authorized, baseDir,
|
||||||
httpPrefix, translate,
|
httpPrefix, translate,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
domainFull, item,
|
domainFull, item,
|
||||||
|
|
@ -544,7 +545,7 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
||||||
continue
|
continue
|
||||||
|
|
||||||
blogRSS2 += \
|
blogRSS2 += \
|
||||||
htmlBlogPostRSS2(authorized, baseDir,
|
_htmlBlogPostRSS2(authorized, baseDir,
|
||||||
httpPrefix, translate,
|
httpPrefix, translate,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
domainFull, item,
|
domainFull, item,
|
||||||
|
|
@ -590,7 +591,7 @@ def htmlBlogPageRSS3(authorized: bool, session,
|
||||||
continue
|
continue
|
||||||
|
|
||||||
blogRSS3 += \
|
blogRSS3 += \
|
||||||
htmlBlogPostRSS3(authorized, baseDir,
|
_htmlBlogPostRSS3(authorized, baseDir,
|
||||||
httpPrefix, translate,
|
httpPrefix, translate,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
domainFull, item,
|
domainFull, item,
|
||||||
|
|
@ -599,26 +600,7 @@ def htmlBlogPageRSS3(authorized: bool, session,
|
||||||
return blogRSS3
|
return blogRSS3
|
||||||
|
|
||||||
|
|
||||||
def getBlogIndexesForAccounts(baseDir: str) -> {}:
|
def _noOfBlogAccounts(baseDir: str) -> int:
|
||||||
""" Get the index files for blogs for each account
|
|
||||||
and add them to a dict
|
|
||||||
"""
|
|
||||||
blogIndexes = {}
|
|
||||||
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
|
||||||
for acct in dirs:
|
|
||||||
if '@' not in acct:
|
|
||||||
continue
|
|
||||||
if 'inbox@' in acct:
|
|
||||||
continue
|
|
||||||
accountDir = os.path.join(baseDir + '/accounts', acct)
|
|
||||||
blogsIndex = accountDir + '/tlblogs.index'
|
|
||||||
if os.path.isfile(blogsIndex):
|
|
||||||
blogIndexes[acct] = blogsIndex
|
|
||||||
break
|
|
||||||
return blogIndexes
|
|
||||||
|
|
||||||
|
|
||||||
def noOfBlogAccounts(baseDir: str) -> int:
|
|
||||||
"""Returns the number of blog accounts
|
"""Returns the number of blog accounts
|
||||||
"""
|
"""
|
||||||
ctr = 0
|
ctr = 0
|
||||||
|
|
@ -636,7 +618,7 @@ def noOfBlogAccounts(baseDir: str) -> int:
|
||||||
return ctr
|
return ctr
|
||||||
|
|
||||||
|
|
||||||
def singleBlogAccountNickname(baseDir: str) -> str:
|
def _singleBlogAccountNickname(baseDir: str) -> str:
|
||||||
"""Returns the nickname of a single blog account
|
"""Returns the nickname of a single blog account
|
||||||
"""
|
"""
|
||||||
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
||||||
|
|
@ -666,8 +648,8 @@ def htmlBlogView(authorized: bool,
|
||||||
cssFilename = baseDir + '/epicyon.css'
|
cssFilename = baseDir + '/epicyon.css'
|
||||||
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
|
|
||||||
if noOfBlogAccounts(baseDir) <= 1:
|
if _noOfBlogAccounts(baseDir) <= 1:
|
||||||
nickname = singleBlogAccountNickname(baseDir)
|
nickname = _singleBlogAccountNickname(baseDir)
|
||||||
if nickname:
|
if nickname:
|
||||||
return htmlBlogPage(authorized, session,
|
return htmlBlogPage(authorized, session,
|
||||||
baseDir, httpPrefix, translate,
|
baseDir, httpPrefix, translate,
|
||||||
|
|
|
||||||
140
blurhash.py
140
blurhash.py
|
|
@ -39,17 +39,7 @@ alphabet = \
|
||||||
alphabet_values = dict(zip(alphabet, range(len(alphabet))))
|
alphabet_values = dict(zip(alphabet, range(len(alphabet))))
|
||||||
|
|
||||||
|
|
||||||
def base83_decode(base83_str):
|
def _base83_encode(value, length):
|
||||||
"""
|
|
||||||
Decodes a base83 string, as used in blurhash, to an integer.
|
|
||||||
"""
|
|
||||||
value = 0
|
|
||||||
for base83_char in base83_str:
|
|
||||||
value = value * 83 + alphabet_values[base83_char]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def base83_encode(value, length):
|
|
||||||
"""
|
"""
|
||||||
Decodes an integer to a base83 string, as used in blurhash.
|
Decodes an integer to a base83 string, as used in blurhash.
|
||||||
|
|
||||||
|
|
@ -67,7 +57,7 @@ def base83_encode(value, length):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def srgb_to_linear(value):
|
def _srgb_to_linear(value):
|
||||||
"""
|
"""
|
||||||
srgb 0-255 integer to linear 0.0-1.0 floating point conversion.
|
srgb 0-255 integer to linear 0.0-1.0 floating point conversion.
|
||||||
"""
|
"""
|
||||||
|
|
@ -77,14 +67,14 @@ def srgb_to_linear(value):
|
||||||
return math.pow((value + 0.055) / 1.055, 2.4)
|
return math.pow((value + 0.055) / 1.055, 2.4)
|
||||||
|
|
||||||
|
|
||||||
def sign_pow(value, exp):
|
def _sign_pow(value, exp):
|
||||||
"""
|
"""
|
||||||
Sign-preserving exponentiation.
|
Sign-preserving exponentiation.
|
||||||
"""
|
"""
|
||||||
return math.copysign(math.pow(abs(value), exp), value)
|
return math.copysign(math.pow(abs(value), exp), value)
|
||||||
|
|
||||||
|
|
||||||
def linear_to_srgb(value):
|
def _linear_to_srgb(value):
|
||||||
"""
|
"""
|
||||||
linear 0.0-1.0 floating point to srgb 0-255 integer conversion.
|
linear 0.0-1.0 floating point to srgb 0-255 integer conversion.
|
||||||
"""
|
"""
|
||||||
|
|
@ -94,101 +84,6 @@ def linear_to_srgb(value):
|
||||||
return int((1.055 * math.pow(value, 1 / 2.4) - 0.055) * 255 + 0.5)
|
return int((1.055 * math.pow(value, 1 / 2.4) - 0.055) * 255 + 0.5)
|
||||||
|
|
||||||
|
|
||||||
def blurhash_components(blurhash):
|
|
||||||
"""
|
|
||||||
Decodes and returns the number of x and y components in the given blurhash.
|
|
||||||
"""
|
|
||||||
if len(blurhash) < 6:
|
|
||||||
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
|
|
||||||
|
|
||||||
return size_x, size_y
|
|
||||||
|
|
||||||
|
|
||||||
def blurhash_decode(blurhash, width, height, punch=1.0, linear=False):
|
|
||||||
"""
|
|
||||||
Decodes the given blurhash to an image of the specified size.
|
|
||||||
|
|
||||||
Returns the resulting image a list of lists of 3-value sRGB 8 bit integer
|
|
||||||
lists. Set linear to True if you would prefer to get linear floating point
|
|
||||||
RGB back.
|
|
||||||
|
|
||||||
The punch parameter can be used to de- or increase the contrast of the
|
|
||||||
resulting image.
|
|
||||||
|
|
||||||
As per the original implementation it is suggested to only decode
|
|
||||||
to a relatively small size and then scale the result up, as it
|
|
||||||
basically looks the same anyways.
|
|
||||||
"""
|
|
||||||
if len(blurhash) < 6:
|
|
||||||
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
|
|
||||||
|
|
||||||
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:
|
|
||||||
raise ValueError("Invalid BlurHash length.")
|
|
||||||
|
|
||||||
# Decode DC component
|
|
||||||
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)
|
|
||||||
)]
|
|
||||||
|
|
||||||
# Decode AC components
|
|
||||||
for component in range(1, size_x * size_y):
|
|
||||||
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,
|
|
||||||
sign_pow((float(ac_value % 19) - 9.0)
|
|
||||||
/ 9.0, 2.0) * real_max_value
|
|
||||||
))
|
|
||||||
|
|
||||||
# Return image RGB values, as a list of lists of lists,
|
|
||||||
# consumable by something like numpy or PIL.
|
|
||||||
pixels = []
|
|
||||||
for y in range(height):
|
|
||||||
pixel_row = []
|
|
||||||
for x in range(width):
|
|
||||||
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
|
|
||||||
if linear is False:
|
|
||||||
pixel_row.append([
|
|
||||||
linear_to_srgb(pixel[0]),
|
|
||||||
linear_to_srgb(pixel[1]),
|
|
||||||
linear_to_srgb(pixel[2]),
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
pixel_row.append(pixel)
|
|
||||||
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
|
Calculates the blurhash for an image using the given x and y
|
||||||
|
|
@ -218,9 +113,9 @@ def blurhash_encode(image, components_x=4, components_y=4, linear=False):
|
||||||
image_linear_line = []
|
image_linear_line = []
|
||||||
for x in range(int(width)):
|
for x in range(int(width)):
|
||||||
image_linear_line.append([
|
image_linear_line.append([
|
||||||
srgb_to_linear(image[y][x][0]),
|
_srgb_to_linear(image[y][x][0]),
|
||||||
srgb_to_linear(image[y][x][1]),
|
_srgb_to_linear(image[y][x][1]),
|
||||||
srgb_to_linear(image[y][x][2])
|
_srgb_to_linear(image[y][x][2])
|
||||||
])
|
])
|
||||||
image_linear.append(image_linear_line)
|
image_linear.append(image_linear_line)
|
||||||
else:
|
else:
|
||||||
|
|
@ -254,9 +149,9 @@ def blurhash_encode(image, components_x=4, components_y=4, linear=False):
|
||||||
abs(component[1]), abs(component[2]))
|
abs(component[1]), abs(component[2]))
|
||||||
|
|
||||||
# Encode components
|
# Encode components
|
||||||
dc_value = (linear_to_srgb(components[0][0]) << 16) + \
|
dc_value = (_linear_to_srgb(components[0][0]) << 16) + \
|
||||||
(linear_to_srgb(components[0][1]) << 8) + \
|
(_linear_to_srgb(components[0][1]) << 8) + \
|
||||||
linear_to_srgb(components[0][2])
|
_linear_to_srgb(components[0][2])
|
||||||
|
|
||||||
quant_max_ac_component = int(max(0, min(82,
|
quant_max_ac_component = int(max(0, min(82,
|
||||||
math.floor(max_ac_component *
|
math.floor(max_ac_component *
|
||||||
|
|
@ -268,9 +163,9 @@ def blurhash_encode(image, components_x=4, components_y=4, linear=False):
|
||||||
r2 = r / ac_component_norm_factor
|
r2 = r / ac_component_norm_factor
|
||||||
g2 = g / ac_component_norm_factor
|
g2 = g / ac_component_norm_factor
|
||||||
b2 = b / ac_component_norm_factor
|
b2 = b / ac_component_norm_factor
|
||||||
r3 = math.floor(sign_pow(r2, 0.5) * 9.0 + 9.5)
|
r3 = math.floor(_sign_pow(r2, 0.5) * 9.0 + 9.5)
|
||||||
g3 = math.floor(sign_pow(g2, 0.5) * 9.0 + 9.5)
|
g3 = math.floor(_sign_pow(g2, 0.5) * 9.0 + 9.5)
|
||||||
b3 = math.floor(sign_pow(b2, 0.5) * 9.0 + 9.5)
|
b3 = math.floor(_sign_pow(b2, 0.5) * 9.0 + 9.5)
|
||||||
ac_values.append(
|
ac_values.append(
|
||||||
int(max(0.0, min(18.0, r3))) * 19 * 19 +
|
int(max(0.0, min(18.0, r3))) * 19 * 19 +
|
||||||
int(max(0.0, min(18.0, g3))) * 19 +
|
int(max(0.0, min(18.0, g3))) * 19 +
|
||||||
|
|
@ -279,10 +174,11 @@ def blurhash_encode(image, components_x=4, components_y=4, linear=False):
|
||||||
|
|
||||||
# Build final blurhash
|
# Build final blurhash
|
||||||
blurhash = ""
|
blurhash = ""
|
||||||
blurhash += base83_encode((components_x - 1) + (components_y - 1) * 9, 1)
|
blurhashValue = (components_x - 1) + (components_y - 1) * 9
|
||||||
blurhash += base83_encode(quant_max_ac_component, 1)
|
blurhash += _base83_encode(blurhashValue, 1)
|
||||||
blurhash += base83_encode(dc_value, 4)
|
blurhash += _base83_encode(quant_max_ac_component, 1)
|
||||||
|
blurhash += _base83_encode(dc_value, 4)
|
||||||
for ac_value in ac_values:
|
for ac_value in ac_values:
|
||||||
blurhash += base83_encode(ac_value, 2)
|
blurhash += _base83_encode(ac_value, 2)
|
||||||
|
|
||||||
return blurhash
|
return blurhash
|
||||||
|
|
|
||||||
208
bookmarks.py
208
bookmarks.py
|
|
@ -18,10 +18,6 @@ from utils import locatePost
|
||||||
from utils import getCachedPostFilename
|
from utils import getCachedPostFilename
|
||||||
from utils import loadJson
|
from utils import loadJson
|
||||||
from utils import saveJson
|
from utils import saveJson
|
||||||
from session import postJson
|
|
||||||
from webfinger import webfingerHandle
|
|
||||||
from auth import createBasicAuthHeader
|
|
||||||
from posts import getPersonBox
|
|
||||||
|
|
||||||
|
|
||||||
def undoBookmarksCollectionEntry(recentPostsCache: {},
|
def undoBookmarksCollectionEntry(recentPostsCache: {},
|
||||||
|
|
@ -111,7 +107,7 @@ def undoBookmarksCollectionEntry(recentPostsCache: {},
|
||||||
def bookmarkedByPerson(postJsonObject: {}, nickname: str, domain: str) -> bool:
|
def bookmarkedByPerson(postJsonObject: {}, nickname: str, domain: str) -> bool:
|
||||||
"""Returns True if the given post is bookmarked by the given person
|
"""Returns True if the given post is bookmarked by the given person
|
||||||
"""
|
"""
|
||||||
if noOfBookmarks(postJsonObject) == 0:
|
if _noOfBookmarks(postJsonObject) == 0:
|
||||||
return False
|
return False
|
||||||
actorMatch = domain + '/users/' + nickname
|
actorMatch = domain + '/users/' + nickname
|
||||||
for item in postJsonObject['object']['bookmarks']['items']:
|
for item in postJsonObject['object']['bookmarks']['items']:
|
||||||
|
|
@ -120,7 +116,7 @@ def bookmarkedByPerson(postJsonObject: {}, nickname: str, domain: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def noOfBookmarks(postJsonObject: {}) -> int:
|
def _noOfBookmarks(postJsonObject: {}) -> int:
|
||||||
"""Returns the number of bookmarks ona given post
|
"""Returns the number of bookmarks ona given post
|
||||||
"""
|
"""
|
||||||
if not postJsonObject.get('object'):
|
if not postJsonObject.get('object'):
|
||||||
|
|
@ -283,32 +279,6 @@ def bookmark(recentPostsCache: {},
|
||||||
return newBookmarkJson
|
return newBookmarkJson
|
||||||
|
|
||||||
|
|
||||||
def bookmarkPost(recentPostsCache: {},
|
|
||||||
session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
|
||||||
bookmarkNickname: str, bookmarkedomain: str,
|
|
||||||
bookmarkPort: int,
|
|
||||||
ccList: [],
|
|
||||||
bookmarkStatusNumber: int, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool, projectVersion: str) -> {}:
|
|
||||||
"""Bookmarks a given status post. This is only used by unit tests
|
|
||||||
"""
|
|
||||||
bookmarkedomain = getFullDomain(bookmarkedomain, bookmarkPort)
|
|
||||||
|
|
||||||
actorBookmarked = httpPrefix + '://' + bookmarkedomain + \
|
|
||||||
'/users/' + bookmarkNickname
|
|
||||||
objectUrl = actorBookmarked + '/statuses/' + str(bookmarkStatusNumber)
|
|
||||||
|
|
||||||
return bookmark(recentPostsCache,
|
|
||||||
session, baseDir, federationList, nickname, domain, port,
|
|
||||||
ccList, httpPrefix, objectUrl, actorBookmarked,
|
|
||||||
clientToServer,
|
|
||||||
sendThreads, postLog, personCache, cachedWebfingers,
|
|
||||||
debug, projectVersion)
|
|
||||||
|
|
||||||
|
|
||||||
def undoBookmark(recentPostsCache: {},
|
def undoBookmark(recentPostsCache: {},
|
||||||
session, baseDir: str, federationList: [],
|
session, baseDir: str, federationList: [],
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
|
|
@ -375,180 +345,6 @@ def undoBookmark(recentPostsCache: {},
|
||||||
return newUndoBookmarkJson
|
return newUndoBookmarkJson
|
||||||
|
|
||||||
|
|
||||||
def undoBookmarkPost(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
|
||||||
bookmarkNickname: str, bookmarkedomain: str,
|
|
||||||
bookmarkPort: int, ccList: [],
|
|
||||||
bookmarkStatusNumber: int, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool) -> {}:
|
|
||||||
"""Removes a bookmarked post
|
|
||||||
"""
|
|
||||||
bookmarkedomain = getFullDomain(bookmarkedomain, bookmarkPort)
|
|
||||||
|
|
||||||
objectUrl = httpPrefix + '://' + bookmarkedomain + \
|
|
||||||
'/users/' + bookmarkNickname + \
|
|
||||||
'/statuses/' + str(bookmarkStatusNumber)
|
|
||||||
|
|
||||||
return undoBookmark(session, baseDir, federationList,
|
|
||||||
nickname, domain, port,
|
|
||||||
ccList, httpPrefix, objectUrl, clientToServer,
|
|
||||||
sendThreads, postLog, personCache,
|
|
||||||
cachedWebfingers, debug)
|
|
||||||
|
|
||||||
|
|
||||||
def sendBookmarkViaServer(baseDir: str, session,
|
|
||||||
fromNickname: str, password: str,
|
|
||||||
fromDomain: str, fromPort: int,
|
|
||||||
httpPrefix: str, bookmarkUrl: str,
|
|
||||||
cachedWebfingers: {}, personCache: {},
|
|
||||||
debug: bool, projectVersion: str) -> {}:
|
|
||||||
"""Creates a bookmark via c2s
|
|
||||||
"""
|
|
||||||
if not session:
|
|
||||||
print('WARN: No session for sendBookmarkViaServer')
|
|
||||||
return 6
|
|
||||||
|
|
||||||
fromDomainFull = getFullDomain(fromDomain, fromPort)
|
|
||||||
|
|
||||||
newBookmarkJson = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
'type': 'Bookmark',
|
|
||||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
|
||||||
'object': bookmarkUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
handle = httpPrefix + '://' + fromDomainFull + '/@' + fromNickname
|
|
||||||
|
|
||||||
# lookup the inbox for the To handle
|
|
||||||
wfRequest = webfingerHandle(session, handle, httpPrefix,
|
|
||||||
cachedWebfingers,
|
|
||||||
fromDomain, projectVersion)
|
|
||||||
if not wfRequest:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: announce webfinger failed for ' + handle)
|
|
||||||
return 1
|
|
||||||
if not isinstance(wfRequest, dict):
|
|
||||||
print('WARN: Webfinger for ' + handle + ' did not return a dict. ' +
|
|
||||||
str(wfRequest))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
postToBox = 'outbox'
|
|
||||||
|
|
||||||
# get the actor inbox for the To handle
|
|
||||||
(inboxUrl, pubKeyId, pubKey,
|
|
||||||
fromPersonId, sharedInbox, avatarUrl,
|
|
||||||
displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
|
|
||||||
projectVersion, httpPrefix, fromNickname,
|
|
||||||
fromDomain, postToBox, 72483)
|
|
||||||
|
|
||||||
if not inboxUrl:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: No ' + postToBox + ' was found for ' + handle)
|
|
||||||
return 3
|
|
||||||
if not fromPersonId:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: No actor was found for ' + handle)
|
|
||||||
return 4
|
|
||||||
|
|
||||||
authHeader = createBasicAuthHeader(fromNickname, password)
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'host': fromDomain,
|
|
||||||
'Content-type': 'application/json',
|
|
||||||
'Authorization': authHeader
|
|
||||||
}
|
|
||||||
postResult = postJson(session, newBookmarkJson, [],
|
|
||||||
inboxUrl, headers)
|
|
||||||
if not postResult:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: POST announce failed for c2s to ' + inboxUrl)
|
|
||||||
return 5
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: c2s POST bookmark success')
|
|
||||||
|
|
||||||
return newBookmarkJson
|
|
||||||
|
|
||||||
|
|
||||||
def sendUndoBookmarkViaServer(baseDir: str, session,
|
|
||||||
fromNickname: str, password: str,
|
|
||||||
fromDomain: str, fromPort: int,
|
|
||||||
httpPrefix: str, bookmarkUrl: str,
|
|
||||||
cachedWebfingers: {}, personCache: {},
|
|
||||||
debug: bool, projectVersion: str) -> {}:
|
|
||||||
"""Undo a bookmark via c2s
|
|
||||||
"""
|
|
||||||
if not session:
|
|
||||||
print('WARN: No session for sendUndoBookmarkViaServer')
|
|
||||||
return 6
|
|
||||||
|
|
||||||
fromDomainFull = getFullDomain(fromDomain, fromPort)
|
|
||||||
|
|
||||||
newUndoBookmarkJson = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
'type': 'Undo',
|
|
||||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
|
||||||
'object': {
|
|
||||||
'type': 'Bookmark',
|
|
||||||
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
|
|
||||||
'object': bookmarkUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handle = httpPrefix + '://' + fromDomainFull + '/@' + fromNickname
|
|
||||||
|
|
||||||
# lookup the inbox for the To handle
|
|
||||||
wfRequest = webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
|
|
||||||
fromDomain, projectVersion)
|
|
||||||
if not wfRequest:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: announce webfinger failed for ' + handle)
|
|
||||||
return 1
|
|
||||||
if not isinstance(wfRequest, dict):
|
|
||||||
print('WARN: Webfinger for ' + handle + ' did not return a dict. ' +
|
|
||||||
str(wfRequest))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
postToBox = 'outbox'
|
|
||||||
|
|
||||||
# get the actor inbox for the To handle
|
|
||||||
(inboxUrl, pubKeyId, pubKey,
|
|
||||||
fromPersonId, sharedInbox, avatarUrl,
|
|
||||||
displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
|
|
||||||
projectVersion, httpPrefix, fromNickname,
|
|
||||||
fromDomain, postToBox, 72528)
|
|
||||||
|
|
||||||
if not inboxUrl:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: No ' + postToBox + ' was found for ' + handle)
|
|
||||||
return 3
|
|
||||||
if not fromPersonId:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: No actor was found for ' + handle)
|
|
||||||
return 4
|
|
||||||
|
|
||||||
authHeader = createBasicAuthHeader(fromNickname, password)
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'host': fromDomain,
|
|
||||||
'Content-type': 'application/json',
|
|
||||||
'Authorization': authHeader
|
|
||||||
}
|
|
||||||
postResult = postJson(session, newUndoBookmarkJson, [],
|
|
||||||
inboxUrl, headers)
|
|
||||||
if not postResult:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: POST announce failed for c2s to ' + inboxUrl)
|
|
||||||
return 5
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: c2s POST undo bookmark success')
|
|
||||||
|
|
||||||
return newUndoBookmarkJson
|
|
||||||
|
|
||||||
|
|
||||||
def outboxBookmark(recentPostsCache: {},
|
def outboxBookmark(recentPostsCache: {},
|
||||||
baseDir: str, httpPrefix: str,
|
baseDir: str, httpPrefix: str,
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
__filename__ = "categories.py"
|
||||||
|
__author__ = "Bob Mottram"
|
||||||
|
__license__ = "AGPL3+"
|
||||||
|
__version__ = "1.1.0"
|
||||||
|
__maintainer__ = "Bob Mottram"
|
||||||
|
__email__ = "bob@freedombone.net"
|
||||||
|
__status__ = "Production"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def getHashtagCategory(baseDir: str, hashtag: str) -> str:
|
||||||
|
"""Returns the category for the hashtag
|
||||||
|
"""
|
||||||
|
categoryFilename = baseDir + '/tags/' + hashtag + '.category'
|
||||||
|
if not os.path.isfile(categoryFilename):
|
||||||
|
categoryFilename = baseDir + '/tags/' + hashtag.title() + '.category'
|
||||||
|
if not os.path.isfile(categoryFilename):
|
||||||
|
categoryFilename = \
|
||||||
|
baseDir + '/tags/' + hashtag.upper() + '.category'
|
||||||
|
if not os.path.isfile(categoryFilename):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
with open(categoryFilename, 'r') as fp:
|
||||||
|
categoryStr = fp.read()
|
||||||
|
if categoryStr:
|
||||||
|
return categoryStr
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def getHashtagCategories(baseDir: str, recent=False, category=None) -> None:
|
||||||
|
"""Returns a dictionary containing hashtag categories
|
||||||
|
"""
|
||||||
|
hashtagCategories = {}
|
||||||
|
|
||||||
|
if recent:
|
||||||
|
currTime = datetime.datetime.utcnow()
|
||||||
|
daysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days
|
||||||
|
recently = daysSinceEpoch - 1
|
||||||
|
|
||||||
|
for subdir, dirs, files in os.walk(baseDir + '/tags'):
|
||||||
|
for f in files:
|
||||||
|
if not f.endswith('.category'):
|
||||||
|
continue
|
||||||
|
categoryFilename = os.path.join(baseDir + '/tags', f)
|
||||||
|
if not os.path.isfile(categoryFilename):
|
||||||
|
continue
|
||||||
|
hashtag = f.split('.')[0]
|
||||||
|
with open(categoryFilename, 'r') as fp:
|
||||||
|
categoryStr = fp.read()
|
||||||
|
|
||||||
|
if not categoryStr:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if category:
|
||||||
|
# only return a dictionary for a specific category
|
||||||
|
if categoryStr != category:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if recent:
|
||||||
|
tagsFilename = baseDir + '/tags/' + hashtag + '.txt'
|
||||||
|
if not os.path.isfile(tagsFilename):
|
||||||
|
continue
|
||||||
|
modTimesinceEpoc = \
|
||||||
|
os.path.getmtime(tagsFilename)
|
||||||
|
lastModifiedDate = \
|
||||||
|
datetime.datetime.fromtimestamp(modTimesinceEpoc)
|
||||||
|
fileDaysSinceEpoch = \
|
||||||
|
(lastModifiedDate -
|
||||||
|
datetime.datetime(1970, 1, 1)).days
|
||||||
|
if fileDaysSinceEpoch < recently:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not hashtagCategories.get(categoryStr):
|
||||||
|
hashtagCategories[categoryStr] = [hashtag]
|
||||||
|
else:
|
||||||
|
if hashtag not in hashtagCategories[categoryStr]:
|
||||||
|
hashtagCategories[categoryStr].append(hashtag)
|
||||||
|
break
|
||||||
|
return hashtagCategories
|
||||||
|
|
||||||
|
|
||||||
|
def _updateHashtagCategories(baseDir: str) -> None:
|
||||||
|
"""Regenerates the list of hashtag categories
|
||||||
|
"""
|
||||||
|
categoryListFilename = baseDir + '/accounts/categoryList.txt'
|
||||||
|
hashtagCategories = getHashtagCategories(baseDir)
|
||||||
|
if not hashtagCategories:
|
||||||
|
if os.path.isfile(categoryListFilename):
|
||||||
|
os.remove(categoryListFilename)
|
||||||
|
return
|
||||||
|
|
||||||
|
categoryList = []
|
||||||
|
for categoryStr, hashtagList in hashtagCategories.items():
|
||||||
|
categoryList.append(categoryStr)
|
||||||
|
categoryList.sort()
|
||||||
|
|
||||||
|
categoryListStr = ''
|
||||||
|
for categoryStr in categoryList:
|
||||||
|
categoryListStr += categoryStr + '\n'
|
||||||
|
|
||||||
|
# save a list of available categories for quick lookup
|
||||||
|
with open(categoryListFilename, 'w+') as fp:
|
||||||
|
fp.write(categoryListStr)
|
||||||
|
|
||||||
|
|
||||||
|
def _validHashtagCategory(category: str) -> bool:
|
||||||
|
"""Returns true if the category name is valid
|
||||||
|
"""
|
||||||
|
if not category:
|
||||||
|
return False
|
||||||
|
|
||||||
|
invalidChars = (',', ' ', '<', ';', '\\')
|
||||||
|
for ch in invalidChars:
|
||||||
|
if ch in category:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# too long
|
||||||
|
if len(category) > 40:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def setHashtagCategory(baseDir: str, hashtag: str, category: str,
|
||||||
|
force=False) -> bool:
|
||||||
|
"""Sets the category for the hashtag
|
||||||
|
"""
|
||||||
|
if not _validHashtagCategory(category):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not force:
|
||||||
|
hashtagFilename = baseDir + '/tags/' + hashtag + '.txt'
|
||||||
|
if not os.path.isfile(hashtagFilename):
|
||||||
|
hashtag = hashtag.title()
|
||||||
|
hashtagFilename = baseDir + '/tags/' + hashtag + '.txt'
|
||||||
|
if not os.path.isfile(hashtagFilename):
|
||||||
|
hashtag = hashtag.upper()
|
||||||
|
hashtagFilename = baseDir + '/tags/' + hashtag + '.txt'
|
||||||
|
if not os.path.isfile(hashtagFilename):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not os.path.isdir(baseDir + '/tags'):
|
||||||
|
os.mkdir(baseDir + '/tags')
|
||||||
|
categoryFilename = baseDir + '/tags/' + hashtag + '.category'
|
||||||
|
if force:
|
||||||
|
# don't overwrite any existing categories
|
||||||
|
if os.path.isfile(categoryFilename):
|
||||||
|
return False
|
||||||
|
with open(categoryFilename, 'w+') as fp:
|
||||||
|
fp.write(category)
|
||||||
|
_updateHashtagCategories(baseDir)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def guessHashtagCategory(tagName: str, hashtagCategories: {}) -> str:
|
||||||
|
"""Tries to guess a category for the given hashtag.
|
||||||
|
This works by trying to find the longest similar hashtag
|
||||||
|
"""
|
||||||
|
categoryMatched = ''
|
||||||
|
tagMatchedLen = 0
|
||||||
|
|
||||||
|
for categoryStr, hashtagList in hashtagCategories.items():
|
||||||
|
for hashtag in hashtagList:
|
||||||
|
if len(hashtag) < 3:
|
||||||
|
# avoid matching very small strings which often
|
||||||
|
# lead to spurious categories
|
||||||
|
continue
|
||||||
|
if hashtag not in tagName:
|
||||||
|
if tagName not in hashtag:
|
||||||
|
continue
|
||||||
|
if not categoryMatched:
|
||||||
|
tagMatchedLen = len(hashtag)
|
||||||
|
categoryMatched = categoryStr
|
||||||
|
else:
|
||||||
|
# match the longest tag
|
||||||
|
if len(hashtag) > tagMatchedLen:
|
||||||
|
categoryMatched = categoryStr
|
||||||
|
if not categoryMatched:
|
||||||
|
return
|
||||||
|
return categoryMatched
|
||||||
60
content.py
60
content.py
|
|
@ -33,7 +33,7 @@ def removeHtmlTag(htmlStr: str, tag: str) -> str:
|
||||||
return htmlStr
|
return htmlStr
|
||||||
|
|
||||||
|
|
||||||
def removeQuotesWithinQuotes(content: str) -> str:
|
def _removeQuotesWithinQuotes(content: str) -> str:
|
||||||
"""Removes any blockquote inside blockquote
|
"""Removes any blockquote inside blockquote
|
||||||
"""
|
"""
|
||||||
if '<blockquote>' not in content:
|
if '<blockquote>' not in content:
|
||||||
|
|
@ -96,7 +96,7 @@ def htmlReplaceEmailQuote(content: str) -> str:
|
||||||
else:
|
else:
|
||||||
lineStr = lineStr.replace('>', '<br>')
|
lineStr = lineStr.replace('>', '<br>')
|
||||||
newContent += '<p>' + lineStr + '</blockquote></p>'
|
newContent += '<p>' + lineStr + '</blockquote></p>'
|
||||||
return removeQuotesWithinQuotes(newContent)
|
return _removeQuotesWithinQuotes(newContent)
|
||||||
|
|
||||||
|
|
||||||
def htmlReplaceQuoteMarks(content: str) -> str:
|
def htmlReplaceQuoteMarks(content: str) -> str:
|
||||||
|
|
@ -314,7 +314,7 @@ def replaceEmojiFromTags(content: str, tag: [], messageType: str) -> str:
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def addMusicTag(content: str, tag: str) -> str:
|
def _addMusicTag(content: str, tag: str) -> str:
|
||||||
"""If a music link is found then ensure that the post is
|
"""If a music link is found then ensure that the post is
|
||||||
tagged appropriately
|
tagged appropriately
|
||||||
"""
|
"""
|
||||||
|
|
@ -416,7 +416,7 @@ def validHashTag(hashtag: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def addHashTags(wordStr: str, httpPrefix: str, domain: str,
|
def _addHashTags(wordStr: str, httpPrefix: str, domain: str,
|
||||||
replaceHashTags: {}, postHashtags: {}) -> bool:
|
replaceHashTags: {}, postHashtags: {}) -> bool:
|
||||||
"""Detects hashtags and adds them to the replacements dict
|
"""Detects hashtags and adds them to the replacements dict
|
||||||
Also updates the hashtags list to be added to the post
|
Also updates the hashtags list to be added to the post
|
||||||
|
|
@ -438,35 +438,7 @@ def addHashTags(wordStr: str, httpPrefix: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def loadEmojiDict(emojiDataFilename: str, emojiDict: {}) -> None:
|
def _addEmoji(baseDir: str, wordStr: str,
|
||||||
"""Creates an emoji dictionary based on emoji/emoji-data.txt
|
|
||||||
"""
|
|
||||||
if not os.path.isfile(emojiDataFilename):
|
|
||||||
return
|
|
||||||
with open(emojiDataFilename, "r") as fileHandler:
|
|
||||||
for line in fileHandler:
|
|
||||||
if len(line) < 5:
|
|
||||||
continue
|
|
||||||
if line.startswith('#'):
|
|
||||||
continue
|
|
||||||
if '; Emoji' not in line:
|
|
||||||
continue
|
|
||||||
if ')' not in line:
|
|
||||||
continue
|
|
||||||
emojiUnicode = line.split(' ')[0]
|
|
||||||
if len(emojiUnicode) < 4:
|
|
||||||
continue
|
|
||||||
if '..' in emojiUnicode:
|
|
||||||
emojiUnicode = emojiUnicode.split('..')[0]
|
|
||||||
emojiName = line.split(')', 1)[1].strip()
|
|
||||||
emojiName = emojiName.replace('\n', '').replace('\r', '')
|
|
||||||
emojiName = emojiName.replace(' ', '').replace('-', '')
|
|
||||||
if '..' in emojiName:
|
|
||||||
emojiName = emojiName.split('..')[0]
|
|
||||||
emojiDict[emojiName.lower()] = emojiUnicode
|
|
||||||
|
|
||||||
|
|
||||||
def addEmoji(baseDir: str, wordStr: str,
|
|
||||||
httpPrefix: str, domain: str,
|
httpPrefix: str, domain: str,
|
||||||
replaceEmoji: {}, postTags: {},
|
replaceEmoji: {}, postTags: {},
|
||||||
emojiDict: {}) -> bool:
|
emojiDict: {}) -> bool:
|
||||||
|
|
@ -517,7 +489,7 @@ def tagExists(tagType: str, tagName: str, tags: {}) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def addMention(wordStr: str, httpPrefix: str, following: str,
|
def _addMention(wordStr: str, httpPrefix: str, following: str,
|
||||||
replaceMentions: {}, recipients: [], tags: {}) -> bool:
|
replaceMentions: {}, recipients: [], tags: {}) -> bool:
|
||||||
"""Detects mentions and adds them to the replacements dict and
|
"""Detects mentions and adds them to the replacements dict and
|
||||||
recipients list
|
recipients list
|
||||||
|
|
@ -700,7 +672,7 @@ def removeLongWords(content: str, maxWordLength: int,
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def loadAutoTags(baseDir: str, nickname: str, domain: str) -> []:
|
def _loadAutoTags(baseDir: str, nickname: str, domain: str) -> []:
|
||||||
"""Loads automatic tags file and returns a list containing
|
"""Loads automatic tags file and returns a list containing
|
||||||
the lines of the file
|
the lines of the file
|
||||||
"""
|
"""
|
||||||
|
|
@ -713,7 +685,7 @@ def loadAutoTags(baseDir: str, nickname: str, domain: str) -> []:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def autoTag(baseDir: str, nickname: str, domain: str,
|
def _autoTag(baseDir: str, nickname: str, domain: str,
|
||||||
wordStr: str, autoTagList: [],
|
wordStr: str, autoTagList: [],
|
||||||
appendTags: []):
|
appendTags: []):
|
||||||
"""Generates a list of tags to be automatically appended to the content
|
"""Generates a list of tags to be automatically appended to the content
|
||||||
|
|
@ -747,7 +719,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
|
||||||
maxWordLength = 40
|
maxWordLength = 40
|
||||||
content = content.replace('\r', '')
|
content = content.replace('\r', '')
|
||||||
content = content.replace('\n', ' --linebreak-- ')
|
content = content.replace('\n', ' --linebreak-- ')
|
||||||
content = addMusicTag(content, 'nowplaying')
|
content = _addMusicTag(content, 'nowplaying')
|
||||||
contentSimplified = \
|
contentSimplified = \
|
||||||
content.replace(',', ' ').replace(';', ' ').replace('- ', ' ')
|
content.replace(',', ' ').replace(';', ' ').replace('- ', ' ')
|
||||||
contentSimplified = contentSimplified.replace('. ', ' ').strip()
|
contentSimplified = contentSimplified.replace('. ', ' ').strip()
|
||||||
|
|
@ -788,7 +760,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
|
||||||
# extract mentions and tags from words
|
# extract mentions and tags from words
|
||||||
longWordsList = []
|
longWordsList = []
|
||||||
prevWordStr = ''
|
prevWordStr = ''
|
||||||
autoTagsList = loadAutoTags(baseDir, nickname, domain)
|
autoTagsList = _loadAutoTags(baseDir, nickname, domain)
|
||||||
appendTags = []
|
appendTags = []
|
||||||
for wordStr in words:
|
for wordStr in words:
|
||||||
wordLen = len(wordStr)
|
wordLen = len(wordStr)
|
||||||
|
|
@ -797,12 +769,12 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
|
||||||
longWordsList.append(wordStr)
|
longWordsList.append(wordStr)
|
||||||
firstChar = wordStr[0]
|
firstChar = wordStr[0]
|
||||||
if firstChar == '@':
|
if firstChar == '@':
|
||||||
if addMention(wordStr, httpPrefix, following,
|
if _addMention(wordStr, httpPrefix, following,
|
||||||
replaceMentions, recipients, hashtags):
|
replaceMentions, recipients, hashtags):
|
||||||
prevWordStr = ''
|
prevWordStr = ''
|
||||||
continue
|
continue
|
||||||
elif firstChar == '#':
|
elif firstChar == '#':
|
||||||
if addHashTags(wordStr, httpPrefix, originalDomain,
|
if _addHashTags(wordStr, httpPrefix, originalDomain,
|
||||||
replaceHashTags, hashtags):
|
replaceHashTags, hashtags):
|
||||||
prevWordStr = ''
|
prevWordStr = ''
|
||||||
continue
|
continue
|
||||||
|
|
@ -819,16 +791,16 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
|
||||||
emojiDict = loadJson(baseDir + '/emoji/emoji.json')
|
emojiDict = loadJson(baseDir + '/emoji/emoji.json')
|
||||||
|
|
||||||
# print('TAG: looking up emoji for :'+wordStr2+':')
|
# print('TAG: looking up emoji for :'+wordStr2+':')
|
||||||
addEmoji(baseDir, ':' + wordStr2 + ':', httpPrefix,
|
_addEmoji(baseDir, ':' + wordStr2 + ':', httpPrefix,
|
||||||
originalDomain, replaceEmoji, hashtags,
|
originalDomain, replaceEmoji, hashtags,
|
||||||
emojiDict)
|
emojiDict)
|
||||||
else:
|
else:
|
||||||
if autoTag(baseDir, nickname, domain, wordStr,
|
if _autoTag(baseDir, nickname, domain, wordStr,
|
||||||
autoTagsList, appendTags):
|
autoTagsList, appendTags):
|
||||||
prevWordStr = ''
|
prevWordStr = ''
|
||||||
continue
|
continue
|
||||||
if prevWordStr:
|
if prevWordStr:
|
||||||
if autoTag(baseDir, nickname, domain,
|
if _autoTag(baseDir, nickname, domain,
|
||||||
prevWordStr + ' ' + wordStr,
|
prevWordStr + ' ' + wordStr,
|
||||||
autoTagsList, appendTags):
|
autoTagsList, appendTags):
|
||||||
prevWordStr = ''
|
prevWordStr = ''
|
||||||
|
|
@ -838,7 +810,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
|
||||||
# add any auto generated tags
|
# add any auto generated tags
|
||||||
for appended in appendTags:
|
for appended in appendTags:
|
||||||
content = content + ' ' + appended
|
content = content + ' ' + appended
|
||||||
addHashTags(appended, httpPrefix, originalDomain,
|
_addHashTags(appended, httpPrefix, originalDomain,
|
||||||
replaceHashTags, hashtags)
|
replaceHashTags, hashtags)
|
||||||
|
|
||||||
# replace words with their html versions
|
# replace words with their html versions
|
||||||
|
|
|
||||||
67
daemon.py
67
daemon.py
|
|
@ -21,7 +21,6 @@ import pyqrcode
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from session import createSession
|
from session import createSession
|
||||||
from webfinger import parseHandle
|
|
||||||
from webfinger import webfingerMeta
|
from webfinger import webfingerMeta
|
||||||
from webfinger import webfingerNodeInfo
|
from webfinger import webfingerNodeInfo
|
||||||
from webfinger import webfingerLookup
|
from webfinger import webfingerLookup
|
||||||
|
|
@ -59,7 +58,7 @@ from person import personBoxJson
|
||||||
from person import createSharedInbox
|
from person import createSharedInbox
|
||||||
from person import createNewsInbox
|
from person import createNewsInbox
|
||||||
from person import suspendAccount
|
from person import suspendAccount
|
||||||
from person import unsuspendAccount
|
from person import reenableAccount
|
||||||
from person import removeAccount
|
from person import removeAccount
|
||||||
from person import canRemovePost
|
from person import canRemovePost
|
||||||
from person import personSnooze
|
from person import personSnooze
|
||||||
|
|
@ -88,7 +87,7 @@ from inbox import populateReplies
|
||||||
from inbox import getPersonPubKey
|
from inbox import getPersonPubKey
|
||||||
from follow import getFollowingFeed
|
from follow import getFollowingFeed
|
||||||
from follow import sendFollowRequest
|
from follow import sendFollowRequest
|
||||||
from follow import unfollowPerson
|
from follow import unfollowAccount
|
||||||
from follow import createInitialLastSeen
|
from follow import createInitialLastSeen
|
||||||
from auth import authorize
|
from auth import authorize
|
||||||
from auth import createPassword
|
from auth import createPassword
|
||||||
|
|
@ -174,7 +173,7 @@ from shares import removeShare
|
||||||
from shares import expireShares
|
from shares import expireShares
|
||||||
from utils import getFullDomain
|
from utils import getFullDomain
|
||||||
from utils import removeHtml
|
from utils import removeHtml
|
||||||
from utils import setHashtagCategory
|
from categories import setHashtagCategory
|
||||||
from utils import isEditor
|
from utils import isEditor
|
||||||
from utils import getImageExtensions
|
from utils import getImageExtensions
|
||||||
from utils import mediaFileMimeType
|
from utils import mediaFileMimeType
|
||||||
|
|
@ -275,22 +274,6 @@ def saveDomainQrcode(baseDir: str, httpPrefix: str,
|
||||||
url.png(qrcodeFilename, scale)
|
url.png(qrcodeFilename, scale)
|
||||||
|
|
||||||
|
|
||||||
def readFollowList(filename: str) -> None:
|
|
||||||
"""Returns a list of ActivityPub addresses to follow
|
|
||||||
"""
|
|
||||||
followlist = []
|
|
||||||
if not os.path.isfile(filename):
|
|
||||||
return followlist
|
|
||||||
followUsers = open(filename, "r")
|
|
||||||
for u in followUsers:
|
|
||||||
if u not in followlist:
|
|
||||||
nickname, domain = parseHandle(u)
|
|
||||||
if nickname:
|
|
||||||
followlist.append(nickname + '@' + domain)
|
|
||||||
followUsers.close()
|
|
||||||
return followlist
|
|
||||||
|
|
||||||
|
|
||||||
class PubServer(BaseHTTPRequestHandler):
|
class PubServer(BaseHTTPRequestHandler):
|
||||||
protocol_version = 'HTTP/1.1'
|
protocol_version = 'HTTP/1.1'
|
||||||
|
|
||||||
|
|
@ -1091,13 +1074,14 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
beginSaveTime = time.time()
|
beginSaveTime = time.time()
|
||||||
# save the json for later queue processing
|
# save the json for later queue processing
|
||||||
|
messageBytesDecoded = messageBytes.decode('utf-8')
|
||||||
queueFilename = \
|
queueFilename = \
|
||||||
savePostToInboxQueue(self.server.baseDir,
|
savePostToInboxQueue(self.server.baseDir,
|
||||||
self.server.httpPrefix,
|
self.server.httpPrefix,
|
||||||
nickname,
|
nickname,
|
||||||
self.server.domainFull,
|
self.server.domainFull,
|
||||||
messageJson,
|
messageJson,
|
||||||
messageBytes.decode('utf-8'),
|
messageBytesDecoded,
|
||||||
headersDict,
|
headersDict,
|
||||||
self.path,
|
self.path,
|
||||||
self.server.debug)
|
self.server.debug)
|
||||||
|
|
@ -1531,7 +1515,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if moderationButton == 'suspend':
|
if moderationButton == 'suspend':
|
||||||
suspendAccount(baseDir, nickname, domain)
|
suspendAccount(baseDir, nickname, domain)
|
||||||
if moderationButton == 'unsuspend':
|
if moderationButton == 'unsuspend':
|
||||||
unsuspendAccount(baseDir, nickname)
|
reenableAccount(baseDir, nickname)
|
||||||
if moderationButton == 'filter':
|
if moderationButton == 'filter':
|
||||||
addGlobalFilter(baseDir, moderationText)
|
addGlobalFilter(baseDir, moderationText)
|
||||||
if moderationButton == 'unfilter':
|
if moderationButton == 'unfilter':
|
||||||
|
|
@ -2132,7 +2116,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
}
|
}
|
||||||
pathUsersSection = path.split('/users/')[1]
|
pathUsersSection = path.split('/users/')[1]
|
||||||
self.postToNickname = pathUsersSection.split('/')[0]
|
self.postToNickname = pathUsersSection.split('/')[0]
|
||||||
unfollowPerson(self.server.baseDir, self.postToNickname,
|
unfollowAccount(self.server.baseDir, self.postToNickname,
|
||||||
self.server.domain,
|
self.server.domain,
|
||||||
followingNickname, followingDomainFull)
|
followingNickname, followingDomainFull)
|
||||||
self._postToOutboxThread(unfollowJson)
|
self._postToOutboxThread(unfollowJson)
|
||||||
|
|
@ -2929,11 +2913,13 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if self.postToNickname:
|
if self.postToNickname:
|
||||||
if monthStr and yearStr:
|
if monthStr and yearStr:
|
||||||
if monthStr.isdigit() and yearStr.isdigit():
|
if monthStr.isdigit() and yearStr.isdigit():
|
||||||
|
yearInt = int(yearStr)
|
||||||
|
monthInt = int(monthStr)
|
||||||
removeCalendarEvent(baseDir,
|
removeCalendarEvent(baseDir,
|
||||||
self.postToNickname,
|
self.postToNickname,
|
||||||
domain,
|
domain,
|
||||||
int(yearStr),
|
yearInt,
|
||||||
int(monthStr),
|
monthInt,
|
||||||
removeMessageId)
|
removeMessageId)
|
||||||
self._postToOutboxThread(deleteJson)
|
self._postToOutboxThread(deleteJson)
|
||||||
if callingDomain.endswith('.onion') and onionDomain:
|
if callingDomain.endswith('.onion') and onionDomain:
|
||||||
|
|
@ -6854,6 +6840,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
recentPostsCache,
|
recentPostsCache,
|
||||||
maxRecentPosts,
|
maxRecentPosts,
|
||||||
translate,
|
translate,
|
||||||
|
self.server.baseDir,
|
||||||
self.server.session,
|
self.server.session,
|
||||||
cachedWebfingers,
|
cachedWebfingers,
|
||||||
personCache,
|
personCache,
|
||||||
|
|
@ -7079,6 +7066,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
'show inbox page')
|
'show inbox page')
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = htmlInbox(self.server.cssCache,
|
msg = htmlInbox(self.server.cssCache,
|
||||||
defaultTimeline,
|
defaultTimeline,
|
||||||
recentPostsCache,
|
recentPostsCache,
|
||||||
|
|
@ -7096,7 +7084,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
allowDeletion,
|
allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
projectVersion,
|
projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
YTReplacementDomain,
|
YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -7202,6 +7190,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.votingTimeMins)
|
self.server.votingTimeMins)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlInboxDMs(self.server.cssCache,
|
htmlInboxDMs(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -7220,7 +7209,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -7318,6 +7307,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.votingTimeMins)
|
self.server.votingTimeMins)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlInboxReplies(self.server.cssCache,
|
htmlInboxReplies(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -7336,7 +7326,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -7434,6 +7424,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.votingTimeMins)
|
self.server.votingTimeMins)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlInboxMedia(self.server.cssCache,
|
htmlInboxMedia(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -7452,7 +7443,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -7551,6 +7542,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.votingTimeMins)
|
self.server.votingTimeMins)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlInboxBlogs(self.server.cssCache,
|
htmlInboxBlogs(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -7569,7 +7561,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -7676,6 +7668,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
editor = isEditor(baseDir, currNickname)
|
editor = isEditor(baseDir, currNickname)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlInboxNews(self.server.cssCache,
|
htmlInboxNews(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -7694,7 +7687,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -7799,6 +7792,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
currNickname = currNickname.split('/')[0]
|
currNickname = currNickname.split('/')[0]
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlInboxFeatures(self.server.cssCache,
|
htmlInboxFeatures(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -7817,7 +7811,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -7984,6 +7978,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.votingTimeMins)
|
self.server.votingTimeMins)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlBookmarks(self.server.cssCache,
|
htmlBookmarks(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -8002,7 +7997,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -8104,6 +8099,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.votingTimeMins)
|
self.server.votingTimeMins)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlEvents(self.server.cssCache,
|
htmlEvents(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -8122,7 +8118,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
@ -8216,6 +8212,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.votingTimeMins)
|
self.server.votingTimeMins)
|
||||||
fullWidthTimelineButtonHeader = \
|
fullWidthTimelineButtonHeader = \
|
||||||
self.server.fullWidthTimelineButtonHeader
|
self.server.fullWidthTimelineButtonHeader
|
||||||
|
minimalNick = self._isMinimal(nickname)
|
||||||
msg = \
|
msg = \
|
||||||
htmlOutbox(self.server.cssCache,
|
htmlOutbox(self.server.cssCache,
|
||||||
self.server.defaultTimeline,
|
self.server.defaultTimeline,
|
||||||
|
|
@ -8234,7 +8231,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.allowDeletion,
|
self.server.allowDeletion,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self._isMinimal(nickname),
|
minimalNick,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly,
|
self.server.showPublishedDateOnly,
|
||||||
self.server.newswire,
|
self.server.newswire,
|
||||||
|
|
|
||||||
|
|
@ -6,499 +6,499 @@
|
||||||
<title>sport</title>
|
<title>sport</title>
|
||||||
<description>billiard darts swim motorsport snooker marathon hockey diving baseball Millwall sailing athletics skating skiing sport football</description>
|
<description>billiard darts swim motorsport snooker marathon hockey diving baseball Millwall sailing athletics skating skiing sport football</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>events</title>
|
||||||
|
<description>neverforget award OONIbday waybackwednesday notifications throwbackthursday adventskalender live Day deepthoughts thursdaythoughts humanrightsday followfriday wednesdaymotivation showerthoughts anarchymonday 100DaysToOffload ff holiday christmas week concert festival dontstarve onthisday livestream sunday screenshotsunday liverpool adayinthelife day InternationalCheetahDay interestingtimes meetup</description>
|
||||||
|
<link/>
|
||||||
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>gafam</title>
|
<title>gafam</title>
|
||||||
<description>zuckerberg caringissharing apple antitrust GoogleDown bankruptBezos youtube ffs facebook interoperability amazon amazonring googleplus Facebook LeiharbeitAbschaffen advertising adtech fuckgoogle microsoft dtm twitter caffeine skype ff chrome hildebrandt youtubedl degoogled youtubers google sharingiscaring gis dt dotcoms deleteyoutube Instagram fascistbook FuckGoogle degoogle fuschia ungoogled ring affordances googledown gafam inspiring fuckoffgoogle deletefacebook office365 instagram MatrixEffect playstore bigtech</description>
|
<description>zuckerberg caringissharing apple antitrust GoogleDown bankruptBezos youtube ffs facebook interoperability amazon boycottinstagram amazonring googleplus degooglisation Facebook LeiharbeitAbschaffen advertising adtech fuckgoogle microsoft dtm twitter caffeine skype chrome hildebrandt youtubedl degoogled youtubers google sharingiscaring gis dt dotcoms deleteyoutube Instagram fascistbook FuckGoogle degoogle fuschia ungoogled ring affordances googledown gafam inspiring fuckoffgoogle deletefacebook office365 instagram MatrixEffect playstore bigtech</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>activitypub</title>
|
<title>activitypub</title>
|
||||||
<description>followerpower Fediverse activitypub pleroma PeerTube webdev fediblock lazyfedi instances fedilab mastotips mastodev friendica misskey siskin followers fediart Pixelfed pixelfed fedidb block Fediseminar monal tusky peertubers imagedescription feditips fedizens Mastodon following epicyon mastomagic dev fediadmin pixeldev instanceblock isolategab fedireads PeertubeMastodonHost Bookwyrm socialhome fediverse smithereen mastodon fedi fediplay peertube lab mobilizon gemifedi</description>
|
<description>followerpower Fediverse activitypub pleroma PeerTube webdev fediblock lazyfedi federation instances fedilab mastotips mastodev friendica misskey siskin followers fediart Pixelfed pixelfed fediverseplaysjackbox fedidb block Fediseminar monal tusky peertubers imagedescription feditips fedizens Mastodon following epicyon mastomagic dev fediadmin pixeldev instanceblock mastodonmonday isolategab fedireads PeertubeMastodonHost Bookwyrm socialhome MastodonMondays fediverse smithereen mastodon fedi fediplay peertube lab mobilizon gemifedi</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>programming</title>
|
<title>programming</title>
|
||||||
<description>Easer cpp report programming css Python FrancisBacon2020 mixers release ada schutzstreifen rustlang ocaml program matrix uptronics profiles typescript forums publiccode FreeSoftware rocketchat early adventofcode scripting warn discord spyware git trevornoah copyright daretocare fedidev c sourcecode publiekecode framaforms WendyLPatrick grep django kingparrot Leiharbeit programmer haskell Tarifvertrag frgmntscnr github openrc tuskydev algorithms lisp forge pleaseshare HirsuteHippo resnetting libraries drivers javascript fragment cpm code elisp TeamFerment patterns html terminal rust sauerkraut request spiritbomb r dramasystem clojurescript ruby peppertop contractpatch computers racket python kabelfernsehen OpenSource Scheibenwischer</description>
|
<description>Easer cpp report programming css tootfic objects Python FrancisBacon2020 mixers release ada schutzstreifen rustlang ocaml program request_reaction uptronics solidarity hypocritcal profiles typescript forums publiccode FreeSoftware vieprivée early adventofcode scripting warn spyware git trevornoah zinccoop daretocare fedidev c sourcecode publiekecode misc framaforms WendyLPatrick grep django gmic sackthelot relevance_P1Y kingparrot Leiharbeit programmer haskell Tarifvertrag frgmntscnr github openrc tuskydev threema algorithms lisp forge pleaseshare HirsuteHippo resnetting fourtwenty libraries drivers javascript fragment cpm code elisp patterns html terminal rust sauerkraut request spiritbomb r dramasystem documentary clojurescript ruby contractpatch computers racket relationships python kabelfernsehen alternatives OpenSource Scheibenwischer</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>writing</title>
|
<title>writing</title>
|
||||||
<description>blog poem blogs interactivestorytelling WriteFreely goodreads journal poetry</description>
|
<description>blog authors poem smallstories blogs interactivestorytelling WriteFreely storytelling goodreads journal poetry</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>hardware</title>
|
<title>hardware</title>
|
||||||
<description>plugandplay PersonalComputer cyberdeck PineCUBE keyboards screenless modem analogcomputing TrueDelta keyboard cybredeck solarpunk lenovo fairelectronics ibm 3dprinting MechcanicalKeyboards openhardware raspberrypi barcode pinetime pinebookpro PinebookPro 3dprint arm paperComputer amd thinkpad print</description>
|
<description>plugandplay PersonalComputer cyberdeck PineCUBE keyboards screenless modem analogcomputing TrueDelta keyboard printmaker cybredeck laptop solarpunk recycling lenovo fairelectronics fuse ibm 3dprinting MechcanicalKeyboards openhardware raspberrypi barcode pinetime pinebookpro PinebookPro 3dprint arm paperComputer amd openpower thinkpad print</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>places</title>
|
<title>places</title>
|
||||||
<description>lapaz luanda asunción nouakchott conakry kyiv moscow saipan gibraltar dublin dannibleibt avarua hargeisa delhi niamey chișinău colombo brasília phnompenh mbabane belgrade belmopan pyongyang hannover ulaanbaatar oranjestad gaborone seattle ndjamena raw singapore kingedwardpoint abidjan nuuk pretoria papeete malé zagreb gitega abudhabi flyingfishcove castries georgetown hagåtña basseterre hamburg kinshasa suva valparaíso athens roseau baku charlotteamalie antananarivo domi pristina santiago sukhumi berlin uptronicsberlin funafuti libreville hanoi philipsburg tehran banjul prague andorralavella daw yerevan portauprince dakar paramaribo tifariti capetown tirana klima ankara ipswich managua lisbon bishkek amsterdam portonovo santodomingo bangkok bucharest kathmandu aden madrid sanjuan vienna kingston kabul damascus stockholm douglas willemstad thehague panamacity beirut amman newdelhi tórshavn nouméa oslo alofi gustavia paris video cockburntown ottawa stepanakert portofspain fsberlin honiara asmara florida nicosia helsinki taipei tegucigalpa tokyo tashkent MadeInEU sarajevo algiers nairobi muscat monaco riyadh lusaka wellington bissau juba mariehamn majuro buenosaires ngerulmud dhaka guatemalacity washington vatican kuwaitcity bern mexicocity bratislava bridgetown delhipolice tunis manila stanley matautu copenhagen barcelona lomé budapest ouagadougou mogadishu freetown victoria brazzaville portmoresby ashgabat kampala elaaiún vilnius bloemfontein sucre london marseille pagopago bradesestate oakland vaduz addis nürnberg naypyidaw CassetteNavigation khartoum baghdad bandar moroni portvila kingstown ChrisCrawford reykjavík manama accra windhoek nukualofa ciutatvella tbilisi canberra quito maputo cetinje putrajaya ramallah bogotá dodoma harare havana warsaw münster valletta localberlin ljubljana bamako kualalumpur podgorica rabat cotonou plymouth seoul Portland dushanbe bangui aotearoa westisland tskhinvali palikir caracas jamestown rome munich ass freestuffberlin sãotomé jakarta daressalaam sansalvador apia essex yaren cairo jerusalem brussels kigali southtarawa beijing minsk montevideo vientiane maseru hamilton doha tripoli portlouis lima adamstown abuja lilongwe nassau lobamba heathrow nyc montreal dili riga lesbos monrovia nursultan gab sanjosé marigot islamabad malabo tallinn sahara thimphu yaoundé praia bujumbura sofia skopje</description>
|
<description>lapaz luanda asunción nouakchott conakry kyiv moscow saipan gibraltar dublin dannibleibt avarua hargeisa delhi niamey chișinău colombo brasília phnompenh mbabane belgrade belmopan pyongyang hannover ulaanbaatar oranjestad gaborone seattle ndjamena raw singapore kingedwardpoint abidjan nuuk pretoria papeete malé zagreb gitega abudhabi flyingfishcove castries georgetown hagåtña borikua basseterre hamburg kinshasa suva valparaíso athens roseau baku charlotteamalie antananarivo domi pristina santiago sukhumi berlin uptronicsberlin funafuti libreville hanoi philipsburg tehran banjul prague andorralavella daw yerevan portauprince dakar paramaribo tifariti capetown tirana klima ankara ipswich managua lisbon bishkek amsterdam portonovo santodomingo bangkok bucharest kathmandu aden madrid sanjuan vienna kingston kabul damascus stockholm douglas willemstad thehague panamacity beirut amman newdelhi tórshavn nouméa oslo alofi gustavia paris video cockburntown ottawa stepanakert portofspain fsberlin honiara asmara florida nicosia helsinki taipei tegucigalpa tokyo tashkent larochelle MadeInEU sarajevo algiers nairobi muscat monaco riyadh lusaka wellington bissau juba mariehamn majuro buenosaires ngerulmud dhaka guatemalacity washington vatican kuwaitcity bern mexicocity bratislava bridgetown delhipolice tunis manila stanley matautu copenhagen barcelona lomé budapest ouagadougou mogadishu freetown victoria brazzaville portmoresby ashgabat kampala elaaiún vilnius bloemfontein sucre london marseille pagopago bradesestate oakland vaduz addis nürnberg naypyidaw CassetteNavigation khartoum baghdad bandar moroni lehavre portvila kingstown ChrisCrawford reykjavík manama accra windhoek nukualofa ciutatvella tbilisi canberra quito maputo cetinje putrajaya ramallah bogotá dodoma harare havana warsaw münster valletta localberlin ljubljana bamako kualalumpur podgorica rabat cotonou plymouth seoul Portland dushanbe bangui aotearoa westisland tskhinvali palikir caracas jamestown rome munich ass freestuffberlin sãotomé jakarta daressalaam sansalvador apia essex yaren cairo jerusalem brussels kigali southtarawa beijing minsk montevideo vientiane maseru hamilton doha tripoli celtic portlouis lima adamstown abuja lilongwe nassau lobamba heathrow nyc montreal dili riga lesbos monrovia nursultan gab sanjosé marigot islamabad malabo tallinn sahara thimphu yaoundé praia bujumbura sofia skopje</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>music</title>
|
<title>music</title>
|
||||||
<description>musicprodution punk ourbeats indiemusic streetpunk bandcamp musicians ipod skinheadmusic rap mp3 Music EnvoieStopHashtagAu81212 thecure vaporwave dubstep synthwave oi rave freemusic nowplaying hiphop experimentalmusic fedimusic soundcloud frankiegoestohollywood dj newwave dorkwave producing musicproduction NowPlaying libremusicproduction MusicAdvent coinkydink fedivers arianagrande synth music metal fediversemusic cyberpunkmusic BandcampFriday</description>
|
<description>musicprodution punk ourbeats indiemusic streetpunk bandcamp musicians jamendo ipod skinheadmusic rap mp3 Music EnvoieStopHashtagAu81212 thecure vaporwave dubstep synthwave oi rave freemusic nowplaying hiphop experimentalmusic fedimusic soundcloud frankiegoestohollywood dj newwave dorkwave producing musicproduction funkwhale NowPlaying libremusicproduction MusicAdvent coinkydink fedivers arianagrande synth music metal fediversemusic cyberpunkmusic BandcampFriday</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>politics</title>
|
<title>politics</title>
|
||||||
<description>solidarity hypocritcal TakeOurPowerBack cia community wageslavery immigration dissent liberation fascism techtuesday skyofmywindow freedomofspeech rojava humanrights leftists Socialism ukpol FreeKeithLamar copwatch capitalismkills petition BorisJohnson freedom abolitionnow anarchism DefundThePolice technews smalltech oilwars bjp ThirdRunway election sky_of_my_window generalstrike antipolitics digitalfreedom mayday hatespeech fascists burntheprisons cyberlaw peerproduction corporations iww commons corporatewatch wageslave frontex communism RemoveThePolice neoliberalism socialecology MutualAid capitalism technology prisons conspiracytheories KeirStarmer politics inclusivity anarchisme nzpol Bookchin brexit totalitarianism TyskySour Labour green BAME decolonizeyourmind surfaceworldblows ecofascism SocietalChange facialrecognition propaganda decolonization digitalrights polizei xp 18Source redandanarchistskinheads PritiPatel latestagecapitalism racist elections RussellMaroonShoatz white prisoners warrants policebrutality borisjohnson Anarchist press mutuality whitehouse freedomofexpression censorship decolonize decenterwhiteness Biden ChineseAppBan cooperative modi law deathtoamerica manipulation britpol Capitalism surveillancecapitalism leftist Revolution ukpolitics blacklivesmatter FreeAlabamaMovement rentstrike dsa techno migration mutualaid multipleexposure AbolishPrison fascist socialcoop anarchistprisoners polizeiproblem wordpress uselection IDPol ourstreets refugees acab freewestpapua tech</description>
|
<description>TakeOurPowerBack cia community wageslavery immigration dissent liberation fascism techtuesday skyofmywindow freedomofspeech rojava humanrights leftists Socialism ukpol FreeKeithLamar copwatch capitalismkills petition BorisJohnson freedom abolitionnow anarchism DefundThePolice technews smalltech oilwars bjp ThirdRunway election sky_of_my_window generalstrike antipolitics digitalfreedom mayday hatespeech fascists lowtech burntheprisons cyberlaw peerproduction corporations iww commons corporatewatch wageslave frontex communism RemoveThePolice neoliberalism socialecology MutualAid capitalism technology prisons conspiracytheories KeirStarmer politics inclusivity anarchisme nzpol Bookchin ClemencyNow brexit totalitarianism TyskySour Labour green BAME decolonizeyourmind surfaceworldblows ecofascism SocietalChange facialrecognition anarchy propaganda decolonization digitalrights polizei xp 18Source redandanarchistskinheads PritiPatel latestagecapitalism racist elections RussellMaroonShoatz white prisoners warrants policebrutality borisjohnson Anarchist press mutuality whitehouse freedomofexpression censorship decolonize decenterwhiteness Biden ChineseAppBan cooperative modi law deathtoamerica manipulation britpol Capitalism surveillancecapitalism leftist Revolution ukpolitics blacklivesmatter FreeAlabamaMovement rentstrike dsa techno migration mutualaid multipleexposure AbolishPrison fascist socialcoop anarchistprisoners polizeiproblem uselection IDPol Slavetrade ourstreets refugees acab freewestpapua tech</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>food</title>
|
<title>food</title>
|
||||||
<description>vitamind cake margarine dessert salsa caviar theexpanse cookery pietons food skillet liquor milk bolognese recipe foodporn yeast plate waffle biscuit glaze omelette filet pastry wine hamburger juice Amazfish sourdough nuts gras toast broth batter foodie ketchup seasoning mayo soup pan voc teamcapy mayonnaise vegan dish avocado spice bakery cooking yogurt spotify crumble cider butter cook cobbler steak pizza soda aroma oil flour cream nutella pie cuisine tartar tea marinade mushroom entree bread salad beans fresh syrup fermentation mushrooms cookie curd soysauce pudding beer baking fish foodwaste wheat pot stew chocolate paste wok recipes olive burger candy kitchen coffee bagel taste meat noodle raclette caramel rice eggs grill poutine lard croissant pasta foods cheese oregano drink muffin foie sauce soy cocoa sandwich mousse chili vinegar</description>
|
<description>vitamind cake margarine dessert salsa caviar theexpanse cookery pietons food skillet liquor milk bolognese recipe foodporn yeast plate waffle biscuit glaze omelette filet pastry wine hamburger juice Amazfish sourdough nuts gras toast broth batter foodie ketchup seasoning mayo soup pan voc teamcapy mayonnaise vegan dish avocado spice bakery cooking yogurt spotify crumble cider butter cook pottery cobbler steak pizza soda fedikitchen aroma oil flour cream nutella pie cuisine tartar tea marinade mushroom entree bread salad beans fresh syrup fermentation mushrooms cookie curd soysauce pudding beer baking fish foodwaste wheat pot TeamFerment stew chocolate paste wok recipes olive burger candy kitchen coffee bagel taste meat noodle raclette caramel rice eggs grill poutine lard croissant pasta foods cheese oregano drink muffin foie sauce soy vore cocoa sandwich mousse chili vinegar</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>farming</title>
|
<title>farming</title>
|
||||||
<description>johndeere</description>
|
<description>johndeere</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>events</title>
|
|
||||||
<description>award OONIbday waybackwednesday notifications throwbackthursday adventskalender live Day deepthoughts thursdaythoughts humanrightsday followfriday wednesdaymotivation showerthoughts anarchymonday 100DaysToOffload holiday Introduction christmas week anarchy concert festival dontstarve onthisday livestream sunday screenshotsunday adayinthelife day InternationalCheetahDay interestingtimes meetup</description>
|
|
||||||
<link/>
|
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>countries</title>
|
<title>countries</title>
|
||||||
<description>romania burma lithuania solomon chile Instanz opensuse fiji tajikistan benin paraguay eeuu senegal ukraine italy brunei nicaragua guyana Pflanzenbestimmung euphoria zambia iceland morocco netherlands swaziland bosnian suriname elsalvador russia samoa european czech belarus hayabusa2 kyrgyzstan uk abuse translation sanmarino catalonia panama japan venezuela gambia freeNukem kuwait barbados papua greece switzerland uae nigeria usa angola honduras djibouti laos sierraleone cambodia ych vietnam dust3d neofeud seychelles marshall kazakhstan estonia tonga stlucia burundi bangladesh egypt mali congo us jordan speedrun grenada israel algeria ghana bosnia russian industrial eritrea bhutan hungary saudi slovenia tig bahamas australia kiribati togo koreanorth poland malawi capeverde run armenia american hautrauswasgeht bahrain mozambique beleuchtung southsudan syria micronesia maldives iran sweden ethiopia cuba liberia canada burkina somalia Chile scotland vaticancity easttimor austria turkey yemen Bolivia denmark trunk madagascar finland philippines ivorycoast haiti ecuador Portugal azerbaijan gasuk spain albania afghanistan europe mauritania dominica thailand belize westpapuauprising macedonia illustration montenegro qatar mongolia costarica boatingeurope birdsofkenya latvia uzbekistan ireland iraq malaysia mexico mauritius oman chad nz georgia zimbabwe france serbia lesotho oddmuse tunisia argentina cameroon namibia sudan indonesia colombia tuvalu beckychambers turkmenistan tanzania germany neuhier norway comoros auteursrecht guatemala kosovo andorra wales servus pakistan belgium china antigua life koreasouth newzealand einzelfall rwanda luxembourg libya italyisntreal nauru Anarchismus moldova palau taiwan kenya trinidad eu botswana CuriosidadesVariadas jamaica vanuatu cyprus aminus3 malta niger westpapua busse unitedstates myanmar saintvincent guinea nepal peru uganda uruguay india lebanon neurodiversity southafrica croatia europeanunion bolivia chinese dominican srilanka bulgaria slovakia speedrunning gabon stkitts liechtenstein brazil shutdowncanada</description>
|
<description>romania burma lithuania solomon chile Instanz opensuse fiji tajikistan benin paraguay eeuu senegal ukraine italy brunei nicaragua guyana Pflanzenbestimmung euphoria zambia iceland morocco netherlands swaziland bosnian solo suriname elsalvador russia samoa european czech belarus hayabusa2 kyrgyzstan uk abuse translation sanmarino catalonia panama japan buyused venezuela gambia freeNukem kuwait barbados papua greece switzerland uae nigeria usa angola honduras djibouti laos sierraleone cambodia ych vietnam dust3d neofeud seychelles marshall kazakhstan estonia tonga stlucia burundi bangladesh egypt mali congo us jordan speedrun grenada israel algeria ghana bosnia russian industrial eritrea bhutan hungary saudi slovenia uspol tig bahamas australia kiribati togo koreanorth poland malawi capeverde run armenia american hautrauswasgeht bahrain mozambique beleuchtung southsudan syria micronesia maldives iran indigenous sweden ethiopia cuba liberia canada burkina somalia Chile scotland vaticancity easttimor austria turkey yemen Bolivia denmark trunk madagascar finland philippines ivorycoast haiti ecuador Portugal azerbaijan gasuk spain albania afghanistan europe mauritania dominica thailand belize westpapuauprising macedonia illustration montenegro qatar mongolia costarica boatingeurope birdsofkenya latvia uzbekistan ireland iraq malaysia mexico mauritius oman chad nz georgia zimbabwe france serbia lesotho oddmuse tunisia argentina cameroon namibia sudan indonesia colombia tuvalu beckychambers turkmenistan tanzania germany neuhier norway comoros auteursrecht guatemala kosovo andorra wales servus pakistan belgium china antigua life koreasouth newzealand einzelfall rwanda luxembourg libya italyisntreal nauru Anarchismus moldova palau taiwan kenya trinidad eu botswana CuriosidadesVariadas jamaica vanuatu cyprus aminus3 malta niger westpapua busse unitedstates myanmar saintvincent guinea nepal peru uganda uruguay india lebanon neurodiversity southafrica croatia europeanunion bolivia chinese dominican srilanka bulgaria slovakia speedrunning gabon stkitts liechtenstein brazil shutdowncanada</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>cycling</title>
|
<title>cycling</title>
|
||||||
<description>bicycle cycling bike Snowbike</description>
|
<description>bicycle cycling bike Snowbike</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>phones</title>
|
<title>phones</title>
|
||||||
<description>mobileapp pine fdroid plasmamobile android smartphone iOS14 BriarProject pinephone mobile fairphone ubuntutouch Android ubports osmand vodafone iphones postmarketos iOS microg mobileKüfA</description>
|
<description>mobileapp pine fdroid plasmamobile android smartphone iOS14 BriarProject pinephone mobile fairphone ubuntutouch Android ubports osmand vodafone iphones postmarketos iOS microg mobileKüfA</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>science</title>
|
<title>science</title>
|
||||||
<description>math womeninstem supercollider dawkins graphTheory psychology biology gene paleontology</description>
|
<description>math womeninstem supercollider nextgeneration archaeologist dawkins graphTheory psychology biology gene paleontology</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>pandemic</title>
|
<title>pandemic</title>
|
||||||
<description>covid19 corona Coronavirus CoronaWarnApp facemasks vaccines vaccine pandemic contacttracing covid coronavirus Lockdown codid19 COVID19 YesWeWork ContactTracing COVID</description>
|
<description>covid19 corona Coronavirus CoronaWarnApp facemasks vaccines vaccine pandemic contacttracing covid coronavirus virus Lockdown codid19 COVID19 YesWeWork ContactTracing COVID</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>software</title>
|
<title>software</title>
|
||||||
<description>app freedombox windows libre nginx Framasoft invidious drm publicdomain kubernetes fossmendations jami FuckOffZoom quicksy free docker freesoftware gimp foss nextcloud wechat openscad TabOrder ikiwiki Linux outreachy lyft nitter opensource diaspora yunohost littlebigdetails cabal conferencing accessibility devops owncast emacs freiesoftware email chatapps floss plugins deltachat application uifail FOSS bittorrent zoom gpl FriendofGNOME usability obnam snap cryptpad software OwnStream zrythm mumble grsync containers irssi mutt design gameoftrees backup rotonde GNU apps licensing profanity ffmpeg lemmy OSM win10 jitsi ux rsync libreoffice dino plugin OCUPACAOCARLOSMARIGHELLA whatsapp openoffice</description>
|
<description>app freedombox windows libre nginx Framasoft invidious drm publicdomain kubernetes fossmendations jami FuckOffZoom quicksy free docker freesoftware gimp foss matrix nextcloud wechat openscad TabOrder ikiwiki Linux rocketchat outreachy lyft nitter discord opensource diaspora yunohost littlebigdetails cabal conferencing accessibility devops owncast emacs freiesoftware email chatapps floss plugins deltachat application uifail FOSS bittorrent zoom gpl FriendofGNOME usability obnam snap cryptpad software OwnStream zrythm mumble grsync containers irssi mutt design gameoftrees backup rotonde GNU thunderbird apps licensing profanity ffmpeg lemmy OSM win10 jitsi wordpress ux rsync libreoffice dino plugin OCUPACAOCARLOSMARIGHELLA whatsapp openoffice</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>security</title>
|
<title>security</title>
|
||||||
<description>encrypt password history cryptography solarwinds infosec gchq IHaveSomethingToHide IronySec cryptowars supplychainattacks UseAMaskUseTor cyberattack security tor e2e bruceschneier vpn openssh openssl e2ee ed25519 encryption ssh crypto giftofencryption malware opsec keepass torsocks nsa protonvpn yubikey nitrokey openpgp castor9 gpgtools gpg cybersecurity CryptoWars signal noscript np trust openvpn datasecurity tracking cloudflare</description>
|
<description>encrypt password history cryptography solarwinds infosec gchq IHaveSomethingToHide IronySec cryptowars supplychainattacks UseAMaskUseTor cyberattack security tor e2e bruceschneier vpn openssh openssl e2ee ed25519 encryption ssh crypto giftofencryption malware opsec keepass torsocks nsa protonvpn yubikey nitrokey openpgp castor9 gpgtools gpg cybersecurity CryptoWars signal noscript np trust openvpn datasecurity tracking cloudflare</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>gardening</title>
|
<title>gardening</title>
|
||||||
<description>sporespondence blockade inde federation deno cabbage bundeswehr onions datenschleuder florespondence thyme DailyFlowers permaculture papuamerdeka flowers gardening de federated fahrräder golden genderQuiz</description>
|
<description>sporespondence blockade inde independant deno cabbage bundeswehr onions bordeaux datenschleuder florespondence thyme DailyFlowers permaculture papuamerdeka flowers gardening de federated devilslettuce fahrräder golden</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>conferences</title>
|
<title>conferences</title>
|
||||||
<description>debconf talk fossdem FreedomBoxSummit schmoocon summit confidenceTricks minidebconf emacsconf ox defcon flossevent conf rC3 conference flossconf apconf C3 config</description>
|
<description>debconf talk fossdem FreedomBoxSummit schmoocon summit confidenceTricks minidebconf emacsconf ox defcon flossevent conf rC3 conference flossconf apconf C3 config</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>cats</title>
|
<title>cats</title>
|
||||||
<description>Cat dailycatpic DailyCatVid Leopard</description>
|
<description>Cat dailycatpic dxp DailyCatVid CatsOfMastodon Leopard catbellies</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>employment</title>
|
<title>employment</title>
|
||||||
<description>InterviewQuestions reproductivework bullshitjobs antiwork worklog hire hirefedi work letthenetwork jobs</description>
|
<description>InterviewQuestions reproductivework bullshitjobs antiwork worklog hire hirefedi work letthenetwork jobs</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>linux</title>
|
<title>linux</title>
|
||||||
<description>osdev linuxisnotanos elementaryos cli kde Debian11 slackware mobian openwrt distros DebianBullseye shareyourdesktop wireguard linuxaudio gtk debian trisquel gnome linuxposting showyourdesktop ubuntu xubuntu unix fedora centos gentoo usergroup systemd linuxgaming Debian distro destinationlinux qubesos i3wm haiku linuxisnotaplatform linux netbsd termux btrfs reproduciblebuilds artix archlinux rhel debianinstaller linuxisajoke</description>
|
<description>osdev linuxisnotanos elementaryos cli kde Debian11 slackware mobian openwrt distros DebianBullseye shareyourdesktop wireguard linuxaudio gtk debian trisquel gnome linuxposting showyourdesktop ubuntu xubuntu unix fedora centos gentoo usergroup systemd linuxgaming Debian distro destinationlinux qubesos i3wm haiku linuxisnotaplatform linux netbsd termux btrfs reproduciblebuilds artix gtk4 archlinux rhel debianinstaller linuxisajoke</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>photos</title>
|
<title>photos</title>
|
||||||
<description>nikon photography photo tokyocameraclub photos photoshop camera picture streetphotography</description>
|
<description>nikon photography photo tokyocameraclub photos photoshop camera picture streetphotography</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>crafts</title>
|
||||||
|
<description>topic_imadethis upholstery hackerspaces sanding sundiy knitting hack biohacking wip jewelry diy upcycling woodworking origami makers quilting quilt 3dmodel woodwork ceramics</description>
|
||||||
|
<link/>
|
||||||
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>pets</title>
|
<title>pets</title>
|
||||||
<description>catpics catalunya catofmastodon mastodogs catbehaviour dogsofmastodon gentrification cats kittens pet dog caturday catsofmastodon cute dogs mastocats cat catcontent</description>
|
<description>catpics catalunya catofmastodon mastodogs catbehaviour dogsofmastodon gentrification cats kittens pet dog caturday catsofmastodon cute dogs mastocats cat catcontent</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>news</title>
|
<title>news</title>
|
||||||
<description>news newsletter doubledownnews journalism SkyNews</description>
|
<description>news Wikileaks newsletter doubledownnews journalism SkyNews</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>games</title>
|
<title>games</title>
|
||||||
<description>minecraft tetris99 TerraNil boardgames gamedesign chess nintendoswitch mud game 0ad ttrpg gamedev guildwars2 TetrisGore gaming Gamesphere rpg tetris dosgaming DnD cyber2077 cyberpunk2077 FreeNukum neopets minetest guildwars dnd</description>
|
<description>minecraft tetris99 TerraNil boardgames gamedesign chess nintendoswitch mud game 0ad ttrpg gamedev guildwars2 TetrisGore gaming Gamesphere rpg tetris dosgaming DnD cyber2077 cyberpunk2077 FreeNukum neopets minetest guildwars dnd</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>retro</title>
|
<title>retro</title>
|
||||||
<description>A500 atarist commodore teletext floppy 8bit atari trs80 floppydisk retrocomputing C64 plan9 80s microcomputing omm retrogaming z80 8bitdo retro amiga bbcmicro microcomputer bbsing</description>
|
<description>A500 atarist commodore teletext floppy 8bit atari trs80 floppydisk retrocomputing C64 plan9 80s microcomputing omm retrogaming z80 8bitdo retro amiga bbcmicro microcomputer bbsing</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>indymedia</title>
|
<title>indymedia</title>
|
||||||
<description>visionontv indymediaback pga indymedia omn tv 4opens</description>
|
<description>visionontv indymediaback pga indymedia omn tv 4opens openmedianetwork</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>activism</title>
|
<title>activism</title>
|
||||||
<description>protestor g20 riseup sflc DanniVive reuse fsfe softwarefreedom ann activist xr directaction eff openrightsgroup protest actiondirecte kroymann HS2 ngo MarcWittmann fsf StopHS2 grassroots BLM changeisinyourhands conservancy XR freeolabini announcement isolateByoblu annieleonard</description>
|
<description>protestor grassroot g20 riseup sflc DanniVive reuse fsfe softwarefreedom ann activist xr directaction eff openrightsgroup protest actiondirecte kroymann HS2 ngo MarcWittmann fsf StopHS2 grassroots BLM changeisinyourhands conservancy JefferySaunders XR freeolabini announcement isolateByoblu annieleonard</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>questions</title>
|
<title>questions</title>
|
||||||
<description>askmastodon askfedi question askmasto askfediverse ask askfosstodon</description>
|
<description>askmastodon askfedi question askmasto askfediverse ask askfosstodon</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>internet</title>
|
<title>internet</title>
|
||||||
<description>spam firefox redecentralize wikipedia rtmp decentralization decentralize w3c torrent data sitejs internetarchaeology WordPress self router dataretention selfhosting icann discourse PeerToPeer dns openstandards nojs oauth CDNsAreEvil protonmail standards yourdataisyourdata gemini SmallWeb xmpp semanticweb socialnetwork ntp socialnetworks proton icmp videocast jabber decentralized wiki ssb darknet cookies darkweb netcat server browser cloudy p2p social www ilovewikipedia web WebsiteStatus twitch socialmedia domain rss ipns mozilla voicemail mail ipfs browsers decentralizeit openculture cyberspace offthegrid cloud internet decentralisation internetarchive js dark openweb onlineharms dot internetshutdowns fixtheweb socialweb</description>
|
<description>spam firefox redecentralize wikipedia rtmp decentralization decentralize w3c torrent data sitejs internetarchaeology WordPress self router dataretention selfhosting icann discourse PeerToPeer dns openstandards nojs oauth CDNsAreEvil protonmail standards yourdataisyourdata gemini SmallWeb xmpp semanticweb socialnetwork ntp socialnetworks proton icmp videocast jabber decentralized wiki ssb darknet cookies darkweb netcat server browser cloudy p2p social www ilovewikipedia web WebsiteStatus twitch socialmedia domain rss ipns mozilla voicemail mail ipfs browsers decentralizeit openculture cyberspace offthegrid cloud internet decentralisation internetarchive js dark openweb onlineharms dot ftp internetshutdowns fixtheweb socialweb</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>birds</title>
|
<title>birds</title>
|
||||||
<description>RainbowBeeEater bird thunderbird</description>
|
<description>RainbowBeeEater bird</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>ethics</title>
|
<title>ethics</title>
|
||||||
<description>digitalethics ethics ethicallicense ethical</description>
|
<description>digitalethics ethics ethicallicense ethical</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>economics</title>
|
<title>economics</title>
|
||||||
<description>theWorkshop WealthConcentration funding shop startups HenryGeorge crowdfunding micropatronage monetize smallbusiness GitPay</description>
|
<description>theWorkshop WealthConcentration funding shop startups HenryGeorge crowdfunding micropatronage monetize smallbusiness GitPay</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>podcasts</title>
|
<title>podcasts</title>
|
||||||
<description>podcasting IntergalacticWasabiHour podcast tilde til tilderadio podcasts smallisbeautiful tilvids</description>
|
<description>podcasting IntergalacticWasabiHour podcast tilde til tilderadio podcasts smallisbeautiful tilvids</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>years</title>
|
<title>years</title>
|
||||||
<description>Year2020 year 1yrago 5yrsago</description>
|
<description>Year2020 year 1yrago 5yrsago</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>identity</title>
|
<title>identity</title>
|
||||||
<description>boomer</description>
|
<description>boomer</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>political</title>
|
<title>political</title>
|
||||||
<description>copservation linguisticProgramming</description>
|
<description>copservation linguisticProgramming</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>fashion</title>
|
<title>fashion</title>
|
||||||
<description>brasil fashionistas fashionesta bras fashion patches</description>
|
<description>brasil fashionistas fashionesta bras fashion patches</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>art</title>
|
<title>art</title>
|
||||||
<description>urban artvsartist2020 watercolor autisticartist barrigòtic art krita urbanart queerart deviantart adultcolouring collage streetart coverart MastoArt painting digitalart comic artwork mandala xkcd santa mastoart gnuimagemanipulationprogram webcomic furryart sticker TattoosOfTheFediverse artvsartist artist meme concretepoetry artwithopensource blackartist zine JuliaHartleyBrewer</description>
|
<description>urban glassart artvsartist2020 watercolor autisticartist barrigòtic art krita urbanart queerart deviantart adultcolouring collage streetart coverart MastoArt culture polArt ink painting digitalart comic artwork mandala xkcd comics santa mastoart gnuimagemanipulationprogram wireart cartoon webcomic furryart sticker artbreeder arttherapy TattoosOfTheFediverse artvsartist sculpture artist meme concretepoetry artwithopensource peppertop blackartist zine furry JuliaHartleyBrewer</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>month</title>
|
<title>month</title>
|
||||||
<description>april july march october november august june december september may feburary january month</description>
|
<description>april july march october november august june december september may feburary january month</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>funding</title>
|
<title>funding</title>
|
||||||
<description>disabledcrowdfund patreon</description>
|
<description>disabledcrowdfund patreon</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>books</title>
|
||||||
|
<description>justhollythings earthsea ebooks book amreading bookreview theLibrary wayfarers books ebook epub cookbook</description>
|
||||||
|
<link/>
|
||||||
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>comedy</title>
|
<title>comedy</title>
|
||||||
<description>laugh humour satire irony standup funny humor</description>
|
<description>laugh humour satire irony standup funny humor</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>crafts</title>
|
|
||||||
<description>upholstery hackerspaces sanding sundiy knitting hack biohacking wip jewelry diy upcycling woodworking origami makers quilting quilt 3dmodel woodwork</description>
|
|
||||||
<link/>
|
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>health</title>
|
<title>health</title>
|
||||||
<description>medical burnout medicine treatment EmotionalFirstAid autistic health meds</description>
|
<description>medical burnout cannabis medicine treatment EmotionalFirstAid maryjane autistic health meds marijuana</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>seasons</title>
|
<title>seasons</title>
|
||||||
<description>spring autumn winter summer</description>
|
<description>spring autumn winter summer solstice wintersolstice</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>gender</title>
|
<title>gender</title>
|
||||||
<description>transwomen transcrowdfund female trans women estradiol queer woman transrights</description>
|
<description>transwomen transcrowdfund female trans women estradiol queer genderQuiz woman transrights</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>fiction</title>
|
<title>fiction</title>
|
||||||
<description>cyberpunk thehobbit fiction</description>
|
<description>cyberpunk thehobbit fiction microfiction</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>legal</title>
|
<title>legal</title>
|
||||||
<description>hfgkarlsruhe amro GameSphere OnlineHarmsBill laipower gdpr intros Anticritique learning energyflow digitalservicesact geekproblem</description>
|
<description>hfgkarlsruhe amro GameSphere OnlineHarmsBill laipower gdpr intros Anticritique learning energyflow digitalservicesact geekproblem</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>introductions</title>
|
<title>introductions</title>
|
||||||
<description>newhere introductions</description>
|
<description>newhere firsttoot Introduction Introductions introduction introductions</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>audio</title>
|
<title>audio</title>
|
||||||
<description>audioproduction audiofeedback audio</description>
|
<description>audioproduction audiofeedback audio</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>bots</title>
|
<title>bots</title>
|
||||||
<description>bot</description>
|
<description>bot</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>climate</title>
|
<title>climate</title>
|
||||||
<description>clouds renewableenergy amp climateemergency climate coal globalwarming weather climatecamp sky climatescience climatecrisis</description>
|
<description>clouds renewableenergy amp climateemergency climate coal globalwarming weather climatecamp science sky climatescience climatecrisis</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>nature</title>
|
<title>nature</title>
|
||||||
<description>morning trees light birds nature frogs sunrise inaturalist morningcrew australianwildlife capybara amphibians</description>
|
<description>morning trees light birds nature frogs sunrise inaturalist morningcrew australianwildlife capybara amphibians</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>books</title>
|
|
||||||
<description>earthsea ebooks book amreading bookreview theLibrary wayfarers books ebook epub cookbook</description>
|
|
||||||
<link/>
|
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>scifi</title>
|
<title>scifi</title>
|
||||||
<description>startrek starwars babylon5</description>
|
<description>startrek starwars babylon5</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>religion</title>
|
<title>religion</title>
|
||||||
<description>pagan</description>
|
<description>neopagan pagan</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>media</title>
|
<title>media</title>
|
||||||
<description>mainstreaming weAreAllCrazy theatlantic traditionalmedia videos railroads ai realmedia</description>
|
<description>mainstreaming stream streaming weAreAllCrazy maiabeyrouti theatlantic traditionalmedia videos railroads taina ai realmedia media</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>techbros</title>
|
<title>techbros</title>
|
||||||
<description>hackernews red reddit</description>
|
<description>hackernews red reddit</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>photography</title>
|
<title>photography</title>
|
||||||
<description>landscapephotography</description>
|
<description>landscapephotography</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>moderation</title>
|
<title>moderation</title>
|
||||||
<description>fedblock</description>
|
<description>fedblock</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>languages</title>
|
<title>languages</title>
|
||||||
<description>lojban</description>
|
<description>lojban</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>election</title>
|
<title>election</title>
|
||||||
<description>voted vote</description>
|
<description>voted vote</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>#music</title>
|
<title>#music</title>
|
||||||
<description>trance</description>
|
<description>trance</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>facts</title>
|
||||||
|
<description>didyouknow</description>
|
||||||
|
<link/>
|
||||||
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>radio</title>
|
<title>radio</title>
|
||||||
<description>radiohost vantascape vantaradio ca radio healthcare hamradio FreeAllPoliticalPrisoners radiobroadcasting 3dcad radioshow california listeningtonow radiobroadcast spazradio anonradio dmca</description>
|
<description>radiohost vantascape vantaradio ca radio healthcare hamradio FreeAllPoliticalPrisoners card10 radiobroadcasting 3dcad radioshow local california listeningtonow radiobroadcast spazradio anonradio dmca</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>licenses</title>
|
<title>licenses</title>
|
||||||
<description>creative creativecommons</description>
|
<description>copyright creative creativecommons</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>education</title>
|
<title>education</title>
|
||||||
<description>education teach tutorial</description>
|
<description>education teach tutorial</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>microcontroller</title>
|
<title>microcontroller</title>
|
||||||
<description>microcontroller arduino</description>
|
<description>microcontroller arduino</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>scotland</title>
|
<title>scotland</title>
|
||||||
<description>glasgow highlands edinburgh loch</description>
|
<description>glasgow highlands edinburgh loch</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>astronomy</title>
|
<title>astronomy</title>
|
||||||
<description>space jupiter moon saturn milkyway</description>
|
<description>space jupiter moon saturn milkyway</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>entertainment</title>
|
<title>entertainment</title>
|
||||||
<description>watching Thundercat entertainment themandalorian</description>
|
<description>watching Thundercat thisisthetypeofmemethatilikecauseitcontainsreptiles entertainment me meow themandalorian</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>privacy</title>
|
<title>privacy</title>
|
||||||
<description>privacymatters surveillance dataprivacy privacy</description>
|
<description>privacymatters surveillance dataprivacy privacy</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>environment</title>
|
<title>environment</title>
|
||||||
<description>climatechange climatechaos</description>
|
<description>climatechange climatechaos</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>#software</title>
|
<title>#software</title>
|
||||||
<description>flatpak</description>
|
<description>flatpak</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>microcontrollers</title>
|
<title>microcontrollers</title>
|
||||||
<description>esp8266 esp32</description>
|
<description>esp8266 esp32</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>people</title>
|
|
||||||
<description>introduction</description>
|
|
||||||
<link/>
|
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>help</title>
|
<title>help</title>
|
||||||
<description>helpful help</description>
|
<description>helpful help</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>war</title>
|
<title>war</title>
|
||||||
<description>weapons</description>
|
<description>weapons</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>philosophy</title>
|
<title>philosophy</title>
|
||||||
<description>stoic postmodernism</description>
|
<description>stoic postmodernism</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>france</title>
|
<title>france</title>
|
||||||
<description>Macronavirus</description>
|
<description>Macronavirus</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>travel</title>
|
<title>travel</title>
|
||||||
<description>travel taxi</description>
|
<description>travel taxi</description>
|
||||||
<link/>
|
<link/>
|
||||||
<pubDate>Sun, 20 Dec 2020 20:57:26 UT</pubDate>
|
<pubDate>Tue, 22 Dec 2020 09:45:58 UT</pubDate>
|
||||||
</item>
|
</item>
|
||||||
</channel>
|
</channel>
|
||||||
</rss>
|
</rss>
|
||||||
|
|
|
||||||
111
delete.py
111
delete.py
|
|
@ -10,82 +10,17 @@ import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from utils import getFullDomain
|
from utils import getFullDomain
|
||||||
from utils import removeIdEnding
|
from utils import removeIdEnding
|
||||||
from utils import getStatusNumber
|
|
||||||
from utils import urlPermitted
|
|
||||||
from utils import getNicknameFromActor
|
from utils import getNicknameFromActor
|
||||||
from utils import getDomainFromActor
|
from utils import getDomainFromActor
|
||||||
from utils import locatePost
|
from utils import locatePost
|
||||||
from utils import deletePost
|
from utils import deletePost
|
||||||
from utils import removeModerationPostFromIndex
|
from utils import removeModerationPostFromIndex
|
||||||
from posts import sendSignedJson
|
|
||||||
from session import postJson
|
from session import postJson
|
||||||
from webfinger import webfingerHandle
|
from webfinger import webfingerHandle
|
||||||
from auth import createBasicAuthHeader
|
from auth import createBasicAuthHeader
|
||||||
from posts import getPersonBox
|
from posts import getPersonBox
|
||||||
|
|
||||||
|
|
||||||
def createDelete(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int,
|
|
||||||
toUrl: str, ccUrl: str, httpPrefix: str,
|
|
||||||
objectUrl: str, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool) -> {}:
|
|
||||||
"""Creates a delete message
|
|
||||||
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
|
|
||||||
and ccUrl might be a specific person whose post is to be deleted
|
|
||||||
objectUrl is typically the url of the message, corresponding to url
|
|
||||||
or atomUri in createPostBase
|
|
||||||
"""
|
|
||||||
if not urlPermitted(objectUrl, federationList):
|
|
||||||
return None
|
|
||||||
|
|
||||||
if ':' in domain:
|
|
||||||
domain = domain.split(':')[0]
|
|
||||||
fullDomain = domain
|
|
||||||
fullDomain = getFullDomain(domain, port)
|
|
||||||
|
|
||||||
statusNumber, published = getStatusNumber()
|
|
||||||
newDeleteId = \
|
|
||||||
httpPrefix + '://' + fullDomain + '/users/' + \
|
|
||||||
nickname + '/statuses/' + statusNumber
|
|
||||||
newDelete = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
|
|
||||||
'atomUri': newDeleteId,
|
|
||||||
'cc': [],
|
|
||||||
'id': newDeleteId + '/activity',
|
|
||||||
'object': objectUrl,
|
|
||||||
'published': published,
|
|
||||||
'to': [toUrl],
|
|
||||||
'type': 'Delete'
|
|
||||||
}
|
|
||||||
if ccUrl:
|
|
||||||
if len(ccUrl) > 0:
|
|
||||||
newDelete['cc'] = [ccUrl]
|
|
||||||
|
|
||||||
deleteNickname = None
|
|
||||||
deleteDomain = None
|
|
||||||
deletePort = None
|
|
||||||
if '/users/' in objectUrl or \
|
|
||||||
'/accounts/' in objectUrl or \
|
|
||||||
'/channel/' in objectUrl or \
|
|
||||||
'/profile/' in objectUrl:
|
|
||||||
deleteNickname = getNicknameFromActor(objectUrl)
|
|
||||||
deleteDomain, deletePort = getDomainFromActor(objectUrl)
|
|
||||||
|
|
||||||
if deleteNickname and deleteDomain:
|
|
||||||
sendSignedJson(newDelete, session, baseDir,
|
|
||||||
nickname, domain, port,
|
|
||||||
deleteNickname, deleteDomain, deletePort,
|
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
|
||||||
httpPrefix, True, clientToServer, federationList,
|
|
||||||
sendThreads, postLog, cachedWebfingers,
|
|
||||||
personCache, debug)
|
|
||||||
|
|
||||||
return newDelete
|
|
||||||
|
|
||||||
|
|
||||||
def sendDeleteViaServer(baseDir: str, session,
|
def sendDeleteViaServer(baseDir: str, session,
|
||||||
fromNickname: str, password: str,
|
fromNickname: str, password: str,
|
||||||
fromDomain: str, fromPort: int,
|
fromDomain: str, fromPort: int,
|
||||||
|
|
@ -167,52 +102,6 @@ def sendDeleteViaServer(baseDir: str, session,
|
||||||
return newDeleteJson
|
return newDeleteJson
|
||||||
|
|
||||||
|
|
||||||
def deletePublic(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
|
||||||
objectUrl: str, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool) -> {}:
|
|
||||||
"""Makes a public delete activity
|
|
||||||
"""
|
|
||||||
fromDomain = getFullDomain(domain, port)
|
|
||||||
|
|
||||||
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,
|
|
||||||
objectUrl, clientToServer,
|
|
||||||
sendThreads, postLog,
|
|
||||||
personCache, cachedWebfingers,
|
|
||||||
debug)
|
|
||||||
|
|
||||||
|
|
||||||
def deletePostPub(session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
|
||||||
deleteNickname: str, deleteDomain: str,
|
|
||||||
deletePort: int, deleteHttpsPrefix: str,
|
|
||||||
deleteStatusNumber: int, clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool) -> {}:
|
|
||||||
"""Deletes a given status post
|
|
||||||
"""
|
|
||||||
deletedDomain = getFullDomain(deleteDomain, deletePort)
|
|
||||||
|
|
||||||
objectUrl = \
|
|
||||||
deleteHttpsPrefix + '://' + deletedDomain + '/users/' + \
|
|
||||||
deleteNickname + '/statuses/' + str(deleteStatusNumber)
|
|
||||||
|
|
||||||
return deletePublic(session, baseDir, federationList,
|
|
||||||
nickname, domain, port, httpPrefix,
|
|
||||||
objectUrl, clientToServer,
|
|
||||||
sendThreads, postLog,
|
|
||||||
personCache, cachedWebfingers,
|
|
||||||
debug)
|
|
||||||
|
|
||||||
|
|
||||||
def outboxDelete(baseDir: str, httpPrefix: str,
|
def outboxDelete(baseDir: str, httpPrefix: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
messageJson: {}, debug: bool,
|
messageJson: {}, debug: bool,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ __email__ = "bob@freedombone.net"
|
||||||
__status__ = "Production"
|
__status__ = "Production"
|
||||||
|
|
||||||
|
|
||||||
def getDonationTypes() -> str:
|
def _getDonationTypes() -> str:
|
||||||
return ('patreon', 'paypal', 'gofundme', 'liberapay',
|
return ('patreon', 'paypal', 'gofundme', 'liberapay',
|
||||||
'kickstarter', 'indiegogo', 'crowdsupply',
|
'kickstarter', 'indiegogo', 'crowdsupply',
|
||||||
'subscribestar')
|
'subscribestar')
|
||||||
|
|
@ -18,7 +18,7 @@ def getDonationUrl(actorJson: {}) -> str:
|
||||||
"""
|
"""
|
||||||
if not actorJson.get('attachment'):
|
if not actorJson.get('attachment'):
|
||||||
return ''
|
return ''
|
||||||
donationType = getDonationTypes()
|
donationType = _getDonationTypes()
|
||||||
for propertyValue in actorJson['attachment']:
|
for propertyValue in actorJson['attachment']:
|
||||||
if not propertyValue.get('name'):
|
if not propertyValue.get('name'):
|
||||||
continue
|
continue
|
||||||
|
|
@ -54,7 +54,7 @@ def setDonationUrl(actorJson: {}, donateUrl: str) -> None:
|
||||||
if not actorJson.get('attachment'):
|
if not actorJson.get('attachment'):
|
||||||
actorJson['attachment'] = []
|
actorJson['attachment'] = []
|
||||||
|
|
||||||
donationType = getDonationTypes()
|
donationType = _getDonationTypes()
|
||||||
donateName = None
|
donateName = None
|
||||||
for paymentService in donationType:
|
for paymentService in donationType:
|
||||||
if paymentService in donateUrl:
|
if paymentService in donateUrl:
|
||||||
|
|
|
||||||
79
epicyon.py
79
epicyon.py
|
|
@ -1124,7 +1124,7 @@ if args.undoItemName:
|
||||||
cachedWebfingers = {}
|
cachedWebfingers = {}
|
||||||
print('Sending undo of shared item: ' + args.undoItemName)
|
print('Sending undo of shared item: ' + args.undoItemName)
|
||||||
|
|
||||||
sendUndoShareViaServer(session,
|
sendUndoShareViaServer(baseDir, session,
|
||||||
args.nickname, args.password,
|
args.nickname, args.password,
|
||||||
domain, port,
|
domain, port,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
|
|
@ -1931,33 +1931,88 @@ if args.testdata:
|
||||||
|
|
||||||
deleteAllPosts(baseDir, nickname, domain, 'inbox')
|
deleteAllPosts(baseDir, nickname, domain, 'inbox')
|
||||||
deleteAllPosts(baseDir, nickname, domain, 'outbox')
|
deleteAllPosts(baseDir, nickname, domain, 'outbox')
|
||||||
|
|
||||||
|
testFollowersOnly = False
|
||||||
|
testSaveToFile = True
|
||||||
|
testClientToServer = False
|
||||||
|
testCommentsEnabled = True
|
||||||
|
testAttachImageFilename = None
|
||||||
|
testMediaType = None
|
||||||
|
testImageDescription = None
|
||||||
|
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"like, this is totally just a #test, man",
|
"like this is totally just a #test man",
|
||||||
False, True, False, True, None, None, useBlurhash)
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType, testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"Zoiks!!!",
|
"Zoiks!!!",
|
||||||
False, True, False, True, None, None, useBlurhash)
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType, testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"Hey scoob we need like a hundred more #milkshakes",
|
"Hey scoob we need like a hundred more #milkshakes",
|
||||||
False, True, False, True, None, None, useBlurhash)
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType, testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"Getting kinda spooky around here",
|
"Getting kinda spooky around here",
|
||||||
False, True, False, True, None, None,
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType, testImageDescription,
|
||||||
useBlurhash, 'someone')
|
useBlurhash, 'someone')
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"And they would have gotten away with it too" +
|
"And they would have gotten away with it too" +
|
||||||
"if it wasn't for those pesky hackers",
|
"if it wasn't for those pesky hackers",
|
||||||
False, True, False, True, 'img/logo.png',
|
testFollowersOnly,
|
||||||
'Description of image', useBlurhash)
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
'img/logo.png', 'image/png',
|
||||||
|
'Description of image',
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"man, these centralized sites are, like, the worst!",
|
"man these centralized sites are like the worst!",
|
||||||
False, True, False, True, None, None, useBlurhash)
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType, testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"another mystery solved #test",
|
"another mystery solved #test",
|
||||||
False, True, False, True, None, None, useBlurhash)
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType, testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
createPublicPost(baseDir, nickname, domain, port, httpPrefix,
|
||||||
"let's go bowling",
|
"let's go bowling",
|
||||||
False, True, False, True, None, None, useBlurhash)
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
testClientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType, testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
|
|
||||||
domainFull = domain + ':' + str(port)
|
domainFull = domain + ':' + str(port)
|
||||||
clearFollows(baseDir, nickname, domain)
|
clearFollows(baseDir, nickname, domain)
|
||||||
|
|
|
||||||
10
filters.py
10
filters.py
|
|
@ -79,7 +79,7 @@ def removeGlobalFilter(baseDir: str, words: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def isTwitterPost(content: str) -> bool:
|
def _isTwitterPost(content: str) -> bool:
|
||||||
"""Returns true if the given post content is a retweet or twitter crosspost
|
"""Returns true if the given post content is a retweet or twitter crosspost
|
||||||
"""
|
"""
|
||||||
if '/twitter.' in content or '@twitter.' in content:
|
if '/twitter.' in content or '@twitter.' in content:
|
||||||
|
|
@ -89,7 +89,7 @@ def isTwitterPost(content: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def isFilteredBase(filename: str, content: str) -> bool:
|
def _isFilteredBase(filename: str, content: str) -> bool:
|
||||||
"""Uses the given file containing filtered words to check
|
"""Uses the given file containing filtered words to check
|
||||||
the given content
|
the given content
|
||||||
"""
|
"""
|
||||||
|
|
@ -122,7 +122,7 @@ def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool:
|
||||||
words must be present although not necessarily adjacent
|
words must be present although not necessarily adjacent
|
||||||
"""
|
"""
|
||||||
globalFiltersFilename = baseDir + '/accounts/filters.txt'
|
globalFiltersFilename = baseDir + '/accounts/filters.txt'
|
||||||
if isFilteredBase(globalFiltersFilename, content):
|
if _isFilteredBase(globalFiltersFilename, content):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not nickname or not domain:
|
if not nickname or not domain:
|
||||||
|
|
@ -132,9 +132,9 @@ def isFiltered(baseDir: str, nickname: str, domain: str, content: str) -> bool:
|
||||||
removeTwitter = baseDir + '/accounts/' + \
|
removeTwitter = baseDir + '/accounts/' + \
|
||||||
nickname + '@' + domain + '/.removeTwitter'
|
nickname + '@' + domain + '/.removeTwitter'
|
||||||
if os.path.isfile(removeTwitter):
|
if os.path.isfile(removeTwitter):
|
||||||
if isTwitterPost(content):
|
if _isTwitterPost(content):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
accountFiltersFilename = baseDir + '/accounts/' + \
|
accountFiltersFilename = baseDir + '/accounts/' + \
|
||||||
nickname + '@' + domain + '/filters.txt'
|
nickname + '@' + domain + '/filters.txt'
|
||||||
return isFilteredBase(accountFiltersFilename, content)
|
return _isFilteredBase(accountFiltersFilename, content)
|
||||||
|
|
|
||||||
54
follow.py
54
follow.py
|
|
@ -65,7 +65,7 @@ def createInitialLastSeen(baseDir: str, httpPrefix: str) -> None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def preApprovedFollower(baseDir: str,
|
def _preApprovedFollower(baseDir: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
approveHandle: str,
|
approveHandle: str,
|
||||||
allowNewsFollowers: bool) -> bool:
|
allowNewsFollowers: bool) -> bool:
|
||||||
|
|
@ -84,7 +84,7 @@ def preApprovedFollower(baseDir: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def removeFromFollowBase(baseDir: str,
|
def _removeFromFollowBase(baseDir: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
acceptOrDenyHandle: str, followFile: str,
|
acceptOrDenyHandle: str, followFile: str,
|
||||||
debug: bool) -> None:
|
debug: bool) -> None:
|
||||||
|
|
@ -114,16 +114,16 @@ def removeFromFollowRequests(baseDir: str,
|
||||||
denyHandle: str, debug: bool) -> None:
|
denyHandle: str, debug: bool) -> None:
|
||||||
"""Removes a handle from follow requests
|
"""Removes a handle from follow requests
|
||||||
"""
|
"""
|
||||||
removeFromFollowBase(baseDir, nickname, domain,
|
_removeFromFollowBase(baseDir, nickname, domain,
|
||||||
denyHandle, 'followrequests', debug)
|
denyHandle, 'followrequests', debug)
|
||||||
|
|
||||||
|
|
||||||
def removeFromFollowRejects(baseDir: str,
|
def _removeFromFollowRejects(baseDir: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
acceptHandle: str, debug: bool) -> None:
|
acceptHandle: str, debug: bool) -> None:
|
||||||
"""Removes a handle from follow rejects
|
"""Removes a handle from follow rejects
|
||||||
"""
|
"""
|
||||||
removeFromFollowBase(baseDir, nickname, domain,
|
_removeFromFollowBase(baseDir, nickname, domain,
|
||||||
acceptHandle, 'followrejects', debug)
|
acceptHandle, 'followrejects', debug)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ def followerOfPerson(baseDir: str, nickname: str, domain: str,
|
||||||
federationList, debug, 'followers.txt')
|
federationList, debug, 'followers.txt')
|
||||||
|
|
||||||
|
|
||||||
def isFollowerOfPerson(baseDir: str, nickname: str, domain: str,
|
def _isFollowerOfPerson(baseDir: str, nickname: str, domain: str,
|
||||||
followerNickname: str, followerDomain: str) -> bool:
|
followerNickname: str, followerDomain: str) -> bool:
|
||||||
"""is the given nickname a follower of followerNickname?
|
"""is the given nickname a follower of followerNickname?
|
||||||
"""
|
"""
|
||||||
|
|
@ -212,7 +212,7 @@ def isFollowerOfPerson(baseDir: str, nickname: str, domain: str,
|
||||||
return alreadyFollowing
|
return alreadyFollowing
|
||||||
|
|
||||||
|
|
||||||
def unfollowPerson(baseDir: str, nickname: str, domain: str,
|
def unfollowAccount(baseDir: str, nickname: str, domain: str,
|
||||||
followNickname: str, followDomain: str,
|
followNickname: str, followDomain: str,
|
||||||
followFile='following.txt',
|
followFile='following.txt',
|
||||||
debug=False) -> bool:
|
debug=False) -> bool:
|
||||||
|
|
@ -261,12 +261,12 @@ def unfollowPerson(baseDir: str, nickname: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def unfollowerOfPerson(baseDir: str, nickname: str, domain: str,
|
def unfollowerOfAccount(baseDir: str, nickname: str, domain: str,
|
||||||
followerNickname: str, followerDomain: str,
|
followerNickname: str, followerDomain: str,
|
||||||
debug=False) -> bool:
|
debug=False) -> bool:
|
||||||
"""Remove a follower of a person
|
"""Remove a follower of a person
|
||||||
"""
|
"""
|
||||||
return unfollowPerson(baseDir, nickname, domain,
|
return unfollowAccount(baseDir, nickname, domain,
|
||||||
followerNickname, followerDomain,
|
followerNickname, followerDomain,
|
||||||
'followers.txt', debug)
|
'followers.txt', debug)
|
||||||
|
|
||||||
|
|
@ -291,7 +291,7 @@ def clearFollowers(baseDir: str, nickname: str, domain: str) -> None:
|
||||||
clearFollows(baseDir, nickname, domain, 'followers.txt')
|
clearFollows(baseDir, nickname, domain, 'followers.txt')
|
||||||
|
|
||||||
|
|
||||||
def getNoOfFollows(baseDir: str, nickname: str, domain: str,
|
def _getNoOfFollows(baseDir: str, nickname: str, domain: str,
|
||||||
authenticated: bool,
|
authenticated: bool,
|
||||||
followFile='following.txt') -> int:
|
followFile='following.txt') -> int:
|
||||||
"""Returns the number of follows or followers
|
"""Returns the number of follows or followers
|
||||||
|
|
@ -324,11 +324,11 @@ def getNoOfFollows(baseDir: str, nickname: str, domain: str,
|
||||||
return ctr
|
return ctr
|
||||||
|
|
||||||
|
|
||||||
def getNoOfFollowers(baseDir: str,
|
def _getNoOfFollowers(baseDir: str,
|
||||||
nickname: str, domain: str, authenticated: bool) -> int:
|
nickname: str, domain: str, authenticated: bool) -> int:
|
||||||
"""Returns the number of followers of the given person
|
"""Returns the number of followers of the given person
|
||||||
"""
|
"""
|
||||||
return getNoOfFollows(baseDir, nickname, domain,
|
return _getNoOfFollows(baseDir, nickname, domain,
|
||||||
authenticated, 'followers.txt')
|
authenticated, 'followers.txt')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -382,7 +382,7 @@ def getFollowingFeed(baseDir: str, domain: str, port: int, path: str,
|
||||||
httpPrefix + '://' + domain + '/users/' + \
|
httpPrefix + '://' + domain + '/users/' + \
|
||||||
nickname + '/' + followFile
|
nickname + '/' + followFile
|
||||||
totalStr = \
|
totalStr = \
|
||||||
getNoOfFollows(baseDir, nickname, domain, authenticated)
|
_getNoOfFollows(baseDir, nickname, domain, authenticated)
|
||||||
following = {
|
following = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'first': firstStr,
|
'first': firstStr,
|
||||||
|
|
@ -463,14 +463,14 @@ def getFollowingFeed(baseDir: str, domain: str, port: int, path: str,
|
||||||
return following
|
return following
|
||||||
|
|
||||||
|
|
||||||
def followApprovalRequired(baseDir: str, nicknameToFollow: str,
|
def _followApprovalRequired(baseDir: str, nicknameToFollow: str,
|
||||||
domainToFollow: str, debug: bool,
|
domainToFollow: str, debug: bool,
|
||||||
followRequestHandle: str,
|
followRequestHandle: str,
|
||||||
allowNewsFollowers: bool) -> bool:
|
allowNewsFollowers: bool) -> bool:
|
||||||
""" Returns the policy for follower approvals
|
""" Returns the policy for follower approvals
|
||||||
"""
|
"""
|
||||||
# has this handle already been manually approved?
|
# has this handle already been manually approved?
|
||||||
if preApprovedFollower(baseDir, nicknameToFollow, domainToFollow,
|
if _preApprovedFollower(baseDir, nicknameToFollow, domainToFollow,
|
||||||
followRequestHandle, allowNewsFollowers):
|
followRequestHandle, allowNewsFollowers):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -494,7 +494,7 @@ def followApprovalRequired(baseDir: str, nicknameToFollow: str,
|
||||||
return manuallyApproveFollows
|
return manuallyApproveFollows
|
||||||
|
|
||||||
|
|
||||||
def noOfFollowRequests(baseDir: str,
|
def _noOfFollowRequests(baseDir: str,
|
||||||
nicknameToFollow: str, domainToFollow: str,
|
nicknameToFollow: str, domainToFollow: str,
|
||||||
nickname: str, domain: str, fromPort: int,
|
nickname: str, domain: str, fromPort: int,
|
||||||
followType: str) -> int:
|
followType: str) -> int:
|
||||||
|
|
@ -521,7 +521,7 @@ def noOfFollowRequests(baseDir: str,
|
||||||
return ctr
|
return ctr
|
||||||
|
|
||||||
|
|
||||||
def storeFollowRequest(baseDir: str,
|
def _storeFollowRequest(baseDir: str,
|
||||||
nicknameToFollow: str, domainToFollow: str, port: int,
|
nicknameToFollow: str, domainToFollow: str, port: int,
|
||||||
nickname: str, domain: str, fromPort: int,
|
nickname: str, domain: str, fromPort: int,
|
||||||
followJson: {},
|
followJson: {},
|
||||||
|
|
@ -668,7 +668,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
||||||
nicknameToFollow)
|
nicknameToFollow)
|
||||||
return True
|
return True
|
||||||
if maxFollowers > 0:
|
if maxFollowers > 0:
|
||||||
if getNoOfFollowers(baseDir,
|
if _getNoOfFollowers(baseDir,
|
||||||
nicknameToFollow, domainToFollow,
|
nicknameToFollow, domainToFollow,
|
||||||
True) > maxFollowers:
|
True) > maxFollowers:
|
||||||
print('WARN: ' + nicknameToFollow +
|
print('WARN: ' + nicknameToFollow +
|
||||||
|
|
@ -682,7 +682,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
||||||
baseDir + '/accounts/' + handleToFollow)
|
baseDir + '/accounts/' + handleToFollow)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if isFollowerOfPerson(baseDir,
|
if _isFollowerOfPerson(baseDir,
|
||||||
nicknameToFollow, domainToFollowFull,
|
nicknameToFollow, domainToFollowFull,
|
||||||
nickname, domainFull):
|
nickname, domainFull):
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -693,26 +693,26 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
||||||
|
|
||||||
# what is the followers policy?
|
# what is the followers policy?
|
||||||
approveHandle = nickname + '@' + domainFull
|
approveHandle = nickname + '@' + domainFull
|
||||||
if followApprovalRequired(baseDir, nicknameToFollow,
|
if _followApprovalRequired(baseDir, nicknameToFollow,
|
||||||
domainToFollow, debug, approveHandle,
|
domainToFollow, debug, approveHandle,
|
||||||
allowNewsFollowers):
|
allowNewsFollowers):
|
||||||
print('Follow approval is required')
|
print('Follow approval is required')
|
||||||
if domain.endswith('.onion'):
|
if domain.endswith('.onion'):
|
||||||
if noOfFollowRequests(baseDir,
|
if _noOfFollowRequests(baseDir,
|
||||||
nicknameToFollow, domainToFollow,
|
nicknameToFollow, domainToFollow,
|
||||||
nickname, domain, fromPort,
|
nickname, domain, fromPort,
|
||||||
'onion') > 5:
|
'onion') > 5:
|
||||||
print('Too many follow requests from onion addresses')
|
print('Too many follow requests from onion addresses')
|
||||||
return False
|
return False
|
||||||
elif domain.endswith('.i2p'):
|
elif domain.endswith('.i2p'):
|
||||||
if noOfFollowRequests(baseDir,
|
if _noOfFollowRequests(baseDir,
|
||||||
nicknameToFollow, domainToFollow,
|
nicknameToFollow, domainToFollow,
|
||||||
nickname, domain, fromPort,
|
nickname, domain, fromPort,
|
||||||
'i2p') > 5:
|
'i2p') > 5:
|
||||||
print('Too many follow requests from i2p addresses')
|
print('Too many follow requests from i2p addresses')
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if noOfFollowRequests(baseDir,
|
if _noOfFollowRequests(baseDir,
|
||||||
nicknameToFollow, domainToFollow,
|
nicknameToFollow, domainToFollow,
|
||||||
nickname, domain, fromPort,
|
nickname, domain, fromPort,
|
||||||
'') > 10:
|
'') > 10:
|
||||||
|
|
@ -720,7 +720,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print('Storing follow request for approval')
|
print('Storing follow request for approval')
|
||||||
return storeFollowRequest(baseDir,
|
return _storeFollowRequest(baseDir,
|
||||||
nicknameToFollow, domainToFollow, port,
|
nicknameToFollow, domainToFollow, port,
|
||||||
nickname, domain, fromPort,
|
nickname, domain, fromPort,
|
||||||
messageJson, debug, messageJson['actor'])
|
messageJson, debug, messageJson['actor'])
|
||||||
|
|
@ -920,13 +920,13 @@ def sendFollowRequest(session, baseDir: str,
|
||||||
'object': followedId
|
'object': followedId
|
||||||
}
|
}
|
||||||
|
|
||||||
if followApprovalRequired(baseDir, nickname, domain, debug,
|
if _followApprovalRequired(baseDir, nickname, domain, debug,
|
||||||
followHandle, allowNewsFollowers):
|
followHandle, allowNewsFollowers):
|
||||||
# Remove any follow requests rejected for the account being followed.
|
# Remove any follow requests rejected for the account being followed.
|
||||||
# It's assumed that if you are following someone then you are
|
# It's assumed that if you are following someone then you are
|
||||||
# ok with them following back. If this isn't the case then a rejected
|
# ok with them following back. If this isn't the case then a rejected
|
||||||
# follow request will block them again.
|
# follow request will block them again.
|
||||||
removeFromFollowRejects(baseDir,
|
_removeFromFollowRejects(baseDir,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
followHandle, debug)
|
followHandle, debug)
|
||||||
|
|
||||||
|
|
@ -1208,7 +1208,7 @@ def outboxUndoFollow(baseDir: str, messageJson: {}, debug: bool) -> None:
|
||||||
getDomainFromActor(messageJson['object']['object'])
|
getDomainFromActor(messageJson['object']['object'])
|
||||||
domainFollowingFull = getFullDomain(domainFollowing, portFollowing)
|
domainFollowingFull = getFullDomain(domainFollowing, portFollowing)
|
||||||
|
|
||||||
if unfollowPerson(baseDir, nicknameFollower, domainFollowerFull,
|
if unfollowAccount(baseDir, nicknameFollower, domainFollowerFull,
|
||||||
nicknameFollowing, domainFollowingFull):
|
nicknameFollowing, domainFollowingFull):
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: ' + nicknameFollower + ' unfollowed ' +
|
print('DEBUG: ' + nicknameFollower + ' unfollowed ' +
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ def receivingCalendarEvents(baseDir: str, nickname: str, domain: str,
|
||||||
return handle + '\n' in open(calendarFilename).read()
|
return handle + '\n' in open(calendarFilename).read()
|
||||||
|
|
||||||
|
|
||||||
def receiveCalendarEvents(baseDir: str, nickname: str, domain: str,
|
def _receiveCalendarEvents(baseDir: str, nickname: str, domain: str,
|
||||||
followingNickname: str,
|
followingNickname: str,
|
||||||
followingDomain: str,
|
followingDomain: str,
|
||||||
add: bool) -> None:
|
add: bool) -> None:
|
||||||
|
|
@ -100,12 +100,12 @@ def receiveCalendarEvents(baseDir: str, nickname: str, domain: str,
|
||||||
def addPersonToCalendar(baseDir: str, nickname: str, domain: str,
|
def addPersonToCalendar(baseDir: str, nickname: str, domain: str,
|
||||||
followingNickname: str,
|
followingNickname: str,
|
||||||
followingDomain: str) -> None:
|
followingDomain: str) -> None:
|
||||||
receiveCalendarEvents(baseDir, nickname, domain,
|
_receiveCalendarEvents(baseDir, nickname, domain,
|
||||||
followingNickname, followingDomain, True)
|
followingNickname, followingDomain, True)
|
||||||
|
|
||||||
|
|
||||||
def removePersonFromCalendar(baseDir: str, nickname: str, domain: str,
|
def removePersonFromCalendar(baseDir: str, nickname: str, domain: str,
|
||||||
followingNickname: str,
|
followingNickname: str,
|
||||||
followingDomain: str) -> None:
|
followingDomain: str) -> None:
|
||||||
receiveCalendarEvents(baseDir, nickname, domain,
|
_receiveCalendarEvents(baseDir, nickname, domain,
|
||||||
followingNickname, followingDomain, False)
|
followingNickname, followingDomain, False)
|
||||||
|
|
|
||||||
24
git.py
24
git.py
|
|
@ -10,7 +10,7 @@ import os
|
||||||
import html
|
import html
|
||||||
|
|
||||||
|
|
||||||
def gitFormatContent(content: str) -> str:
|
def _gitFormatContent(content: str) -> str:
|
||||||
""" replace html formatting, so that it's more
|
""" replace html formatting, so that it's more
|
||||||
like the original patch file
|
like the original patch file
|
||||||
"""
|
"""
|
||||||
|
|
@ -22,7 +22,7 @@ def gitFormatContent(content: str) -> str:
|
||||||
return patchStr
|
return patchStr
|
||||||
|
|
||||||
|
|
||||||
def getGitProjectName(baseDir: str, nickname: str, domain: str,
|
def _getGitProjectName(baseDir: str, nickname: str, domain: str,
|
||||||
subject: str) -> str:
|
subject: str) -> str:
|
||||||
"""Returns the project name for a git patch
|
"""Returns the project name for a git patch
|
||||||
The project name should be contained within the subject line
|
The project name should be contained within the subject line
|
||||||
|
|
@ -71,13 +71,13 @@ def isGitPatch(baseDir: str, nickname: str, domain: str,
|
||||||
return False
|
return False
|
||||||
if checkProjectName:
|
if checkProjectName:
|
||||||
projectName = \
|
projectName = \
|
||||||
getGitProjectName(baseDir, nickname, domain, subject)
|
_getGitProjectName(baseDir, nickname, domain, subject)
|
||||||
if not projectName:
|
if not projectName:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def getGitHash(patchStr: str) -> str:
|
def _getGitHash(patchStr: str) -> str:
|
||||||
"""Returns the commit hash from a given patch
|
"""Returns the commit hash from a given patch
|
||||||
"""
|
"""
|
||||||
patchLines = patchStr.split('\n')
|
patchLines = patchStr.split('\n')
|
||||||
|
|
@ -91,7 +91,7 @@ def getGitHash(patchStr: str) -> str:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def getPatchDescription(patchStr: str) -> str:
|
def _getPatchDescription(patchStr: str) -> str:
|
||||||
"""Returns the description from a given patch
|
"""Returns the description from a given patch
|
||||||
"""
|
"""
|
||||||
patchLines = patchStr.split('\n')
|
patchLines = patchStr.split('\n')
|
||||||
|
|
@ -134,8 +134,8 @@ def convertPostToPatch(baseDir: str, nickname: str, domain: str,
|
||||||
postJsonObject['object']['content'],
|
postJsonObject['object']['content'],
|
||||||
False):
|
False):
|
||||||
return False
|
return False
|
||||||
patchStr = gitFormatContent(postJsonObject['object']['content'])
|
patchStr = _gitFormatContent(postJsonObject['object']['content'])
|
||||||
commitHash = getGitHash(patchStr)
|
commitHash = _getGitHash(patchStr)
|
||||||
if not commitHash:
|
if not commitHash:
|
||||||
return False
|
return False
|
||||||
postJsonObject['object']['type'] = 'Patch'
|
postJsonObject['object']['type'] = 'Patch'
|
||||||
|
|
@ -146,7 +146,7 @@ def convertPostToPatch(baseDir: str, nickname: str, domain: str,
|
||||||
postJsonObject['object']['hash'] = commitHash
|
postJsonObject['object']['hash'] = commitHash
|
||||||
postJsonObject['object']['description'] = {
|
postJsonObject['object']['description'] = {
|
||||||
"mediaType": "text/plain",
|
"mediaType": "text/plain",
|
||||||
"content": getPatchDescription(patchStr)
|
"content": _getPatchDescription(patchStr)
|
||||||
}
|
}
|
||||||
# remove content map
|
# remove content map
|
||||||
if postJsonObject['object'].get('contentMap'):
|
if postJsonObject['object'].get('contentMap'):
|
||||||
|
|
@ -155,7 +155,7 @@ def convertPostToPatch(baseDir: str, nickname: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def gitAddFromHandle(patchStr: str, handle: str) -> str:
|
def _gitAddFromHandle(patchStr: str, handle: str) -> str:
|
||||||
"""Adds the activitypub handle of the sender to the patch
|
"""Adds the activitypub handle of the sender to the patch
|
||||||
"""
|
"""
|
||||||
fromStr = 'AP-signed-off-by: '
|
fromStr = 'AP-signed-off-by: '
|
||||||
|
|
@ -181,7 +181,7 @@ def receiveGitPatch(baseDir: str, nickname: str, domain: str,
|
||||||
messageType, subject, content):
|
messageType, subject, content):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
patchStr = gitFormatContent(content)
|
patchStr = _gitFormatContent(content)
|
||||||
|
|
||||||
patchLines = patchStr.split('\n')
|
patchLines = patchStr.split('\n')
|
||||||
patchFilename = None
|
patchFilename = None
|
||||||
|
|
@ -197,7 +197,7 @@ def receiveGitPatch(baseDir: str, nickname: str, domain: str,
|
||||||
patchSubject = patchSubject.replace('[PATCH]', '').strip()
|
patchSubject = patchSubject.replace('[PATCH]', '').strip()
|
||||||
patchSubject = patchSubject.replace(' ', '_')
|
patchSubject = patchSubject.replace(' ', '_')
|
||||||
projectName = \
|
projectName = \
|
||||||
getGitProjectName(baseDir, nickname, domain, subject)
|
_getGitProjectName(baseDir, nickname, domain, subject)
|
||||||
if not os.path.isdir(patchesDir):
|
if not os.path.isdir(patchesDir):
|
||||||
os.mkdir(patchesDir)
|
os.mkdir(patchesDir)
|
||||||
projectDir = patchesDir + '/' + projectName
|
projectDir = patchesDir + '/' + projectName
|
||||||
|
|
@ -209,7 +209,7 @@ def receiveGitPatch(baseDir: str, nickname: str, domain: str,
|
||||||
if not patchFilename:
|
if not patchFilename:
|
||||||
return False
|
return False
|
||||||
patchStr = \
|
patchStr = \
|
||||||
gitAddFromHandle(patchStr, '@' + fromNickname + '@' + fromDomain)
|
_gitAddFromHandle(patchStr, '@' + fromNickname + '@' + fromDomain)
|
||||||
with open(patchFilename, 'w+') as patchFile:
|
with open(patchFilename, 'w+') as patchFile:
|
||||||
patchFile.write(patchStr)
|
patchFile.write(patchStr)
|
||||||
patchNotifyFilename = \
|
patchNotifyFilename = \
|
||||||
|
|
|
||||||
32
happening.py
32
happening.py
|
|
@ -17,7 +17,7 @@ from utils import daysInMonth
|
||||||
from utils import mergeDicts
|
from utils import mergeDicts
|
||||||
|
|
||||||
|
|
||||||
def validUuid(testUuid: str, version=4):
|
def _validUuid(testUuid: str, version=4):
|
||||||
"""Check if uuid_to_test is a valid UUID
|
"""Check if uuid_to_test is a valid UUID
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -28,7 +28,7 @@ def validUuid(testUuid: str, version=4):
|
||||||
return str(uuid_obj) == testUuid
|
return str(uuid_obj) == testUuid
|
||||||
|
|
||||||
|
|
||||||
def removeEventFromTimeline(eventId: str, tlEventsFilename: str) -> None:
|
def _removeEventFromTimeline(eventId: str, tlEventsFilename: str) -> None:
|
||||||
"""Removes the given event Id from the timeline
|
"""Removes the given event Id from the timeline
|
||||||
"""
|
"""
|
||||||
if eventId + '\n' not in open(tlEventsFilename).read():
|
if eventId + '\n' not in open(tlEventsFilename).read():
|
||||||
|
|
@ -71,7 +71,7 @@ def saveEventPost(baseDir: str, handle: str, postId: str,
|
||||||
|
|
||||||
if eventJson.get('name') and eventJson.get('actor') and \
|
if eventJson.get('name') and eventJson.get('actor') and \
|
||||||
eventJson.get('uuid') and eventJson.get('content'):
|
eventJson.get('uuid') and eventJson.get('content'):
|
||||||
if not validUuid(eventJson['uuid']):
|
if not _validUuid(eventJson['uuid']):
|
||||||
return False
|
return False
|
||||||
print('Mobilizon type event')
|
print('Mobilizon type event')
|
||||||
# if this is a full description of an event then save it
|
# if this is a full description of an event then save it
|
||||||
|
|
@ -92,7 +92,7 @@ def saveEventPost(baseDir: str, handle: str, postId: str,
|
||||||
tlEventsFilename = baseDir + '/accounts/' + handle + '/events.txt'
|
tlEventsFilename = baseDir + '/accounts/' + handle + '/events.txt'
|
||||||
|
|
||||||
if os.path.isfile(tlEventsFilename):
|
if os.path.isfile(tlEventsFilename):
|
||||||
removeEventFromTimeline(eventId, tlEventsFilename)
|
_removeEventFromTimeline(eventId, tlEventsFilename)
|
||||||
try:
|
try:
|
||||||
with open(tlEventsFilename, 'r+') as tlEventsFile:
|
with open(tlEventsFilename, 'r+') as tlEventsFile:
|
||||||
content = tlEventsFile.read()
|
content = tlEventsFile.read()
|
||||||
|
|
@ -146,7 +146,7 @@ def saveEventPost(baseDir: str, handle: str, postId: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def isHappeningEvent(tag: {}) -> bool:
|
def _isHappeningEvent(tag: {}) -> bool:
|
||||||
"""Is this tag an Event or Place ActivityStreams type?
|
"""Is this tag an Event or Place ActivityStreams type?
|
||||||
"""
|
"""
|
||||||
if not tag.get('type'):
|
if not tag.get('type'):
|
||||||
|
|
@ -156,7 +156,7 @@ def isHappeningEvent(tag: {}) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def isHappeningPost(postJsonObject: {}) -> bool:
|
def _isHappeningPost(postJsonObject: {}) -> bool:
|
||||||
"""Is this a post with tags?
|
"""Is this a post with tags?
|
||||||
"""
|
"""
|
||||||
if not postJsonObject:
|
if not postJsonObject:
|
||||||
|
|
@ -208,13 +208,13 @@ def getTodaysEvents(baseDir: str, nickname: str, domain: str,
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postJsonObject = loadJson(postFilename)
|
postJsonObject = loadJson(postFilename)
|
||||||
if not isHappeningPost(postJsonObject):
|
if not _isHappeningPost(postJsonObject):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postEvent = []
|
postEvent = []
|
||||||
dayOfMonth = None
|
dayOfMonth = None
|
||||||
for tag in postJsonObject['object']['tag']:
|
for tag in postJsonObject['object']['tag']:
|
||||||
if not isHappeningEvent(tag):
|
if not _isHappeningEvent(tag):
|
||||||
continue
|
continue
|
||||||
# this tag is an event or a place
|
# this tag is an event or a place
|
||||||
if tag['type'] == 'Event':
|
if tag['type'] == 'Event':
|
||||||
|
|
@ -275,11 +275,11 @@ def todaysEventsCheck(baseDir: str, nickname: str, domain: str) -> bool:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postJsonObject = loadJson(postFilename)
|
postJsonObject = loadJson(postFilename)
|
||||||
if not isHappeningPost(postJsonObject):
|
if not _isHappeningPost(postJsonObject):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for tag in postJsonObject['object']['tag']:
|
for tag in postJsonObject['object']['tag']:
|
||||||
if not isHappeningEvent(tag):
|
if not _isHappeningEvent(tag):
|
||||||
continue
|
continue
|
||||||
# this tag is an event or a place
|
# this tag is an event or a place
|
||||||
if tag['type'] != 'Event':
|
if tag['type'] != 'Event':
|
||||||
|
|
@ -322,11 +322,11 @@ def thisWeeksEventsCheck(baseDir: str, nickname: str, domain: str) -> bool:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postJsonObject = loadJson(postFilename)
|
postJsonObject = loadJson(postFilename)
|
||||||
if not isHappeningPost(postJsonObject):
|
if not _isHappeningPost(postJsonObject):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for tag in postJsonObject['object']['tag']:
|
for tag in postJsonObject['object']['tag']:
|
||||||
if not isHappeningEvent(tag):
|
if not _isHappeningEvent(tag):
|
||||||
continue
|
continue
|
||||||
# this tag is an event or a place
|
# this tag is an event or a place
|
||||||
if tag['type'] != 'Event':
|
if tag['type'] != 'Event':
|
||||||
|
|
@ -377,14 +377,14 @@ def getThisWeeksEvents(baseDir: str, nickname: str, domain: str) -> {}:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postJsonObject = loadJson(postFilename)
|
postJsonObject = loadJson(postFilename)
|
||||||
if not isHappeningPost(postJsonObject):
|
if not _isHappeningPost(postJsonObject):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postEvent = []
|
postEvent = []
|
||||||
dayOfMonth = None
|
dayOfMonth = None
|
||||||
weekDayIndex = None
|
weekDayIndex = None
|
||||||
for tag in postJsonObject['object']['tag']:
|
for tag in postJsonObject['object']['tag']:
|
||||||
if not isHappeningEvent(tag):
|
if not _isHappeningEvent(tag):
|
||||||
continue
|
continue
|
||||||
# this tag is an event or a place
|
# this tag is an event or a place
|
||||||
if tag['type'] == 'Event':
|
if tag['type'] == 'Event':
|
||||||
|
|
@ -462,13 +462,13 @@ def getCalendarEvents(baseDir: str, nickname: str, domain: str,
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postJsonObject = loadJson(postFilename)
|
postJsonObject = loadJson(postFilename)
|
||||||
if not isHappeningPost(postJsonObject):
|
if not _isHappeningPost(postJsonObject):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
postEvent = []
|
postEvent = []
|
||||||
dayOfMonth = None
|
dayOfMonth = None
|
||||||
for tag in postJsonObject['object']['tag']:
|
for tag in postJsonObject['object']['tag']:
|
||||||
if not isHappeningEvent(tag):
|
if not _isHappeningEvent(tag):
|
||||||
continue
|
continue
|
||||||
# this tag is an event or a place
|
# this tag is an event or a place
|
||||||
if tag['type'] == 'Event':
|
if tag['type'] == 'Event':
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def verifyRecentSignature(signedDateStr: str) -> bool:
|
def _verifyRecentSignature(signedDateStr: str) -> bool:
|
||||||
"""Checks whether the given time taken from the header is within
|
"""Checks whether the given time taken from the header is within
|
||||||
12 hours of the current time
|
12 hours of the current time
|
||||||
"""
|
"""
|
||||||
|
|
@ -219,7 +219,7 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
||||||
else:
|
else:
|
||||||
if headers.get(signedHeader):
|
if headers.get(signedHeader):
|
||||||
if signedHeader == 'date':
|
if signedHeader == 'date':
|
||||||
if not verifyRecentSignature(headers[signedHeader]):
|
if not _verifyRecentSignature(headers[signedHeader]):
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: ' +
|
print('DEBUG: ' +
|
||||||
'verifyPostHeaders date is not recent ' +
|
'verifyPostHeaders date is not recent ' +
|
||||||
|
|
@ -230,7 +230,7 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
|
||||||
else:
|
else:
|
||||||
signedHeaderCap = signedHeader.capitalize()
|
signedHeaderCap = signedHeader.capitalize()
|
||||||
if signedHeaderCap == 'Date':
|
if signedHeaderCap == 'Date':
|
||||||
if not verifyRecentSignature(headers[signedHeaderCap]):
|
if not _verifyRecentSignature(headers[signedHeaderCap]):
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: ' +
|
print('DEBUG: ' +
|
||||||
'verifyPostHeaders date is not recent ' +
|
'verifyPostHeaders date is not recent ' +
|
||||||
|
|
|
||||||
230
inbox.py
230
inbox.py
|
|
@ -32,14 +32,14 @@ from utils import loadJson
|
||||||
from utils import saveJson
|
from utils import saveJson
|
||||||
from utils import updateLikesCollection
|
from utils import updateLikesCollection
|
||||||
from utils import undoLikesCollectionEntry
|
from utils import undoLikesCollectionEntry
|
||||||
from utils import getHashtagCategories
|
from categories import getHashtagCategories
|
||||||
from utils import setHashtagCategory
|
from categories import setHashtagCategory
|
||||||
from httpsig import verifyPostHeaders
|
from httpsig import verifyPostHeaders
|
||||||
from session import createSession
|
from session import createSession
|
||||||
from session import getJson
|
from session import getJson
|
||||||
from follow import receiveFollowRequest
|
from follow import receiveFollowRequest
|
||||||
from follow import getFollowersOfActor
|
from follow import getFollowersOfActor
|
||||||
from follow import unfollowerOfPerson
|
from follow import unfollowerOfAccount
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from cache import getPersonFromCache
|
from cache import getPersonFromCache
|
||||||
from cache import storePersonInCache
|
from cache import storePersonInCache
|
||||||
|
|
@ -49,8 +49,8 @@ from bookmarks import undoBookmarksCollectionEntry
|
||||||
from blocking import isBlocked
|
from blocking import isBlocked
|
||||||
from blocking import isBlockedDomain
|
from blocking import isBlockedDomain
|
||||||
from filters import isFiltered
|
from filters import isFiltered
|
||||||
from announce import updateAnnounceCollection
|
from utils import updateAnnounceCollection
|
||||||
from announce import undoAnnounceCollectionEntry
|
from utils import undoAnnounceCollectionEntry
|
||||||
from httpsig import messageContentDigest
|
from httpsig import messageContentDigest
|
||||||
from posts import validContentWarning
|
from posts import validContentWarning
|
||||||
from posts import downloadAnnounce
|
from posts import downloadAnnounce
|
||||||
|
|
@ -70,28 +70,7 @@ from content import dangerousMarkup
|
||||||
from happening import saveEventPost
|
from happening import saveEventPost
|
||||||
from delete import removeOldHashtags
|
from delete import removeOldHashtags
|
||||||
from follow import isFollowingActor
|
from follow import isFollowingActor
|
||||||
|
from categories import guessHashtagCategory
|
||||||
|
|
||||||
def guessHashtagCategory(tagName: str, hashtagCategories: {}) -> str:
|
|
||||||
"""Tries to guess a category for the given hashtag.
|
|
||||||
This works by trying to find the longest similar hashtag
|
|
||||||
"""
|
|
||||||
categoryMatched = ''
|
|
||||||
tagMatchedLen = 0
|
|
||||||
|
|
||||||
for categoryStr, hashtagList in hashtagCategories.items():
|
|
||||||
for hashtag in hashtagList:
|
|
||||||
if hashtag in tagName or tagName in hashtag:
|
|
||||||
if not categoryMatched:
|
|
||||||
tagMatchedLen = len(hashtag)
|
|
||||||
categoryMatched = categoryStr
|
|
||||||
else:
|
|
||||||
# match the longest tag
|
|
||||||
if len(hashtag) > tagMatchedLen:
|
|
||||||
categoryMatched = categoryStr
|
|
||||||
if not categoryMatched:
|
|
||||||
return
|
|
||||||
return categoryMatched
|
|
||||||
|
|
||||||
|
|
||||||
def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
|
def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
|
||||||
|
|
@ -159,7 +138,7 @@ def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
|
||||||
setHashtagCategory(baseDir, tagName, categoryStr)
|
setHashtagCategory(baseDir, tagName, categoryStr)
|
||||||
|
|
||||||
|
|
||||||
def inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
|
def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
translate: {},
|
translate: {},
|
||||||
baseDir: str, httpPrefix: str,
|
baseDir: str, httpPrefix: str,
|
||||||
session, cachedWebfingers: {}, personCache: {},
|
session, cachedWebfingers: {}, personCache: {},
|
||||||
|
|
@ -334,15 +313,6 @@ def inboxPermittedMessage(domain: str, messageJson: {},
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def validPublishedDate(published: str) -> bool:
|
|
||||||
currTime = datetime.datetime.utcnow()
|
|
||||||
pubDate = datetime.datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
daysSincePublished = (currTime - pubDate).days
|
|
||||||
if daysSincePublished > 30:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def savePostToInboxQueue(baseDir: str, httpPrefix: str,
|
def savePostToInboxQueue(baseDir: str, httpPrefix: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
|
|
@ -481,7 +451,7 @@ def savePostToInboxQueue(baseDir: str, httpPrefix: str,
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def inboxPostRecipientsAdd(baseDir: str, httpPrefix: str, toList: [],
|
def _inboxPostRecipientsAdd(baseDir: str, httpPrefix: str, toList: [],
|
||||||
recipientsDict: {},
|
recipientsDict: {},
|
||||||
domainMatch: str, domain: str,
|
domainMatch: str, domain: str,
|
||||||
actor: str, debug: bool) -> bool:
|
actor: str, debug: bool) -> bool:
|
||||||
|
|
@ -515,7 +485,7 @@ def inboxPostRecipientsAdd(baseDir: str, httpPrefix: str, toList: [],
|
||||||
return followerRecipients, recipientsDict
|
return followerRecipients, recipientsDict
|
||||||
|
|
||||||
|
|
||||||
def inboxPostRecipients(baseDir: str, postJsonObject: {},
|
def _inboxPostRecipients(baseDir: str, postJsonObject: {},
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
debug: bool) -> ([], []):
|
debug: bool) -> ([], []):
|
||||||
"""Returns dictionaries containing the recipients of the given post
|
"""Returns dictionaries containing the recipients of the given post
|
||||||
|
|
@ -550,7 +520,7 @@ def inboxPostRecipients(baseDir: str, postJsonObject: {},
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: resolving "to"')
|
print('DEBUG: resolving "to"')
|
||||||
includesFollowers, recipientsDict = \
|
includesFollowers, recipientsDict = \
|
||||||
inboxPostRecipientsAdd(baseDir, httpPrefix,
|
_inboxPostRecipientsAdd(baseDir, httpPrefix,
|
||||||
recipientsList,
|
recipientsList,
|
||||||
recipientsDict,
|
recipientsDict,
|
||||||
domainMatch, domainBase,
|
domainMatch, domainBase,
|
||||||
|
|
@ -567,7 +537,7 @@ def inboxPostRecipients(baseDir: str, postJsonObject: {},
|
||||||
else:
|
else:
|
||||||
recipientsList = [postJsonObject['object']['cc']]
|
recipientsList = [postJsonObject['object']['cc']]
|
||||||
includesFollowers, recipientsDict = \
|
includesFollowers, recipientsDict = \
|
||||||
inboxPostRecipientsAdd(baseDir, httpPrefix,
|
_inboxPostRecipientsAdd(baseDir, httpPrefix,
|
||||||
recipientsList,
|
recipientsList,
|
||||||
recipientsDict,
|
recipientsDict,
|
||||||
domainMatch, domainBase,
|
domainMatch, domainBase,
|
||||||
|
|
@ -592,7 +562,7 @@ def inboxPostRecipients(baseDir: str, postJsonObject: {},
|
||||||
else:
|
else:
|
||||||
recipientsList = [postJsonObject['to']]
|
recipientsList = [postJsonObject['to']]
|
||||||
includesFollowers, recipientsDict = \
|
includesFollowers, recipientsDict = \
|
||||||
inboxPostRecipientsAdd(baseDir, httpPrefix,
|
_inboxPostRecipientsAdd(baseDir, httpPrefix,
|
||||||
recipientsList,
|
recipientsList,
|
||||||
recipientsDict,
|
recipientsDict,
|
||||||
domainMatch, domainBase,
|
domainMatch, domainBase,
|
||||||
|
|
@ -606,7 +576,7 @@ def inboxPostRecipients(baseDir: str, postJsonObject: {},
|
||||||
else:
|
else:
|
||||||
recipientsList = [postJsonObject['cc']]
|
recipientsList = [postJsonObject['cc']]
|
||||||
includesFollowers, recipientsDict = \
|
includesFollowers, recipientsDict = \
|
||||||
inboxPostRecipientsAdd(baseDir, httpPrefix,
|
_inboxPostRecipientsAdd(baseDir, httpPrefix,
|
||||||
recipientsList,
|
recipientsList,
|
||||||
recipientsDict,
|
recipientsDict,
|
||||||
domainMatch, domainBase,
|
domainMatch, domainBase,
|
||||||
|
|
@ -626,7 +596,7 @@ def inboxPostRecipients(baseDir: str, postJsonObject: {},
|
||||||
return recipientsDict, recipientsDictFollowers
|
return recipientsDict, recipientsDictFollowers
|
||||||
|
|
||||||
|
|
||||||
def receiveUndoFollow(session, baseDir: str, httpPrefix: str,
|
def _receiveUndoFollow(session, baseDir: str, httpPrefix: str,
|
||||||
port: int, messageJson: {},
|
port: int, messageJson: {},
|
||||||
federationList: [],
|
federationList: [],
|
||||||
debug: bool) -> bool:
|
debug: bool) -> bool:
|
||||||
|
|
@ -667,7 +637,7 @@ def receiveUndoFollow(session, baseDir: str, httpPrefix: str,
|
||||||
getDomainFromActor(messageJson['object']['object'])
|
getDomainFromActor(messageJson['object']['object'])
|
||||||
domainFollowingFull = getFullDomain(domainFollowing, portFollowing)
|
domainFollowingFull = getFullDomain(domainFollowing, portFollowing)
|
||||||
|
|
||||||
if unfollowerOfPerson(baseDir,
|
if unfollowerOfAccount(baseDir,
|
||||||
nicknameFollowing, domainFollowingFull,
|
nicknameFollowing, domainFollowingFull,
|
||||||
nicknameFollower, domainFollowerFull,
|
nicknameFollower, domainFollowerFull,
|
||||||
debug):
|
debug):
|
||||||
|
|
@ -683,7 +653,7 @@ def receiveUndoFollow(session, baseDir: str, httpPrefix: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def receiveUndo(session, baseDir: str, httpPrefix: str,
|
def _receiveUndo(session, baseDir: str, httpPrefix: str,
|
||||||
port: int, sendThreads: [], postLog: [],
|
port: int, sendThreads: [], postLog: [],
|
||||||
cachedWebfingers: {}, personCache: {},
|
cachedWebfingers: {}, personCache: {},
|
||||||
messageJson: {}, federationList: [],
|
messageJson: {}, federationList: [],
|
||||||
|
|
@ -728,13 +698,13 @@ def receiveUndo(session, baseDir: str, httpPrefix: str,
|
||||||
' object within object is not a string')
|
' object within object is not a string')
|
||||||
return False
|
return False
|
||||||
if messageJson['object']['type'] == 'Follow':
|
if messageJson['object']['type'] == 'Follow':
|
||||||
return receiveUndoFollow(session, baseDir, httpPrefix,
|
return _receiveUndoFollow(session, baseDir, httpPrefix,
|
||||||
port, messageJson,
|
port, messageJson,
|
||||||
federationList, debug)
|
federationList, debug)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def receiveEventPost(recentPostsCache: {}, session, baseDir: str,
|
def _receiveEventPost(recentPostsCache: {}, session, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
personCache: {}, messageJson: {}, federationList: [],
|
personCache: {}, messageJson: {}, federationList: [],
|
||||||
|
|
@ -753,11 +723,12 @@ def receiveEventPost(recentPostsCache: {}, session, baseDir: str,
|
||||||
saveEventPost(baseDir, handle, postId, messageJson['object'])
|
saveEventPost(baseDir, handle, postId, messageJson['object'])
|
||||||
|
|
||||||
|
|
||||||
def personReceiveUpdate(baseDir: str,
|
def _personReceiveUpdate(baseDir: str,
|
||||||
domain: str, port: int,
|
domain: str, port: int,
|
||||||
updateNickname: str, updateDomain: str,
|
updateNickname: str, updateDomain: str,
|
||||||
updatePort: int,
|
updatePort: int,
|
||||||
personJson: {}, personCache: {}, debug: bool) -> bool:
|
personJson: {}, personCache: {},
|
||||||
|
debug: bool) -> bool:
|
||||||
"""Changes an actor. eg: avatar or display name change
|
"""Changes an actor. eg: avatar or display name change
|
||||||
"""
|
"""
|
||||||
print('Receiving actor update for ' + personJson['url'] +
|
print('Receiving actor update for ' + personJson['url'] +
|
||||||
|
|
@ -825,8 +796,9 @@ def personReceiveUpdate(baseDir: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def receiveUpdateToQuestion(recentPostsCache: {}, messageJson: {},
|
def _receiveUpdateToQuestion(recentPostsCache: {}, messageJson: {},
|
||||||
baseDir: str, nickname: str, domain: str) -> None:
|
baseDir: str,
|
||||||
|
nickname: str, domain: str) -> None:
|
||||||
"""Updating a question as new votes arrive
|
"""Updating a question as new votes arrive
|
||||||
"""
|
"""
|
||||||
# message url of the question
|
# message url of the question
|
||||||
|
|
@ -862,7 +834,7 @@ def receiveUpdateToQuestion(recentPostsCache: {}, messageJson: {},
|
||||||
removePostFromCache(messageJson, recentPostsCache)
|
removePostFromCache(messageJson, recentPostsCache)
|
||||||
|
|
||||||
|
|
||||||
def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
def _receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
personCache: {}, messageJson: {}, federationList: [],
|
personCache: {}, messageJson: {}, federationList: [],
|
||||||
|
|
@ -897,7 +869,7 @@ def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if messageJson['object']['type'] == 'Question':
|
if messageJson['object']['type'] == 'Question':
|
||||||
receiveUpdateToQuestion(recentPostsCache, messageJson,
|
_receiveUpdateToQuestion(recentPostsCache, messageJson,
|
||||||
baseDir, nickname, domain)
|
baseDir, nickname, domain)
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: Question update was received')
|
print('DEBUG: Question update was received')
|
||||||
|
|
@ -910,7 +882,7 @@ def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
||||||
if updateNickname:
|
if updateNickname:
|
||||||
updateDomain, updatePort = \
|
updateDomain, updatePort = \
|
||||||
getDomainFromActor(messageJson['id'])
|
getDomainFromActor(messageJson['id'])
|
||||||
if personReceiveUpdate(baseDir, domain, port,
|
if _personReceiveUpdate(baseDir, domain, port,
|
||||||
updateNickname, updateDomain,
|
updateNickname, updateDomain,
|
||||||
updatePort, messageJson,
|
updatePort, messageJson,
|
||||||
personCache, debug):
|
personCache, debug):
|
||||||
|
|
@ -931,7 +903,7 @@ def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
||||||
if updateNickname:
|
if updateNickname:
|
||||||
updateDomain, updatePort = \
|
updateDomain, updatePort = \
|
||||||
getDomainFromActor(messageJson['actor'])
|
getDomainFromActor(messageJson['actor'])
|
||||||
if personReceiveUpdate(baseDir,
|
if _personReceiveUpdate(baseDir,
|
||||||
domain, port,
|
domain, port,
|
||||||
updateNickname, updateDomain,
|
updateNickname, updateDomain,
|
||||||
updatePort,
|
updatePort,
|
||||||
|
|
@ -944,7 +916,7 @@ def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def receiveLike(recentPostsCache: {},
|
def _receiveLike(recentPostsCache: {},
|
||||||
session, handle: str, isGroup: bool, baseDir: str,
|
session, handle: str, isGroup: bool, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
onionDomain: str,
|
onionDomain: str,
|
||||||
|
|
@ -987,8 +959,9 @@ def receiveLike(recentPostsCache: {},
|
||||||
if not os.path.isdir(baseDir + '/accounts/' + handle):
|
if not os.path.isdir(baseDir + '/accounts/' + handle):
|
||||||
print('DEBUG: unknown recipient of like - ' + handle)
|
print('DEBUG: unknown recipient of like - ' + handle)
|
||||||
# if this post in the outbox of the person?
|
# if this post in the outbox of the person?
|
||||||
postFilename = locatePost(baseDir, handle.split('@')[0],
|
handleName = handle.split('@')[0]
|
||||||
handle.split('@')[1],
|
handleDom = handle.split('@')[1]
|
||||||
|
postFilename = locatePost(baseDir, handleName, handleDom,
|
||||||
messageJson['object'])
|
messageJson['object'])
|
||||||
if not postFilename:
|
if not postFilename:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -998,20 +971,21 @@ def receiveLike(recentPostsCache: {},
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: liked post found in inbox')
|
print('DEBUG: liked post found in inbox')
|
||||||
|
|
||||||
if not alreadyLiked(baseDir,
|
handleName = handle.split('@')[0]
|
||||||
handle.split('@')[0],
|
handleDom = handle.split('@')[1]
|
||||||
handle.split('@')[1],
|
if not _alreadyLiked(baseDir,
|
||||||
|
handleName, handleDom,
|
||||||
messageJson['object'],
|
messageJson['object'],
|
||||||
messageJson['actor']):
|
messageJson['actor']):
|
||||||
updateLikesCollection(recentPostsCache, baseDir, postFilename,
|
updateLikesCollection(recentPostsCache, baseDir, postFilename,
|
||||||
messageJson['object'],
|
messageJson['object'],
|
||||||
messageJson['actor'], domain, debug)
|
messageJson['actor'], domain, debug)
|
||||||
likeNotify(baseDir, domain, onionDomain, handle,
|
_likeNotify(baseDir, domain, onionDomain, handle,
|
||||||
messageJson['actor'], messageJson['object'])
|
messageJson['actor'], messageJson['object'])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def receiveUndoLike(recentPostsCache: {},
|
def _receiveUndoLike(recentPostsCache: {},
|
||||||
session, handle: str, isGroup: bool, baseDir: str,
|
session, handle: str, isGroup: bool, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
|
|
@ -1056,8 +1030,10 @@ def receiveUndoLike(recentPostsCache: {},
|
||||||
if not os.path.isdir(baseDir + '/accounts/' + handle):
|
if not os.path.isdir(baseDir + '/accounts/' + handle):
|
||||||
print('DEBUG: unknown recipient of undo like - ' + handle)
|
print('DEBUG: unknown recipient of undo like - ' + handle)
|
||||||
# if this post in the outbox of the person?
|
# if this post in the outbox of the person?
|
||||||
|
handleName = handle.split('@')[0]
|
||||||
|
handleDom = handle.split('@')[1]
|
||||||
postFilename = \
|
postFilename = \
|
||||||
locatePost(baseDir, handle.split('@')[0], handle.split('@')[1],
|
locatePost(baseDir, handleName, handleDom,
|
||||||
messageJson['object']['object'])
|
messageJson['object']['object'])
|
||||||
if not postFilename:
|
if not postFilename:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -1072,7 +1048,7 @@ def receiveUndoLike(recentPostsCache: {},
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def receiveBookmark(recentPostsCache: {},
|
def _receiveBookmark(recentPostsCache: {},
|
||||||
session, handle: str, isGroup: bool, baseDir: str,
|
session, handle: str, isGroup: bool, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
|
|
@ -1138,7 +1114,7 @@ def receiveBookmark(recentPostsCache: {},
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def receiveUndoBookmark(recentPostsCache: {},
|
def _receiveUndoBookmark(recentPostsCache: {},
|
||||||
session, handle: str, isGroup: bool, baseDir: str,
|
session, handle: str, isGroup: bool, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
|
|
@ -1207,7 +1183,7 @@ def receiveUndoBookmark(recentPostsCache: {},
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def receiveDelete(session, handle: str, isGroup: bool, baseDir: str,
|
def _receiveDelete(session, handle: str, isGroup: bool, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
personCache: {}, messageJson: {}, federationList: [],
|
personCache: {}, messageJson: {}, federationList: [],
|
||||||
|
|
@ -1293,9 +1269,10 @@ def receiveDelete(session, handle: str, isGroup: bool, baseDir: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def receiveAnnounce(recentPostsCache: {},
|
def _receiveAnnounce(recentPostsCache: {},
|
||||||
session, handle: str, isGroup: bool, baseDir: str,
|
session, handle: str, isGroup: bool, baseDir: str,
|
||||||
httpPrefix: str, domain: str, onionDomain: str, port: int,
|
httpPrefix: str,
|
||||||
|
domain: str, onionDomain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
personCache: {}, messageJson: {}, federationList: [],
|
personCache: {}, messageJson: {}, federationList: [],
|
||||||
debug: bool, translate: {},
|
debug: bool, translate: {},
|
||||||
|
|
@ -1440,7 +1417,7 @@ def receiveAnnounce(recentPostsCache: {},
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def receiveUndoAnnounce(recentPostsCache: {},
|
def _receiveUndoAnnounce(recentPostsCache: {},
|
||||||
session, handle: str, isGroup: bool, baseDir: str,
|
session, handle: str, isGroup: bool, baseDir: str,
|
||||||
httpPrefix: str, domain: str, port: int,
|
httpPrefix: str, domain: str, port: int,
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
|
|
@ -1473,8 +1450,9 @@ def receiveUndoAnnounce(recentPostsCache: {},
|
||||||
if not os.path.isdir(baseDir + '/accounts/' + handle):
|
if not os.path.isdir(baseDir + '/accounts/' + handle):
|
||||||
print('DEBUG: unknown recipient of undo announce - ' + handle)
|
print('DEBUG: unknown recipient of undo announce - ' + handle)
|
||||||
# if this post in the outbox of the person?
|
# if this post in the outbox of the person?
|
||||||
postFilename = locatePost(baseDir, handle.split('@')[0],
|
handleName = handle.split('@')[0]
|
||||||
handle.split('@')[1],
|
handleDom = handle.split('@')[1]
|
||||||
|
postFilename = locatePost(baseDir, handleName, handleDom,
|
||||||
messageJson['object']['object'])
|
messageJson['object']['object'])
|
||||||
if not postFilename:
|
if not postFilename:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -1512,7 +1490,7 @@ def jsonPostAllowsComments(postJsonObject: {}) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def postAllowsComments(postFilename: str) -> bool:
|
def _postAllowsComments(postFilename: str) -> bool:
|
||||||
"""Returns true if the given post allows comments/replies
|
"""Returns true if the given post allows comments/replies
|
||||||
"""
|
"""
|
||||||
postJsonObject = loadJson(postFilename)
|
postJsonObject = loadJson(postFilename)
|
||||||
|
|
@ -1563,7 +1541,7 @@ def populateReplies(baseDir: str, httpPrefix: str, domain: str,
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: post may have expired - ' + replyTo)
|
print('DEBUG: post may have expired - ' + replyTo)
|
||||||
return False
|
return False
|
||||||
if not postAllowsComments(postFilename):
|
if not _postAllowsComments(postFilename):
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: post does not allow comments - ' + replyTo)
|
print('DEBUG: post does not allow comments - ' + replyTo)
|
||||||
return False
|
return False
|
||||||
|
|
@ -1585,19 +1563,19 @@ def populateReplies(baseDir: str, httpPrefix: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def estimateNumberOfMentions(content: str) -> int:
|
def _estimateNumberOfMentions(content: str) -> int:
|
||||||
"""Returns a rough estimate of the number of mentions
|
"""Returns a rough estimate of the number of mentions
|
||||||
"""
|
"""
|
||||||
return int(content.count('@') / 2)
|
return int(content.count('@') / 2)
|
||||||
|
|
||||||
|
|
||||||
def estimateNumberOfEmoji(content: str) -> int:
|
def _estimateNumberOfEmoji(content: str) -> int:
|
||||||
"""Returns a rough estimate of the number of emoji
|
"""Returns a rough estimate of the number of emoji
|
||||||
"""
|
"""
|
||||||
return int(content.count(':') / 2)
|
return int(content.count(':') / 2)
|
||||||
|
|
||||||
|
|
||||||
def validPostContent(baseDir: str, nickname: str, domain: str,
|
def _validPostContent(baseDir: str, nickname: str, domain: str,
|
||||||
messageJson: {}, maxMentions: int, maxEmoji: int,
|
messageJson: {}, maxMentions: int, maxEmoji: int,
|
||||||
allowLocalNetworkAccess: bool) -> bool:
|
allowLocalNetworkAccess: bool) -> bool:
|
||||||
"""Is the content of a received post valid?
|
"""Is the content of a received post valid?
|
||||||
|
|
@ -1645,14 +1623,14 @@ def validPostContent(baseDir: str, nickname: str, domain: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# check (rough) number of mentions
|
# check (rough) number of mentions
|
||||||
mentionsEst = estimateNumberOfMentions(messageJson['object']['content'])
|
mentionsEst = _estimateNumberOfMentions(messageJson['object']['content'])
|
||||||
if mentionsEst > maxMentions:
|
if mentionsEst > maxMentions:
|
||||||
if messageJson['object'].get('id'):
|
if messageJson['object'].get('id'):
|
||||||
print('REJECT HELLTHREAD: ' + messageJson['object']['id'])
|
print('REJECT HELLTHREAD: ' + messageJson['object']['id'])
|
||||||
print('REJECT HELLTHREAD: Too many mentions in post - ' +
|
print('REJECT HELLTHREAD: Too many mentions in post - ' +
|
||||||
messageJson['object']['content'])
|
messageJson['object']['content'])
|
||||||
return False
|
return False
|
||||||
if estimateNumberOfEmoji(messageJson['object']['content']) > maxEmoji:
|
if _estimateNumberOfEmoji(messageJson['object']['content']) > maxEmoji:
|
||||||
if messageJson['object'].get('id'):
|
if messageJson['object'].get('id'):
|
||||||
print('REJECT EMOJI OVERLOAD: ' + messageJson['object']['id'])
|
print('REJECT EMOJI OVERLOAD: ' + messageJson['object']['id'])
|
||||||
print('REJECT EMOJI OVERLOAD: Too many emoji in post - ' +
|
print('REJECT EMOJI OVERLOAD: Too many emoji in post - ' +
|
||||||
|
|
@ -1680,7 +1658,7 @@ def validPostContent(baseDir: str, nickname: str, domain: str,
|
||||||
postPostFilename = locatePost(baseDir, nickname, domain,
|
postPostFilename = locatePost(baseDir, nickname, domain,
|
||||||
originalPostId)
|
originalPostId)
|
||||||
if postPostFilename:
|
if postPostFilename:
|
||||||
if not postAllowsComments(postPostFilename):
|
if not _postAllowsComments(postPostFilename):
|
||||||
print('REJECT: reply to post which does not ' +
|
print('REJECT: reply to post which does not ' +
|
||||||
'allow comments: ' + originalPostId)
|
'allow comments: ' + originalPostId)
|
||||||
return False
|
return False
|
||||||
|
|
@ -1688,7 +1666,7 @@ def validPostContent(baseDir: str, nickname: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def obtainAvatarForReplyPost(session, baseDir: str, httpPrefix: str,
|
def _obtainAvatarForReplyPost(session, baseDir: str, httpPrefix: str,
|
||||||
domain: str, onionDomain: str, personCache: {},
|
domain: str, onionDomain: str, personCache: {},
|
||||||
postJsonObject: {}, debug: bool) -> None:
|
postJsonObject: {}, debug: bool) -> None:
|
||||||
"""Tries to obtain the actor for the person being replied to
|
"""Tries to obtain the actor for the person being replied to
|
||||||
|
|
@ -1738,7 +1716,7 @@ def obtainAvatarForReplyPost(session, baseDir: str, httpPrefix: str,
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
def dmNotify(baseDir: str, handle: str, url: str) -> None:
|
def _dmNotify(baseDir: str, handle: str, url: str) -> None:
|
||||||
"""Creates a notification that a new DM has arrived
|
"""Creates a notification that a new DM has arrived
|
||||||
"""
|
"""
|
||||||
accountDir = baseDir + '/accounts/' + handle
|
accountDir = baseDir + '/accounts/' + handle
|
||||||
|
|
@ -1750,7 +1728,7 @@ def dmNotify(baseDir: str, handle: str, url: str) -> None:
|
||||||
fp.write(url)
|
fp.write(url)
|
||||||
|
|
||||||
|
|
||||||
def alreadyLiked(baseDir: str, nickname: str, domain: str,
|
def _alreadyLiked(baseDir: str, nickname: str, domain: str,
|
||||||
postUrl: str, likerActor: str) -> bool:
|
postUrl: str, likerActor: str) -> bool:
|
||||||
"""Is the given post already liked by the given handle?
|
"""Is the given post already liked by the given handle?
|
||||||
"""
|
"""
|
||||||
|
|
@ -1781,7 +1759,7 @@ def alreadyLiked(baseDir: str, nickname: str, domain: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def likeNotify(baseDir: str, domain: str, onionDomain: str,
|
def _likeNotify(baseDir: str, domain: str, onionDomain: str,
|
||||||
handle: str, actor: str, url: str) -> None:
|
handle: str, actor: str, url: str) -> None:
|
||||||
"""Creates a notification that a like has arrived
|
"""Creates a notification that a like has arrived
|
||||||
"""
|
"""
|
||||||
|
|
@ -1814,7 +1792,7 @@ def likeNotify(baseDir: str, domain: str, onionDomain: str,
|
||||||
if likerNickname and likerDomain:
|
if likerNickname and likerDomain:
|
||||||
likerHandle = likerNickname + '@' + likerDomain
|
likerHandle = likerNickname + '@' + likerDomain
|
||||||
else:
|
else:
|
||||||
print('likeNotify likerHandle: ' +
|
print('_likeNotify likerHandle: ' +
|
||||||
str(likerNickname) + '@' + str(likerDomain))
|
str(likerNickname) + '@' + str(likerDomain))
|
||||||
likerHandle = actor
|
likerHandle = actor
|
||||||
if likerHandle != handle:
|
if likerHandle != handle:
|
||||||
|
|
@ -1843,7 +1821,7 @@ def likeNotify(baseDir: str, domain: str, onionDomain: str,
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def replyNotify(baseDir: str, handle: str, url: str) -> None:
|
def _replyNotify(baseDir: str, handle: str, url: str) -> None:
|
||||||
"""Creates a notification that a new reply has arrived
|
"""Creates a notification that a new reply has arrived
|
||||||
"""
|
"""
|
||||||
accountDir = baseDir + '/accounts/' + handle
|
accountDir = baseDir + '/accounts/' + handle
|
||||||
|
|
@ -1855,7 +1833,7 @@ def replyNotify(baseDir: str, handle: str, url: str) -> None:
|
||||||
fp.write(url)
|
fp.write(url)
|
||||||
|
|
||||||
|
|
||||||
def gitPatchNotify(baseDir: str, handle: str,
|
def _gitPatchNotify(baseDir: str, handle: str,
|
||||||
subject: str, content: str,
|
subject: str, content: str,
|
||||||
fromNickname: str, fromDomain: str) -> None:
|
fromNickname: str, fromDomain: str) -> None:
|
||||||
"""Creates a notification that a new git patch has arrived
|
"""Creates a notification that a new git patch has arrived
|
||||||
|
|
@ -1870,7 +1848,7 @@ def gitPatchNotify(baseDir: str, handle: str,
|
||||||
fp.write('git ' + handle + ' ' + subject)
|
fp.write('git ' + handle + ' ' + subject)
|
||||||
|
|
||||||
|
|
||||||
def groupHandle(baseDir: str, handle: str) -> bool:
|
def _groupHandle(baseDir: str, handle: str) -> bool:
|
||||||
"""Is the given account handle a group?
|
"""Is the given account handle a group?
|
||||||
"""
|
"""
|
||||||
actorFile = baseDir + '/accounts/' + handle + '.json'
|
actorFile = baseDir + '/accounts/' + handle + '.json'
|
||||||
|
|
@ -1882,7 +1860,7 @@ def groupHandle(baseDir: str, handle: str) -> bool:
|
||||||
return actorJson['type'] == 'Group'
|
return actorJson['type'] == 'Group'
|
||||||
|
|
||||||
|
|
||||||
def getGroupName(baseDir: str, handle: str) -> str:
|
def _getGroupName(baseDir: str, handle: str) -> str:
|
||||||
"""Returns the preferred name of a group
|
"""Returns the preferred name of a group
|
||||||
"""
|
"""
|
||||||
actorFile = baseDir + '/accounts/' + handle + '.json'
|
actorFile = baseDir + '/accounts/' + handle + '.json'
|
||||||
|
|
@ -1894,7 +1872,7 @@ def getGroupName(baseDir: str, handle: str) -> str:
|
||||||
return actorJson['name']
|
return actorJson['name']
|
||||||
|
|
||||||
|
|
||||||
def sendToGroupMembers(session, baseDir: str, handle: str, port: int,
|
def _sendToGroupMembers(session, baseDir: str, handle: str, port: int,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
httpPrefix: str, federationList: [],
|
httpPrefix: str, federationList: [],
|
||||||
sendThreads: [], postLog: [], cachedWebfingers: {},
|
sendThreads: [], postLog: [], cachedWebfingers: {},
|
||||||
|
|
@ -1907,7 +1885,7 @@ def sendToGroupMembers(session, baseDir: str, handle: str, port: int,
|
||||||
if not postJsonObject.get('object'):
|
if not postJsonObject.get('object'):
|
||||||
return
|
return
|
||||||
nickname = handle.split('@')[0]
|
nickname = handle.split('@')[0]
|
||||||
# groupname = getGroupName(baseDir, handle)
|
# groupname = _getGroupName(baseDir, handle)
|
||||||
domain = handle.split('@')[1]
|
domain = handle.split('@')[1]
|
||||||
domainFull = getFullDomain(domain, port)
|
domainFull = getFullDomain(domain, port)
|
||||||
# set sender
|
# set sender
|
||||||
|
|
@ -1971,7 +1949,8 @@ def sendToGroupMembers(session, baseDir: str, handle: str, port: int,
|
||||||
personCache, debug, __version__)
|
personCache, debug, __version__)
|
||||||
|
|
||||||
|
|
||||||
def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
|
def _inboxUpdateCalendar(baseDir: str, handle: str,
|
||||||
|
postJsonObject: {}) -> None:
|
||||||
"""Detects whether the tag list on a post contains calendar events
|
"""Detects whether the tag list on a post contains calendar events
|
||||||
and if so saves the post id to a file in the calendar directory
|
and if so saves the post id to a file in the calendar directory
|
||||||
for the account
|
for the account
|
||||||
|
|
@ -2047,7 +2026,7 @@ def inboxUpdateIndex(boxname: str, baseDir: str, handle: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def updateLastSeen(baseDir: str, handle: str, actor: str) -> None:
|
def _updateLastSeen(baseDir: str, handle: str, actor: str) -> None:
|
||||||
"""Updates the time when the given handle last saw the given actor
|
"""Updates the time when the given handle last saw the given actor
|
||||||
This can later be used to indicate if accounts are dormant/abandoned/moved
|
This can later be used to indicate if accounts are dormant/abandoned/moved
|
||||||
"""
|
"""
|
||||||
|
|
@ -2079,7 +2058,7 @@ def updateLastSeen(baseDir: str, handle: str, actor: str) -> None:
|
||||||
lastSeenFile.write(str(daysSinceEpoch))
|
lastSeenFile.write(str(daysSinceEpoch))
|
||||||
|
|
||||||
|
|
||||||
def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
session, keyId: str, handle: str, messageJson: {},
|
session, keyId: str, handle: str, messageJson: {},
|
||||||
baseDir: str, httpPrefix: str, sendThreads: [],
|
baseDir: str, httpPrefix: str, sendThreads: [],
|
||||||
postLog: [], cachedWebfingers: {}, personCache: {},
|
postLog: [], cachedWebfingers: {}, personCache: {},
|
||||||
|
|
@ -2099,11 +2078,11 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
if '#' in actor:
|
if '#' in actor:
|
||||||
actor = keyId.split('#')[0]
|
actor = keyId.split('#')[0]
|
||||||
|
|
||||||
updateLastSeen(baseDir, handle, actor)
|
_updateLastSeen(baseDir, handle, actor)
|
||||||
|
|
||||||
isGroup = groupHandle(baseDir, handle)
|
isGroup = _groupHandle(baseDir, handle)
|
||||||
|
|
||||||
if receiveLike(recentPostsCache,
|
if _receiveLike(recentPostsCache,
|
||||||
session, handle, isGroup,
|
session, handle, isGroup,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
|
|
@ -2118,7 +2097,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
print('DEBUG: Like accepted from ' + actor)
|
print('DEBUG: Like accepted from ' + actor)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if receiveUndoLike(recentPostsCache,
|
if _receiveUndoLike(recentPostsCache,
|
||||||
session, handle, isGroup,
|
session, handle, isGroup,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
|
|
@ -2132,7 +2111,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
print('DEBUG: Undo like accepted from ' + actor)
|
print('DEBUG: Undo like accepted from ' + actor)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if receiveBookmark(recentPostsCache,
|
if _receiveBookmark(recentPostsCache,
|
||||||
session, handle, isGroup,
|
session, handle, isGroup,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
|
|
@ -2146,7 +2125,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
print('DEBUG: Bookmark accepted from ' + actor)
|
print('DEBUG: Bookmark accepted from ' + actor)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if receiveUndoBookmark(recentPostsCache,
|
if _receiveUndoBookmark(recentPostsCache,
|
||||||
session, handle, isGroup,
|
session, handle, isGroup,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
|
|
@ -2160,9 +2139,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
print('DEBUG: Undo bookmark accepted from ' + actor)
|
print('DEBUG: Undo bookmark accepted from ' + actor)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# labelAccusatoryPost(messageJson, translate)
|
if _receiveAnnounce(recentPostsCache,
|
||||||
|
|
||||||
if receiveAnnounce(recentPostsCache,
|
|
||||||
session, handle, isGroup,
|
session, handle, isGroup,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, onionDomain, port,
|
domain, onionDomain, port,
|
||||||
|
|
@ -2176,7 +2153,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: Announce accepted from ' + actor)
|
print('DEBUG: Announce accepted from ' + actor)
|
||||||
|
|
||||||
if receiveUndoAnnounce(recentPostsCache,
|
if _receiveUndoAnnounce(recentPostsCache,
|
||||||
session, handle, isGroup,
|
session, handle, isGroup,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
|
|
@ -2190,7 +2167,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
print('DEBUG: Undo announce accepted from ' + actor)
|
print('DEBUG: Undo announce accepted from ' + actor)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if receiveDelete(session, handle, isGroup,
|
if _receiveDelete(session, handle, isGroup,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
sendThreads, postLog,
|
sendThreads, postLog,
|
||||||
|
|
@ -2218,7 +2195,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
postJsonObject = messageJson
|
postJsonObject = messageJson
|
||||||
|
|
||||||
nickname = handle.split('@')[0]
|
nickname = handle.split('@')[0]
|
||||||
if validPostContent(baseDir, nickname, domain,
|
if _validPostContent(baseDir, nickname, domain,
|
||||||
postJsonObject, maxMentions, maxEmoji,
|
postJsonObject, maxMentions, maxEmoji,
|
||||||
allowLocalNetworkAccess):
|
allowLocalNetworkAccess):
|
||||||
|
|
||||||
|
|
@ -2243,7 +2220,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
jsonObj['summary'],
|
jsonObj['summary'],
|
||||||
jsonObj['content'],
|
jsonObj['content'],
|
||||||
fromNickname, fromDomain):
|
fromNickname, fromDomain):
|
||||||
gitPatchNotify(baseDir, handle,
|
_gitPatchNotify(baseDir, handle,
|
||||||
jsonObj['summary'],
|
jsonObj['summary'],
|
||||||
jsonObj['content'],
|
jsonObj['content'],
|
||||||
fromNickname, fromDomain)
|
fromNickname, fromDomain)
|
||||||
|
|
@ -2323,7 +2300,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
return False
|
return False
|
||||||
# dm index will be updated
|
# dm index will be updated
|
||||||
updateIndexList.append('dm')
|
updateIndexList.append('dm')
|
||||||
dmNotify(baseDir, handle,
|
_dmNotify(baseDir, handle,
|
||||||
httpPrefix + '://' + domain + '/users/' +
|
httpPrefix + '://' + domain + '/users/' +
|
||||||
nickname + '/dm')
|
nickname + '/dm')
|
||||||
|
|
||||||
|
|
@ -2343,7 +2320,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
if isinstance(inReplyTo, str):
|
if isinstance(inReplyTo, str):
|
||||||
if not isMuted(baseDir, nickname, domain,
|
if not isMuted(baseDir, nickname, domain,
|
||||||
inReplyTo):
|
inReplyTo):
|
||||||
replyNotify(baseDir, handle,
|
_replyNotify(baseDir, handle,
|
||||||
httpPrefix + '://' + domain +
|
httpPrefix + '://' + domain +
|
||||||
'/users/' + nickname +
|
'/users/' + nickname +
|
||||||
'/tlreplies')
|
'/tlreplies')
|
||||||
|
|
@ -2363,7 +2340,7 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
updateIndexList.append('tlevents')
|
updateIndexList.append('tlevents')
|
||||||
|
|
||||||
# get the avatar for a reply/announce
|
# get the avatar for a reply/announce
|
||||||
obtainAvatarForReplyPost(session, baseDir,
|
_obtainAvatarForReplyPost(session, baseDir,
|
||||||
httpPrefix, domain, onionDomain,
|
httpPrefix, domain, onionDomain,
|
||||||
personCache, postJsonObject, debug)
|
personCache, postJsonObject, debug)
|
||||||
|
|
||||||
|
|
@ -2389,13 +2366,14 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
print('Saving inbox post as html to cache')
|
print('Saving inbox post as html to cache')
|
||||||
|
|
||||||
htmlCacheStartTime = time.time()
|
htmlCacheStartTime = time.time()
|
||||||
inboxStorePostToHtmlCache(recentPostsCache,
|
handleName = handle.split('@')[0]
|
||||||
|
_inboxStorePostToHtmlCache(recentPostsCache,
|
||||||
maxRecentPosts,
|
maxRecentPosts,
|
||||||
translate, baseDir,
|
translate, baseDir,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
session, cachedWebfingers,
|
session, cachedWebfingers,
|
||||||
personCache,
|
personCache,
|
||||||
handle.split('@')[0],
|
handleName,
|
||||||
domain, port,
|
domain, port,
|
||||||
postJsonObject,
|
postJsonObject,
|
||||||
allowDeletion,
|
allowDeletion,
|
||||||
|
|
@ -2409,13 +2387,14 @@ def inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
' post as html to cache in ' +
|
' post as html to cache in ' +
|
||||||
timeDiff + ' mS')
|
timeDiff + ' mS')
|
||||||
|
|
||||||
inboxUpdateCalendar(baseDir, handle, postJsonObject)
|
_inboxUpdateCalendar(baseDir, handle, postJsonObject)
|
||||||
|
|
||||||
storeHashTags(baseDir, handle.split('@')[0], postJsonObject)
|
handleName = handle.split('@')[0]
|
||||||
|
storeHashTags(baseDir, handleName, postJsonObject)
|
||||||
|
|
||||||
# send the post out to group members
|
# send the post out to group members
|
||||||
if isGroup:
|
if isGroup:
|
||||||
sendToGroupMembers(session, baseDir, handle, port,
|
_sendToGroupMembers(session, baseDir, handle, port,
|
||||||
postJsonObject,
|
postJsonObject,
|
||||||
httpPrefix, federationList, sendThreads,
|
httpPrefix, federationList, sendThreads,
|
||||||
postLog, cachedWebfingers, personCache,
|
postLog, cachedWebfingers, personCache,
|
||||||
|
|
@ -2450,7 +2429,7 @@ def clearQueueItems(baseDir: str, queue: []) -> None:
|
||||||
print('Removed ' + str(ctr) + ' inbox queue items')
|
print('Removed ' + str(ctr) + ' inbox queue items')
|
||||||
|
|
||||||
|
|
||||||
def restoreQueueItems(baseDir: str, queue: []) -> None:
|
def _restoreQueueItems(baseDir: str, queue: []) -> None:
|
||||||
"""Checks the queue for each account and appends filenames
|
"""Checks the queue for each account and appends filenames
|
||||||
"""
|
"""
|
||||||
queue.clear()
|
queue.clear()
|
||||||
|
|
@ -2513,7 +2492,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
|
|
||||||
# if queue processing was interrupted (eg server crash)
|
# if queue processing was interrupted (eg server crash)
|
||||||
# then this loads any outstanding items back into the queue
|
# then this loads any outstanding items back into the queue
|
||||||
restoreQueueItems(baseDir, queue)
|
_restoreQueueItems(baseDir, queue)
|
||||||
|
|
||||||
# keep track of numbers of incoming posts per day
|
# keep track of numbers of incoming posts per day
|
||||||
quotasLastUpdateDaily = int(time.time())
|
quotasLastUpdateDaily = int(time.time())
|
||||||
|
|
@ -2545,7 +2524,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
queueRestoreCtr += 1
|
queueRestoreCtr += 1
|
||||||
if queueRestoreCtr >= 30:
|
if queueRestoreCtr >= 30:
|
||||||
queueRestoreCtr = 0
|
queueRestoreCtr = 0
|
||||||
restoreQueueItems(baseDir, queue)
|
_restoreQueueItems(baseDir, queue)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
currTime = int(time.time())
|
currTime = int(time.time())
|
||||||
|
|
@ -2738,12 +2717,13 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: checking http headers')
|
print('DEBUG: checking http headers')
|
||||||
pprint(queueJson['httpHeaders'])
|
pprint(queueJson['httpHeaders'])
|
||||||
|
postStr = json.dumps(queueJson['post'])
|
||||||
if not verifyPostHeaders(httpPrefix,
|
if not verifyPostHeaders(httpPrefix,
|
||||||
pubKey,
|
pubKey,
|
||||||
queueJson['httpHeaders'],
|
queueJson['httpHeaders'],
|
||||||
queueJson['path'], False,
|
queueJson['path'], False,
|
||||||
queueJson['digest'],
|
queueJson['digest'],
|
||||||
json.dumps(queueJson['post']),
|
postStr,
|
||||||
debug):
|
debug):
|
||||||
print('Queue: Header signature check failed')
|
print('Queue: Header signature check failed')
|
||||||
pprint(queueJson['httpHeaders'])
|
pprint(queueJson['httpHeaders'])
|
||||||
|
|
@ -2761,7 +2741,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
# if queueJson['post'].get('id'):
|
# if queueJson['post'].get('id'):
|
||||||
# queueJson['post']['id']=queueJson['id']
|
# queueJson['post']['id']=queueJson['id']
|
||||||
|
|
||||||
if receiveUndo(session,
|
if _receiveUndo(session,
|
||||||
baseDir, httpPrefix, port,
|
baseDir, httpPrefix, port,
|
||||||
sendThreads, postLog,
|
sendThreads, postLog,
|
||||||
cachedWebfingers,
|
cachedWebfingers,
|
||||||
|
|
@ -2812,7 +2792,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
queue.pop(0)
|
queue.pop(0)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if receiveEventPost(recentPostsCache, session,
|
if _receiveEventPost(recentPostsCache, session,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
sendThreads, postLog,
|
sendThreads, postLog,
|
||||||
|
|
@ -2829,7 +2809,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
queue.pop(0)
|
queue.pop(0)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if receiveUpdate(recentPostsCache, session,
|
if _receiveUpdate(recentPostsCache, session,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
domain, port,
|
domain, port,
|
||||||
sendThreads, postLog,
|
sendThreads, postLog,
|
||||||
|
|
@ -2848,7 +2828,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
|
|
||||||
# get recipients list
|
# get recipients list
|
||||||
recipientsDict, recipientsDictFollowers = \
|
recipientsDict, recipientsDictFollowers = \
|
||||||
inboxPostRecipients(baseDir, queueJson['post'],
|
_inboxPostRecipients(baseDir, queueJson['post'],
|
||||||
httpPrefix, domain, port, debug)
|
httpPrefix, domain, port, debug)
|
||||||
if len(recipientsDict.items()) == 0 and \
|
if len(recipientsDict.items()) == 0 and \
|
||||||
len(recipientsDictFollowers.items()) == 0:
|
len(recipientsDictFollowers.items()) == 0:
|
||||||
|
|
@ -2896,7 +2876,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
for handle, capsId in recipientsDict.items():
|
for handle, capsId in recipientsDict.items():
|
||||||
destination = \
|
destination = \
|
||||||
queueJson['destination'].replace(inboxHandle, handle)
|
queueJson['destination'].replace(inboxHandle, handle)
|
||||||
inboxAfterInitial(recentPostsCache,
|
_inboxAfterInitial(recentPostsCache,
|
||||||
maxRecentPosts,
|
maxRecentPosts,
|
||||||
session, keyId, handle,
|
session, keyId, handle,
|
||||||
queueJson['post'],
|
queueJson['post'],
|
||||||
|
|
|
||||||
36
jsonldsig.py
36
jsonldsig.py
|
|
@ -28,21 +28,21 @@ import base64
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
def b64safeEncode(payload: {}) -> str:
|
def _b64safeEncode(payload: {}) -> str:
|
||||||
"""
|
"""
|
||||||
b64 url safe encoding with the padding removed.
|
b64 url safe encoding with the padding removed.
|
||||||
"""
|
"""
|
||||||
return base64.urlsafe_b64encode(payload).rstrip(b'=')
|
return base64.urlsafe_b64encode(payload).rstrip(b'=')
|
||||||
|
|
||||||
|
|
||||||
def b64safeDecode(payload: {}) -> str:
|
def _b64safeDecode(payload: {}) -> str:
|
||||||
"""
|
"""
|
||||||
b64 url safe decoding with the padding added.
|
b64 url safe decoding with the padding added.
|
||||||
"""
|
"""
|
||||||
return base64.urlsafe_b64decode(payload + b'=' * (4 - len(payload) % 4))
|
return base64.urlsafe_b64decode(payload + b'=' * (4 - len(payload) % 4))
|
||||||
|
|
||||||
|
|
||||||
def normalizeJson(payload: {}) -> str:
|
def _normalizeJson(payload: {}) -> str:
|
||||||
"""
|
"""
|
||||||
Normalize with URDNA2015
|
Normalize with URDNA2015
|
||||||
"""
|
"""
|
||||||
|
|
@ -50,7 +50,7 @@ def normalizeJson(payload: {}) -> str:
|
||||||
sort_keys=True).encode('utf-8')
|
sort_keys=True).encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def signRs256(payload: {}, privateKeyPem: str) -> str:
|
def _signRs256(payload: {}, privateKeyPem: str) -> str:
|
||||||
"""
|
"""
|
||||||
Produce a RS256 signature of the payload
|
Produce a RS256 signature of the payload
|
||||||
"""
|
"""
|
||||||
|
|
@ -60,7 +60,7 @@ def signRs256(payload: {}, privateKeyPem: str) -> str:
|
||||||
return signature
|
return signature
|
||||||
|
|
||||||
|
|
||||||
def verifyRs256(payload: {}, signature: str, publicKeyPem: str) -> bool:
|
def _verifyRs256(payload: {}, signature: str, publicKeyPem: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Verifies a RS256 signature
|
Verifies a RS256 signature
|
||||||
"""
|
"""
|
||||||
|
|
@ -69,7 +69,7 @@ def verifyRs256(payload: {}, signature: str, publicKeyPem: str) -> bool:
|
||||||
return verifier.verify(SHA256.new(payload), signature)
|
return verifier.verify(SHA256.new(payload), signature)
|
||||||
|
|
||||||
|
|
||||||
def signJws(payload: {}, privateKeyPem: str) -> str:
|
def _signJws(payload: {}, privateKeyPem: str) -> str:
|
||||||
"""
|
"""
|
||||||
Prepare payload to sign
|
Prepare payload to sign
|
||||||
"""
|
"""
|
||||||
|
|
@ -78,28 +78,28 @@ def signJws(payload: {}, privateKeyPem: str) -> str:
|
||||||
'b64': False,
|
'b64': False,
|
||||||
'crit': ['b64']
|
'crit': ['b64']
|
||||||
}
|
}
|
||||||
normalizedJson = normalizeJson(header)
|
normalizedJson = _normalizeJson(header)
|
||||||
encodedHeader = b64safeEncode(normalizedJson)
|
encodedHeader = _b64safeEncode(normalizedJson)
|
||||||
preparedPayload = b'.'.join([encodedHeader, payload])
|
preparedPayload = b'.'.join([encodedHeader, payload])
|
||||||
|
|
||||||
signature = signRs256(preparedPayload, privateKeyPem)
|
signature = _signRs256(preparedPayload, privateKeyPem)
|
||||||
encodedSignature = b64safeEncode(signature)
|
encodedSignature = _b64safeEncode(signature)
|
||||||
jwsSignature = b'..'.join([encodedHeader, encodedSignature])
|
jwsSignature = b'..'.join([encodedHeader, encodedSignature])
|
||||||
|
|
||||||
return jwsSignature
|
return jwsSignature
|
||||||
|
|
||||||
|
|
||||||
def verifyJws(payload: {}, jwsSignature: str, publicKeyPem: str) -> bool:
|
def _verifyJws(payload: {}, jwsSignature: str, publicKeyPem: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Verifies a signature using the given public key
|
Verifies a signature using the given public key
|
||||||
"""
|
"""
|
||||||
encodedHeader, encodedSignature = jwsSignature.split(b'..')
|
encodedHeader, encodedSignature = jwsSignature.split(b'..')
|
||||||
signature = b64safeDecode(encodedSignature)
|
signature = _b64safeDecode(encodedSignature)
|
||||||
payload = b'.'.join([encodedHeader, payload])
|
payload = b'.'.join([encodedHeader, payload])
|
||||||
return verifyRs256(payload, signature, publicKeyPem)
|
return _verifyRs256(payload, signature, publicKeyPem)
|
||||||
|
|
||||||
|
|
||||||
def jsonldNormalize(jldDocument: str):
|
def _jsonldNormalize(jldDocument: str):
|
||||||
"""
|
"""
|
||||||
Normalize and hash the json-ld document
|
Normalize and hash the json-ld document
|
||||||
"""
|
"""
|
||||||
|
|
@ -117,8 +117,8 @@ def jsonldSign(jldDocument: {}, privateKeyPem: str) -> {}:
|
||||||
Produces a signed JSON-LD document with a Json Web Signature
|
Produces a signed JSON-LD document with a Json Web Signature
|
||||||
"""
|
"""
|
||||||
jldDocument = deepcopy(jldDocument)
|
jldDocument = deepcopy(jldDocument)
|
||||||
normalizedJldHash = jsonldNormalize(jldDocument)
|
normalizedJldHash = _jsonldNormalize(jldDocument)
|
||||||
jwsSignature = signJws(normalizedJldHash, privateKeyPem)
|
jwsSignature = _signJws(normalizedJldHash, privateKeyPem)
|
||||||
|
|
||||||
# construct the signature document and add it to jsonld
|
# construct the signature document and add it to jsonld
|
||||||
signature = {
|
signature = {
|
||||||
|
|
@ -138,9 +138,9 @@ def jsonldVerify(signedJldDocument: {}, publicKeyPem: str) -> bool:
|
||||||
signedJldDocument = deepcopy(signedJldDocument)
|
signedJldDocument = deepcopy(signedJldDocument)
|
||||||
signature = signedJldDocument.pop('signature')
|
signature = signedJldDocument.pop('signature')
|
||||||
jwsSignature = signature['signatureValue'].encode('utf-8')
|
jwsSignature = signature['signatureValue'].encode('utf-8')
|
||||||
normalizedJldHash = jsonldNormalize(signedJldDocument)
|
normalizedJldHash = _jsonldNormalize(signedJldDocument)
|
||||||
|
|
||||||
return verifyJws(normalizedJldHash, jwsSignature, publicKeyPem)
|
return _verifyJws(normalizedJldHash, jwsSignature, publicKeyPem)
|
||||||
|
|
||||||
|
|
||||||
def testSignJsonld(jldDocument: {}, privateKeyPem: str) -> {}:
|
def testSignJsonld(jldDocument: {}, privateKeyPem: str) -> {}:
|
||||||
|
|
|
||||||
74
like.py
74
like.py
|
|
@ -50,7 +50,7 @@ def noOfLikes(postJsonObject: {}) -> int:
|
||||||
return len(postJsonObject['object']['likes']['items'])
|
return len(postJsonObject['object']['likes']['items'])
|
||||||
|
|
||||||
|
|
||||||
def like(recentPostsCache: {},
|
def _like(recentPostsCache: {},
|
||||||
session, baseDir: str, federationList: [],
|
session, baseDir: str, federationList: [],
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
ccList: [], httpPrefix: str,
|
ccList: [], httpPrefix: str,
|
||||||
|
|
@ -134,83 +134,13 @@ def likePost(recentPostsCache: {},
|
||||||
actorLiked = httpPrefix + '://' + likeDomain + '/users/' + likeNickname
|
actorLiked = httpPrefix + '://' + likeDomain + '/users/' + likeNickname
|
||||||
objectUrl = actorLiked + '/statuses/' + str(likeStatusNumber)
|
objectUrl = actorLiked + '/statuses/' + str(likeStatusNumber)
|
||||||
|
|
||||||
return like(recentPostsCache,
|
return _like(recentPostsCache,
|
||||||
session, baseDir, federationList, nickname, domain, port,
|
session, baseDir, federationList, nickname, domain, port,
|
||||||
ccList, httpPrefix, objectUrl, actorLiked, clientToServer,
|
ccList, httpPrefix, objectUrl, actorLiked, clientToServer,
|
||||||
sendThreads, postLog, personCache, cachedWebfingers,
|
sendThreads, postLog, personCache, cachedWebfingers,
|
||||||
debug, projectVersion)
|
debug, projectVersion)
|
||||||
|
|
||||||
|
|
||||||
def undolike(recentPostsCache: {},
|
|
||||||
session, baseDir: str, federationList: [],
|
|
||||||
nickname: str, domain: str, port: int,
|
|
||||||
ccList: [], httpPrefix: str,
|
|
||||||
objectUrl: str, actorLiked: str,
|
|
||||||
clientToServer: bool,
|
|
||||||
sendThreads: [], postLog: [],
|
|
||||||
personCache: {}, cachedWebfingers: {},
|
|
||||||
debug: bool, projectVersion: str) -> {}:
|
|
||||||
"""Removes a like
|
|
||||||
actor is the person doing the liking
|
|
||||||
'to' might be a specific person (actor) whose post was liked
|
|
||||||
object is typically the url of the message which was liked
|
|
||||||
"""
|
|
||||||
if not urlPermitted(objectUrl, federationList):
|
|
||||||
return None
|
|
||||||
|
|
||||||
fullDomain = getFullDomain(domain, port)
|
|
||||||
|
|
||||||
newUndoLikeJson = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
'type': 'Undo',
|
|
||||||
'actor': httpPrefix + '://' + fullDomain + '/users/' + nickname,
|
|
||||||
'object': {
|
|
||||||
'type': 'Like',
|
|
||||||
'actor': httpPrefix + '://' + fullDomain + '/users/' + nickname,
|
|
||||||
'object': objectUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ccList:
|
|
||||||
if len(ccList) > 0:
|
|
||||||
newUndoLikeJson['cc'] = ccList
|
|
||||||
newUndoLikeJson['object']['cc'] = ccList
|
|
||||||
|
|
||||||
# Extract the domain and nickname from a statuses link
|
|
||||||
likedPostNickname = None
|
|
||||||
likedPostDomain = None
|
|
||||||
likedPostPort = None
|
|
||||||
if actorLiked:
|
|
||||||
likedPostNickname = getNicknameFromActor(actorLiked)
|
|
||||||
likedPostDomain, likedPostPort = getDomainFromActor(actorLiked)
|
|
||||||
else:
|
|
||||||
if '/users/' in objectUrl or \
|
|
||||||
'/accounts/' in objectUrl or \
|
|
||||||
'/channel/' in objectUrl or \
|
|
||||||
'/profile/' in objectUrl:
|
|
||||||
likedPostNickname = getNicknameFromActor(objectUrl)
|
|
||||||
likedPostDomain, likedPostPort = getDomainFromActor(objectUrl)
|
|
||||||
|
|
||||||
if likedPostNickname:
|
|
||||||
postFilename = locatePost(baseDir, nickname, domain, objectUrl)
|
|
||||||
if not postFilename:
|
|
||||||
return None
|
|
||||||
|
|
||||||
undoLikesCollectionEntry(baseDir, postFilename, objectUrl,
|
|
||||||
newUndoLikeJson['actor'], domain, debug)
|
|
||||||
|
|
||||||
sendSignedJson(newUndoLikeJson, session, baseDir,
|
|
||||||
nickname, domain, port,
|
|
||||||
likedPostNickname, likedPostDomain, likedPostPort,
|
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
|
||||||
httpPrefix, True, clientToServer, federationList,
|
|
||||||
sendThreads, postLog, cachedWebfingers, personCache,
|
|
||||||
debug, projectVersion)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return newUndoLikeJson
|
|
||||||
|
|
||||||
|
|
||||||
def sendLikeViaServer(baseDir: str, session,
|
def sendLikeViaServer(baseDir: str, session,
|
||||||
fromNickname: str, password: str,
|
fromNickname: str, password: str,
|
||||||
fromDomain: str, fromPort: int,
|
fromDomain: str, fromPort: int,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ def manualDenyFollowRequest(session, baseDir: str,
|
||||||
print('Follow request from ' + denyHandle + ' was denied.')
|
print('Follow request from ' + denyHandle + ' was denied.')
|
||||||
|
|
||||||
|
|
||||||
def approveFollowerHandle(accountDir: str, approveHandle: str) -> None:
|
def _approveFollowerHandle(accountDir: str, approveHandle: str) -> None:
|
||||||
""" Record manually approved handles so that if they unfollow and then
|
""" Record manually approved handles so that if they unfollow and then
|
||||||
re-follow later then they don't need to be manually approved again
|
re-follow later then they don't need to be manually approved again
|
||||||
"""
|
"""
|
||||||
|
|
@ -203,7 +203,7 @@ def manualApproveFollowRequest(session, baseDir: str,
|
||||||
# in followers.txt
|
# in followers.txt
|
||||||
if approveHandleFull in open(followersFilename).read():
|
if approveHandleFull in open(followersFilename).read():
|
||||||
# mark this handle as approved for following
|
# mark this handle as approved for following
|
||||||
approveFollowerHandle(accountDir, approveHandle)
|
_approveFollowerHandle(accountDir, approveHandle)
|
||||||
# update the follow requests with the handles not yet approved
|
# update the follow requests with the handles not yet approved
|
||||||
os.rename(approveFollowsFilename + '.new', approveFollowsFilename)
|
os.rename(approveFollowsFilename + '.new', approveFollowsFilename)
|
||||||
# remove the .follow file
|
# remove the .follow file
|
||||||
|
|
|
||||||
17
media.py
17
media.py
|
|
@ -6,7 +6,7 @@ __maintainer__ = "Bob Mottram"
|
||||||
__email__ = "bob@freedombone.net"
|
__email__ = "bob@freedombone.net"
|
||||||
__status__ = "Production"
|
__status__ = "Production"
|
||||||
|
|
||||||
from blurhash import blurhash_encode as blurencode
|
from blurhash import blurhash_encode
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import numpy
|
import numpy
|
||||||
import os
|
import os
|
||||||
|
|
@ -56,11 +56,12 @@ def removeMetaData(imageFilename: str, outputFilename: str) -> None:
|
||||||
os.system('/usr/bin/mogrify -strip ' + outputFilename) # nosec
|
os.system('/usr/bin/mogrify -strip ' + outputFilename) # nosec
|
||||||
|
|
||||||
|
|
||||||
def getImageHash(imageFilename: str) -> str:
|
def _getImageHash(imageFilename: str) -> str:
|
||||||
return blurencode(numpy.array(Image.open(imageFilename).convert("RGB")))
|
value = numpy.array(Image.open(imageFilename).convert("RGB"))
|
||||||
|
return blurhash_encode(value)
|
||||||
|
|
||||||
|
|
||||||
def isMedia(imageFilename: str) -> bool:
|
def _isMedia(imageFilename: str) -> bool:
|
||||||
permittedMedia = getMediaExtensions()
|
permittedMedia = getMediaExtensions()
|
||||||
for m in permittedMedia:
|
for m in permittedMedia:
|
||||||
if imageFilename.endswith('.' + m):
|
if imageFilename.endswith('.' + m):
|
||||||
|
|
@ -102,7 +103,7 @@ def getAttachmentMediaType(filename: str) -> str:
|
||||||
return mediaType
|
return mediaType
|
||||||
|
|
||||||
|
|
||||||
def updateEtag(mediaFilename: str) -> None:
|
def _updateEtag(mediaFilename: str) -> None:
|
||||||
""" calculate the etag, which is a sha1 of the data
|
""" calculate the etag, which is a sha1 of the data
|
||||||
"""
|
"""
|
||||||
# only create etags for media
|
# only create etags for media
|
||||||
|
|
@ -142,7 +143,7 @@ def attachMedia(baseDir: str, httpPrefix: str, domain: str, port: int,
|
||||||
Blurhash is optional, since low power systems may take a long
|
Blurhash is optional, since low power systems may take a long
|
||||||
time to calculate it
|
time to calculate it
|
||||||
"""
|
"""
|
||||||
if not isMedia(imageFilename):
|
if not _isMedia(imageFilename):
|
||||||
return postJson
|
return postJson
|
||||||
|
|
||||||
fileExtension = None
|
fileExtension = None
|
||||||
|
|
@ -181,7 +182,7 @@ def attachMedia(baseDir: str, httpPrefix: str, domain: str, port: int,
|
||||||
if mediaType.startswith('image/'):
|
if mediaType.startswith('image/'):
|
||||||
attachmentJson['focialPoint'] = [0.0, 0.0]
|
attachmentJson['focialPoint'] = [0.0, 0.0]
|
||||||
if useBlurhash:
|
if useBlurhash:
|
||||||
attachmentJson['blurhash'] = getImageHash(imageFilename)
|
attachmentJson['blurhash'] = _getImageHash(imageFilename)
|
||||||
postJson['attachment'] = [attachmentJson]
|
postJson['attachment'] = [attachmentJson]
|
||||||
|
|
||||||
if baseDir:
|
if baseDir:
|
||||||
|
|
@ -189,7 +190,7 @@ def attachMedia(baseDir: str, httpPrefix: str, domain: str, port: int,
|
||||||
removeMetaData(imageFilename, mediaFilename)
|
removeMetaData(imageFilename, mediaFilename)
|
||||||
else:
|
else:
|
||||||
copyfile(imageFilename, mediaFilename)
|
copyfile(imageFilename, mediaFilename)
|
||||||
updateEtag(mediaFilename)
|
_updateEtag(mediaFilename)
|
||||||
|
|
||||||
return postJson
|
return postJson
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ __status__ = "Production"
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
def migrateFollows(followFilename: str, oldHandle: str,
|
def _migrateFollows(followFilename: str, oldHandle: str,
|
||||||
newHandle: str) -> None:
|
newHandle: str) -> None:
|
||||||
"""Changes a handle within following or followers list
|
"""Changes a handle within following or followers list
|
||||||
"""
|
"""
|
||||||
|
|
@ -48,7 +48,7 @@ def migrateAccount(baseDir: str, oldHandle: str, newHandle: str) -> None:
|
||||||
if '@' in handle:
|
if '@' in handle:
|
||||||
accountDir = baseDir + '/accounts/' + handle
|
accountDir = baseDir + '/accounts/' + handle
|
||||||
followFilename = accountDir + '/following.txt'
|
followFilename = accountDir + '/following.txt'
|
||||||
migrateFollows(followFilename, oldHandle, newHandle)
|
_migrateFollows(followFilename, oldHandle, newHandle)
|
||||||
followFilename = accountDir + '/followers.txt'
|
followFilename = accountDir + '/followers.txt'
|
||||||
migrateFollows(followFilename, oldHandle, newHandle)
|
_migrateFollows(followFilename, oldHandle, newHandle)
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ from inbox import storeHashTags
|
||||||
from session import createSession
|
from session import createSession
|
||||||
|
|
||||||
|
|
||||||
def updateFeedsOutboxIndex(baseDir: str, domain: str, postId: str) -> None:
|
def _updateFeedsOutboxIndex(baseDir: str, domain: str, postId: str) -> None:
|
||||||
"""Updates the index used for imported RSS feeds
|
"""Updates the index used for imported RSS feeds
|
||||||
"""
|
"""
|
||||||
basePath = baseDir + '/accounts/news@' + domain
|
basePath = baseDir + '/accounts/news@' + domain
|
||||||
|
|
@ -59,7 +59,7 @@ def updateFeedsOutboxIndex(baseDir: str, domain: str, postId: str) -> None:
|
||||||
feedsFile.close()
|
feedsFile.close()
|
||||||
|
|
||||||
|
|
||||||
def saveArrivedTime(baseDir: str, postFilename: str, arrived: str) -> None:
|
def _saveArrivedTime(baseDir: str, postFilename: str, arrived: str) -> None:
|
||||||
"""Saves the time when an rss post arrived to a file
|
"""Saves the time when an rss post arrived to a file
|
||||||
"""
|
"""
|
||||||
arrivedFile = open(postFilename + '.arrived', 'w+')
|
arrivedFile = open(postFilename + '.arrived', 'w+')
|
||||||
|
|
@ -68,7 +68,7 @@ def saveArrivedTime(baseDir: str, postFilename: str, arrived: str) -> None:
|
||||||
arrivedFile.close()
|
arrivedFile.close()
|
||||||
|
|
||||||
|
|
||||||
def removeControlCharacters(content: str) -> str:
|
def _removeControlCharacters(content: str) -> str:
|
||||||
"""Remove escaped html
|
"""Remove escaped html
|
||||||
"""
|
"""
|
||||||
if '&' in content:
|
if '&' in content:
|
||||||
|
|
@ -227,7 +227,7 @@ def hashtagRuleTree(operators: [],
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
|
|
||||||
def newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
|
def _newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
|
||||||
hashtags: [], httpPrefix: str,
|
hashtags: [], httpPrefix: str,
|
||||||
domain: str, port: int,
|
domain: str, port: int,
|
||||||
personCache: {},
|
personCache: {},
|
||||||
|
|
@ -356,7 +356,7 @@ def newswireHashtagProcessing(session, baseDir: str, postJsonObject: {},
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def createNewsMirror(baseDir: str, domain: str,
|
def _createNewsMirror(baseDir: str, domain: str,
|
||||||
postIdNumber: str, url: str,
|
postIdNumber: str, url: str,
|
||||||
maxMirroredArticles: int) -> bool:
|
maxMirroredArticles: int) -> bool:
|
||||||
"""Creates a local mirror of a news article
|
"""Creates a local mirror of a news article
|
||||||
|
|
@ -446,7 +446,7 @@ def createNewsMirror(baseDir: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
def _convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
domain: str, port: int,
|
domain: str, port: int,
|
||||||
newswire: {},
|
newswire: {},
|
||||||
translate: {},
|
translate: {},
|
||||||
|
|
@ -497,7 +497,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
newswire[originalDateStr][3] = filename
|
newswire[originalDateStr][3] = filename
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rssTitle = removeControlCharacters(item[0])
|
rssTitle = _removeControlCharacters(item[0])
|
||||||
url = item[1]
|
url = item[1]
|
||||||
if dangerousMarkup(url, allowLocalNetworkAccess) or \
|
if dangerousMarkup(url, allowLocalNetworkAccess) or \
|
||||||
dangerousMarkup(rssTitle, allowLocalNetworkAccess):
|
dangerousMarkup(rssTitle, allowLocalNetworkAccess):
|
||||||
|
|
@ -505,7 +505,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
rssDescription = ''
|
rssDescription = ''
|
||||||
|
|
||||||
# get the rss description if it exists
|
# get the rss description if it exists
|
||||||
rssDescription = removeControlCharacters(item[4])
|
rssDescription = _removeControlCharacters(item[4])
|
||||||
if rssDescription.startswith('<![CDATA['):
|
if rssDescription.startswith('<![CDATA['):
|
||||||
rssDescription = rssDescription.replace('<![CDATA[', '')
|
rssDescription = rssDescription.replace('<![CDATA[', '')
|
||||||
rssDescription = rssDescription.replace(']]>', '')
|
rssDescription = rssDescription.replace(']]>', '')
|
||||||
|
|
@ -555,7 +555,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if mirrored:
|
if mirrored:
|
||||||
if not createNewsMirror(baseDir, domain, statusNumber,
|
if not _createNewsMirror(baseDir, domain, statusNumber,
|
||||||
url, maxMirroredArticles):
|
url, maxMirroredArticles):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -590,7 +590,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
|
|
||||||
moderated = item[5]
|
moderated = item[5]
|
||||||
|
|
||||||
savePost = newswireHashtagProcessing(session, baseDir, blog, hashtags,
|
savePost = _newswireHashtagProcessing(session, baseDir, blog, hashtags,
|
||||||
httpPrefix, domain, port,
|
httpPrefix, domain, port,
|
||||||
personCache, cachedWebfingers,
|
personCache, cachedWebfingers,
|
||||||
federationList,
|
federationList,
|
||||||
|
|
@ -628,7 +628,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
blog['object']['content'] = content
|
blog['object']['content'] = content
|
||||||
|
|
||||||
# update the newswire tags if new ones have been found by
|
# update the newswire tags if new ones have been found by
|
||||||
# newswireHashtagProcessing
|
# _newswireHashtagProcessing
|
||||||
for tag in hashtags:
|
for tag in hashtags:
|
||||||
if tag not in newswire[originalDateStr][6]:
|
if tag not in newswire[originalDateStr][6]:
|
||||||
newswire[originalDateStr][6].append(tag)
|
newswire[originalDateStr][6].append(tag)
|
||||||
|
|
@ -637,13 +637,13 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
|
|
||||||
clearFromPostCaches(baseDir, recentPostsCache, postId)
|
clearFromPostCaches(baseDir, recentPostsCache, postId)
|
||||||
if saveJson(blog, filename):
|
if saveJson(blog, filename):
|
||||||
updateFeedsOutboxIndex(baseDir, domain, postId + '.json')
|
_updateFeedsOutboxIndex(baseDir, domain, postId + '.json')
|
||||||
|
|
||||||
# Save a file containing the time when the post arrived
|
# Save a file containing the time when the post arrived
|
||||||
# this can then later be used to construct the news timeline
|
# this can then later be used to construct the news timeline
|
||||||
# excluding items during the voting period
|
# excluding items during the voting period
|
||||||
if moderated:
|
if moderated:
|
||||||
saveArrivedTime(baseDir, filename,
|
_saveArrivedTime(baseDir, filename,
|
||||||
blog['object']['arrived'])
|
blog['object']['arrived'])
|
||||||
else:
|
else:
|
||||||
if os.path.isfile(filename + '.arrived'):
|
if os.path.isfile(filename + '.arrived'):
|
||||||
|
|
@ -658,7 +658,7 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
|
||||||
newswire[originalDateStr][3] = filename
|
newswire[originalDateStr][3] = filename
|
||||||
|
|
||||||
|
|
||||||
def mergeWithPreviousNewswire(oldNewswire: {}, newNewswire: {}) -> None:
|
def _mergeWithPreviousNewswire(oldNewswire: {}, newNewswire: {}) -> None:
|
||||||
"""Preserve any votes or generated activitypub post filename
|
"""Preserve any votes or generated activitypub post filename
|
||||||
as rss feeds are updated
|
as rss feeds are updated
|
||||||
"""
|
"""
|
||||||
|
|
@ -707,14 +707,14 @@ def runNewswireDaemon(baseDir: str, httpd,
|
||||||
if os.path.isfile(newswireStateFilename):
|
if os.path.isfile(newswireStateFilename):
|
||||||
httpd.newswire = loadJson(newswireStateFilename)
|
httpd.newswire = loadJson(newswireStateFilename)
|
||||||
|
|
||||||
mergeWithPreviousNewswire(httpd.newswire, newNewswire)
|
_mergeWithPreviousNewswire(httpd.newswire, newNewswire)
|
||||||
|
|
||||||
httpd.newswire = newNewswire
|
httpd.newswire = newNewswire
|
||||||
if newNewswire:
|
if newNewswire:
|
||||||
saveJson(httpd.newswire, newswireStateFilename)
|
saveJson(httpd.newswire, newswireStateFilename)
|
||||||
print('Newswire updated')
|
print('Newswire updated')
|
||||||
|
|
||||||
convertRSStoActivityPub(baseDir,
|
_convertRSStoActivityPub(baseDir,
|
||||||
httpPrefix, domain, port,
|
httpPrefix, domain, port,
|
||||||
newNewswire, translate,
|
newNewswire, translate,
|
||||||
httpd.recentPostsCache,
|
httpd.recentPostsCache,
|
||||||
|
|
|
||||||
103
newswire.py
103
newswire.py
|
|
@ -15,7 +15,7 @@ from datetime import timedelta
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from utils import validPostDate
|
from utils import validPostDate
|
||||||
from utils import setHashtagCategory
|
from categories import setHashtagCategory
|
||||||
from utils import firstParagraphFromString
|
from utils import firstParagraphFromString
|
||||||
from utils import isPublicPost
|
from utils import isPublicPost
|
||||||
from utils import locatePost
|
from utils import locatePost
|
||||||
|
|
@ -29,7 +29,7 @@ from blocking import isBlockedHashtag
|
||||||
from filters import isFiltered
|
from filters import isFiltered
|
||||||
|
|
||||||
|
|
||||||
def removeCDATA(text: str) -> str:
|
def _removeCDATA(text: str) -> str:
|
||||||
"""Removes any CDATA from the given text
|
"""Removes any CDATA from the given text
|
||||||
"""
|
"""
|
||||||
if 'CDATA[' in text:
|
if 'CDATA[' in text:
|
||||||
|
|
@ -95,7 +95,7 @@ def getNewswireTags(text: str, maxTags: int) -> []:
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
def addNewswireDictEntry(baseDir: str, domain: str,
|
def _addNewswireDictEntry(baseDir: str, domain: str,
|
||||||
newswire: {}, dateStr: str,
|
newswire: {}, dateStr: str,
|
||||||
title: str, link: str,
|
title: str, link: str,
|
||||||
votesStatus: str, postFilename: str,
|
votesStatus: str, postFilename: str,
|
||||||
|
|
@ -143,7 +143,7 @@ def addNewswireDictEntry(baseDir: str, domain: str,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def validFeedDate(pubDate: str) -> bool:
|
def _validFeedDate(pubDate: str) -> bool:
|
||||||
# convert from YY-MM-DD HH:MM:SS+00:00 to
|
# convert from YY-MM-DD HH:MM:SS+00:00 to
|
||||||
# YY-MM-DDTHH:MM:SSZ
|
# YY-MM-DDTHH:MM:SSZ
|
||||||
postDate = pubDate.replace(' ', 'T').replace('+00:00', 'Z')
|
postDate = pubDate.replace(' ', 'T').replace('+00:00', 'Z')
|
||||||
|
|
@ -219,10 +219,10 @@ def loadHashtagCategories(baseDir: str, language: str) -> None:
|
||||||
|
|
||||||
with open(hashtagCategoriesFilename, 'r') as fp:
|
with open(hashtagCategoriesFilename, 'r') as fp:
|
||||||
xmlStr = fp.read()
|
xmlStr = fp.read()
|
||||||
xml2StrToHashtagCategories(baseDir, xmlStr, 1024, True)
|
_xml2StrToHashtagCategories(baseDir, xmlStr, 1024, True)
|
||||||
|
|
||||||
|
|
||||||
def xml2StrToHashtagCategories(baseDir: str, xmlStr: str,
|
def _xml2StrToHashtagCategories(baseDir: str, xmlStr: str,
|
||||||
maxCategoriesFeedItemSizeKb: int,
|
maxCategoriesFeedItemSizeKb: int,
|
||||||
force=False) -> None:
|
force=False) -> None:
|
||||||
"""Updates hashtag categories based upon an rss feed
|
"""Updates hashtag categories based upon an rss feed
|
||||||
|
|
@ -261,7 +261,7 @@ def xml2StrToHashtagCategories(baseDir: str, xmlStr: str,
|
||||||
setHashtagCategory(baseDir, hashtag, categoryStr, force)
|
setHashtagCategory(baseDir, hashtag, categoryStr, force)
|
||||||
|
|
||||||
|
|
||||||
def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
|
def _xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
moderated: bool, mirrored: bool,
|
moderated: bool, mirrored: bool,
|
||||||
maxPostsPerSource: int,
|
maxPostsPerSource: int,
|
||||||
maxFeedItemSizeKb: int,
|
maxFeedItemSizeKb: int,
|
||||||
|
|
@ -274,7 +274,7 @@ def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
|
|
||||||
# is this an rss feed containing hashtag categories?
|
# is this an rss feed containing hashtag categories?
|
||||||
if '<title>#categories</title>' in xmlStr:
|
if '<title>#categories</title>' in xmlStr:
|
||||||
xml2StrToHashtagCategories(baseDir, xmlStr,
|
_xml2StrToHashtagCategories(baseDir, xmlStr,
|
||||||
maxCategoriesFeedItemSizeKb)
|
maxCategoriesFeedItemSizeKb)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
@ -300,17 +300,17 @@ def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
if '</pubDate>' not in rssItem:
|
if '</pubDate>' not in rssItem:
|
||||||
continue
|
continue
|
||||||
title = rssItem.split('<title>')[1]
|
title = rssItem.split('<title>')[1]
|
||||||
title = removeCDATA(title.split('</title>')[0])
|
title = _removeCDATA(title.split('</title>')[0])
|
||||||
description = ''
|
description = ''
|
||||||
if '<description>' in rssItem and '</description>' in rssItem:
|
if '<description>' in rssItem and '</description>' in rssItem:
|
||||||
description = rssItem.split('<description>')[1]
|
description = rssItem.split('<description>')[1]
|
||||||
description = removeCDATA(description.split('</description>')[0])
|
description = _removeCDATA(description.split('</description>')[0])
|
||||||
else:
|
else:
|
||||||
if '<media:description>' in rssItem and \
|
if '<media:description>' in rssItem and \
|
||||||
'</media:description>' in rssItem:
|
'</media:description>' in rssItem:
|
||||||
description = rssItem.split('<media:description>')[1]
|
description = rssItem.split('<media:description>')[1]
|
||||||
description = description.split('</media:description>')[0]
|
description = description.split('</media:description>')[0]
|
||||||
description = removeCDATA(description)
|
description = _removeCDATA(description)
|
||||||
link = rssItem.split('<link>')[1]
|
link = rssItem.split('<link>')[1]
|
||||||
link = link.split('</link>')[0]
|
link = link.split('</link>')[0]
|
||||||
if '://' not in link:
|
if '://' not in link:
|
||||||
|
|
@ -325,10 +325,10 @@ def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
|
|
||||||
pubDateStr = parseFeedDate(pubDate)
|
pubDateStr = parseFeedDate(pubDate)
|
||||||
if pubDateStr:
|
if pubDateStr:
|
||||||
if validFeedDate(pubDateStr):
|
if _validFeedDate(pubDateStr):
|
||||||
postFilename = ''
|
postFilename = ''
|
||||||
votesStatus = []
|
votesStatus = []
|
||||||
addNewswireDictEntry(baseDir, domain,
|
_addNewswireDictEntry(baseDir, domain,
|
||||||
result, pubDateStr,
|
result, pubDateStr,
|
||||||
title, link,
|
title, link,
|
||||||
votesStatus, postFilename,
|
votesStatus, postFilename,
|
||||||
|
|
@ -341,7 +341,7 @@ def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def xml1StrToDict(baseDir: str, domain: str, xmlStr: str,
|
def _xml1StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
moderated: bool, mirrored: bool,
|
moderated: bool, mirrored: bool,
|
||||||
maxPostsPerSource: int,
|
maxPostsPerSource: int,
|
||||||
maxFeedItemSizeKb: int,
|
maxFeedItemSizeKb: int,
|
||||||
|
|
@ -356,7 +356,7 @@ def xml1StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
|
|
||||||
# is this an rss feed containing hashtag categories?
|
# is this an rss feed containing hashtag categories?
|
||||||
if '<title>#categories</title>' in xmlStr:
|
if '<title>#categories</title>' in xmlStr:
|
||||||
xml2StrToHashtagCategories(baseDir, xmlStr,
|
_xml2StrToHashtagCategories(baseDir, xmlStr,
|
||||||
maxCategoriesFeedItemSizeKb)
|
maxCategoriesFeedItemSizeKb)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
@ -384,17 +384,17 @@ def xml1StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
if '</dc:date>' not in rssItem:
|
if '</dc:date>' not in rssItem:
|
||||||
continue
|
continue
|
||||||
title = rssItem.split('<title>')[1]
|
title = rssItem.split('<title>')[1]
|
||||||
title = removeCDATA(title.split('</title>')[0])
|
title = _removeCDATA(title.split('</title>')[0])
|
||||||
description = ''
|
description = ''
|
||||||
if '<description>' in rssItem and '</description>' in rssItem:
|
if '<description>' in rssItem and '</description>' in rssItem:
|
||||||
description = rssItem.split('<description>')[1]
|
description = rssItem.split('<description>')[1]
|
||||||
description = removeCDATA(description.split('</description>')[0])
|
description = _removeCDATA(description.split('</description>')[0])
|
||||||
else:
|
else:
|
||||||
if '<media:description>' in rssItem and \
|
if '<media:description>' in rssItem and \
|
||||||
'</media:description>' in rssItem:
|
'</media:description>' in rssItem:
|
||||||
description = rssItem.split('<media:description>')[1]
|
description = rssItem.split('<media:description>')[1]
|
||||||
description = description.split('</media:description>')[0]
|
description = description.split('</media:description>')[0]
|
||||||
description = removeCDATA(description)
|
description = _removeCDATA(description)
|
||||||
link = rssItem.split('<link>')[1]
|
link = rssItem.split('<link>')[1]
|
||||||
link = link.split('</link>')[0]
|
link = link.split('</link>')[0]
|
||||||
if '://' not in link:
|
if '://' not in link:
|
||||||
|
|
@ -409,10 +409,10 @@ def xml1StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
|
|
||||||
pubDateStr = parseFeedDate(pubDate)
|
pubDateStr = parseFeedDate(pubDate)
|
||||||
if pubDateStr:
|
if pubDateStr:
|
||||||
if validFeedDate(pubDateStr):
|
if _validFeedDate(pubDateStr):
|
||||||
postFilename = ''
|
postFilename = ''
|
||||||
votesStatus = []
|
votesStatus = []
|
||||||
addNewswireDictEntry(baseDir, domain,
|
_addNewswireDictEntry(baseDir, domain,
|
||||||
result, pubDateStr,
|
result, pubDateStr,
|
||||||
title, link,
|
title, link,
|
||||||
votesStatus, postFilename,
|
votesStatus, postFilename,
|
||||||
|
|
@ -425,7 +425,7 @@ def xml1StrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
|
def _atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
moderated: bool, mirrored: bool,
|
moderated: bool, mirrored: bool,
|
||||||
maxPostsPerSource: int,
|
maxPostsPerSource: int,
|
||||||
maxFeedItemSizeKb: int) -> {}:
|
maxFeedItemSizeKb: int) -> {}:
|
||||||
|
|
@ -456,17 +456,17 @@ def atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
if '</updated>' not in atomItem:
|
if '</updated>' not in atomItem:
|
||||||
continue
|
continue
|
||||||
title = atomItem.split('<title>')[1]
|
title = atomItem.split('<title>')[1]
|
||||||
title = removeCDATA(title.split('</title>')[0])
|
title = _removeCDATA(title.split('</title>')[0])
|
||||||
description = ''
|
description = ''
|
||||||
if '<summary>' in atomItem and '</summary>' in atomItem:
|
if '<summary>' in atomItem and '</summary>' in atomItem:
|
||||||
description = atomItem.split('<summary>')[1]
|
description = atomItem.split('<summary>')[1]
|
||||||
description = removeCDATA(description.split('</summary>')[0])
|
description = _removeCDATA(description.split('</summary>')[0])
|
||||||
else:
|
else:
|
||||||
if '<media:description>' in atomItem and \
|
if '<media:description>' in atomItem and \
|
||||||
'</media:description>' in atomItem:
|
'</media:description>' in atomItem:
|
||||||
description = atomItem.split('<media:description>')[1]
|
description = atomItem.split('<media:description>')[1]
|
||||||
description = description.split('</media:description>')[0]
|
description = description.split('</media:description>')[0]
|
||||||
description = removeCDATA(description)
|
description = _removeCDATA(description)
|
||||||
link = atomItem.split('<link>')[1]
|
link = atomItem.split('<link>')[1]
|
||||||
link = link.split('</link>')[0]
|
link = link.split('</link>')[0]
|
||||||
if '://' not in link:
|
if '://' not in link:
|
||||||
|
|
@ -481,10 +481,10 @@ def atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
|
|
||||||
pubDateStr = parseFeedDate(pubDate)
|
pubDateStr = parseFeedDate(pubDate)
|
||||||
if pubDateStr:
|
if pubDateStr:
|
||||||
if validFeedDate(pubDateStr):
|
if _validFeedDate(pubDateStr):
|
||||||
postFilename = ''
|
postFilename = ''
|
||||||
votesStatus = []
|
votesStatus = []
|
||||||
addNewswireDictEntry(baseDir, domain,
|
_addNewswireDictEntry(baseDir, domain,
|
||||||
result, pubDateStr,
|
result, pubDateStr,
|
||||||
title, link,
|
title, link,
|
||||||
votesStatus, postFilename,
|
votesStatus, postFilename,
|
||||||
|
|
@ -497,7 +497,7 @@ def atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def atomFeedYTToDict(baseDir: str, domain: str, xmlStr: str,
|
def _atomFeedYTToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
moderated: bool, mirrored: bool,
|
moderated: bool, mirrored: bool,
|
||||||
maxPostsPerSource: int,
|
maxPostsPerSource: int,
|
||||||
maxFeedItemSizeKb: int) -> {}:
|
maxFeedItemSizeKb: int) -> {}:
|
||||||
|
|
@ -532,17 +532,17 @@ def atomFeedYTToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
if '</yt:videoId>' not in atomItem:
|
if '</yt:videoId>' not in atomItem:
|
||||||
continue
|
continue
|
||||||
title = atomItem.split('<title>')[1]
|
title = atomItem.split('<title>')[1]
|
||||||
title = removeCDATA(title.split('</title>')[0])
|
title = _removeCDATA(title.split('</title>')[0])
|
||||||
description = ''
|
description = ''
|
||||||
if '<media:description>' in atomItem and \
|
if '<media:description>' in atomItem and \
|
||||||
'</media:description>' in atomItem:
|
'</media:description>' in atomItem:
|
||||||
description = atomItem.split('<media:description>')[1]
|
description = atomItem.split('<media:description>')[1]
|
||||||
description = description.split('</media:description>')[0]
|
description = description.split('</media:description>')[0]
|
||||||
description = removeCDATA(description)
|
description = _removeCDATA(description)
|
||||||
elif '<summary>' in atomItem and '</summary>' in atomItem:
|
elif '<summary>' in atomItem and '</summary>' in atomItem:
|
||||||
description = atomItem.split('<summary>')[1]
|
description = atomItem.split('<summary>')[1]
|
||||||
description = description.split('</summary>')[0]
|
description = description.split('</summary>')[0]
|
||||||
description = removeCDATA(description)
|
description = _removeCDATA(description)
|
||||||
link = atomItem.split('<yt:videoId>')[1]
|
link = atomItem.split('<yt:videoId>')[1]
|
||||||
link = link.split('</yt:videoId>')[0]
|
link = link.split('</yt:videoId>')[0]
|
||||||
link = 'https://www.youtube.com/watch?v=' + link.strip()
|
link = 'https://www.youtube.com/watch?v=' + link.strip()
|
||||||
|
|
@ -551,10 +551,10 @@ def atomFeedYTToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
|
|
||||||
pubDateStr = parseFeedDate(pubDate)
|
pubDateStr = parseFeedDate(pubDate)
|
||||||
if pubDateStr:
|
if pubDateStr:
|
||||||
if validFeedDate(pubDateStr):
|
if _validFeedDate(pubDateStr):
|
||||||
postFilename = ''
|
postFilename = ''
|
||||||
votesStatus = []
|
votesStatus = []
|
||||||
addNewswireDictEntry(baseDir, domain,
|
_addNewswireDictEntry(baseDir, domain,
|
||||||
result, pubDateStr,
|
result, pubDateStr,
|
||||||
title, link,
|
title, link,
|
||||||
votesStatus, postFilename,
|
votesStatus, postFilename,
|
||||||
|
|
@ -567,7 +567,7 @@ def atomFeedYTToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def xmlStrToDict(baseDir: str, domain: str, xmlStr: str,
|
def _xmlStrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
moderated: bool, mirrored: bool,
|
moderated: bool, mirrored: bool,
|
||||||
maxPostsPerSource: int,
|
maxPostsPerSource: int,
|
||||||
maxFeedItemSizeKb: int,
|
maxFeedItemSizeKb: int,
|
||||||
|
|
@ -576,27 +576,27 @@ def xmlStrToDict(baseDir: str, domain: str, xmlStr: str,
|
||||||
"""
|
"""
|
||||||
if '<yt:videoId>' in xmlStr and '<yt:channelId>' in xmlStr:
|
if '<yt:videoId>' in xmlStr and '<yt:channelId>' in xmlStr:
|
||||||
print('YouTube feed: reading')
|
print('YouTube feed: reading')
|
||||||
return atomFeedYTToDict(baseDir, domain,
|
return _atomFeedYTToDict(baseDir, domain,
|
||||||
xmlStr, moderated, mirrored,
|
xmlStr, moderated, mirrored,
|
||||||
maxPostsPerSource, maxFeedItemSizeKb)
|
maxPostsPerSource, maxFeedItemSizeKb)
|
||||||
elif 'rss version="2.0"' in xmlStr:
|
elif 'rss version="2.0"' in xmlStr:
|
||||||
return xml2StrToDict(baseDir, domain,
|
return _xml2StrToDict(baseDir, domain,
|
||||||
xmlStr, moderated, mirrored,
|
xmlStr, moderated, mirrored,
|
||||||
maxPostsPerSource, maxFeedItemSizeKb,
|
maxPostsPerSource, maxFeedItemSizeKb,
|
||||||
maxCategoriesFeedItemSizeKb)
|
maxCategoriesFeedItemSizeKb)
|
||||||
elif '<?xml version="1.0"' in xmlStr:
|
elif '<?xml version="1.0"' in xmlStr:
|
||||||
return xml1StrToDict(baseDir, domain,
|
return _xml1StrToDict(baseDir, domain,
|
||||||
xmlStr, moderated, mirrored,
|
xmlStr, moderated, mirrored,
|
||||||
maxPostsPerSource, maxFeedItemSizeKb,
|
maxPostsPerSource, maxFeedItemSizeKb,
|
||||||
maxCategoriesFeedItemSizeKb)
|
maxCategoriesFeedItemSizeKb)
|
||||||
elif 'xmlns="http://www.w3.org/2005/Atom"' in xmlStr:
|
elif 'xmlns="http://www.w3.org/2005/Atom"' in xmlStr:
|
||||||
return atomFeedToDict(baseDir, domain,
|
return _atomFeedToDict(baseDir, domain,
|
||||||
xmlStr, moderated, mirrored,
|
xmlStr, moderated, mirrored,
|
||||||
maxPostsPerSource, maxFeedItemSizeKb)
|
maxPostsPerSource, maxFeedItemSizeKb)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def YTchannelToAtomFeed(url: str) -> str:
|
def _YTchannelToAtomFeed(url: str) -> str:
|
||||||
"""Converts a YouTube channel url into an atom feed url
|
"""Converts a YouTube channel url into an atom feed url
|
||||||
"""
|
"""
|
||||||
if 'youtube.com/channel/' not in url:
|
if 'youtube.com/channel/' not in url:
|
||||||
|
|
@ -633,13 +633,13 @@ def getRSS(baseDir: str, domain: str, session, url: str,
|
||||||
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'
|
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'
|
||||||
if not session:
|
if not session:
|
||||||
print('WARN: no session specified for getRSS')
|
print('WARN: no session specified for getRSS')
|
||||||
url = YTchannelToAtomFeed(url)
|
url = _YTchannelToAtomFeed(url)
|
||||||
try:
|
try:
|
||||||
result = session.get(url, headers=sessionHeaders, params=sessionParams)
|
result = session.get(url, headers=sessionHeaders, params=sessionParams)
|
||||||
if result:
|
if result:
|
||||||
if int(len(result.text) / 1024) < maxFeedSizeKb and \
|
if int(len(result.text) / 1024) < maxFeedSizeKb and \
|
||||||
not containsInvalidChars(result.text):
|
not containsInvalidChars(result.text):
|
||||||
return xmlStrToDict(baseDir, domain, result.text,
|
return _xmlStrToDict(baseDir, domain, result.text,
|
||||||
moderated, mirrored,
|
moderated, mirrored,
|
||||||
maxPostsPerSource,
|
maxPostsPerSource,
|
||||||
maxFeedItemSizeKb,
|
maxFeedItemSizeKb,
|
||||||
|
|
@ -692,7 +692,7 @@ def getRSSfromDict(baseDir: str, newswire: {},
|
||||||
continue
|
continue
|
||||||
rssStr += '<item>\n'
|
rssStr += '<item>\n'
|
||||||
rssStr += ' <title>' + fields[0] + '</title>\n'
|
rssStr += ' <title>' + fields[0] + '</title>\n'
|
||||||
description = removeCDATA(firstParagraphFromString(fields[4]))
|
description = _removeCDATA(firstParagraphFromString(fields[4]))
|
||||||
rssStr += ' <description>' + description + '</description>\n'
|
rssStr += ' <description>' + description + '</description>\n'
|
||||||
url = fields[1]
|
url = fields[1]
|
||||||
if '://' not in url:
|
if '://' not in url:
|
||||||
|
|
@ -707,7 +707,7 @@ def getRSSfromDict(baseDir: str, newswire: {},
|
||||||
return rssStr
|
return rssStr
|
||||||
|
|
||||||
|
|
||||||
def isNewswireBlogPost(postJsonObject: {}) -> bool:
|
def _isNewswireBlogPost(postJsonObject: {}) -> bool:
|
||||||
"""Is the given object a blog post?
|
"""Is the given object a blog post?
|
||||||
There isn't any difference between a blog post and a newswire blog post
|
There isn't any difference between a blog post and a newswire blog post
|
||||||
but we may here need to check for different properties than
|
but we may here need to check for different properties than
|
||||||
|
|
@ -727,7 +727,7 @@ def isNewswireBlogPost(postJsonObject: {}) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def getHashtagsFromPost(postJsonObject: {}) -> []:
|
def _getHashtagsFromPost(postJsonObject: {}) -> []:
|
||||||
"""Returns a list of any hashtags within a post
|
"""Returns a list of any hashtags within a post
|
||||||
"""
|
"""
|
||||||
if not postJsonObject.get('object'):
|
if not postJsonObject.get('object'):
|
||||||
|
|
@ -753,7 +753,7 @@ def getHashtagsFromPost(postJsonObject: {}) -> []:
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
def addAccountBlogsToNewswire(baseDir: str, nickname: str, domain: str,
|
def _addAccountBlogsToNewswire(baseDir: str, nickname: str, domain: str,
|
||||||
newswire: {},
|
newswire: {},
|
||||||
maxBlogsPerAccount: int,
|
maxBlogsPerAccount: int,
|
||||||
indexFilename: str,
|
indexFilename: str,
|
||||||
|
|
@ -803,7 +803,7 @@ def addAccountBlogsToNewswire(baseDir: str, nickname: str, domain: str,
|
||||||
postJsonObject = None
|
postJsonObject = None
|
||||||
if fullPostFilename:
|
if fullPostFilename:
|
||||||
postJsonObject = loadJson(fullPostFilename)
|
postJsonObject = loadJson(fullPostFilename)
|
||||||
if isNewswireBlogPost(postJsonObject):
|
if _isNewswireBlogPost(postJsonObject):
|
||||||
published = postJsonObject['object']['published']
|
published = postJsonObject['object']['published']
|
||||||
published = published.replace('T', ' ')
|
published = published.replace('T', ' ')
|
||||||
published = published.replace('Z', '+00:00')
|
published = published.replace('Z', '+00:00')
|
||||||
|
|
@ -812,14 +812,15 @@ def addAccountBlogsToNewswire(baseDir: str, nickname: str, domain: str,
|
||||||
votes = loadJson(fullPostFilename + '.votes')
|
votes = loadJson(fullPostFilename + '.votes')
|
||||||
content = postJsonObject['object']['content']
|
content = postJsonObject['object']['content']
|
||||||
description = firstParagraphFromString(content)
|
description = firstParagraphFromString(content)
|
||||||
description = removeCDATA(description)
|
description = _removeCDATA(description)
|
||||||
addNewswireDictEntry(baseDir, domain,
|
tagsFromPost = _getHashtagsFromPost(postJsonObject)
|
||||||
|
_addNewswireDictEntry(baseDir, domain,
|
||||||
newswire, published,
|
newswire, published,
|
||||||
postJsonObject['object']['summary'],
|
postJsonObject['object']['summary'],
|
||||||
postJsonObject['object']['url'],
|
postJsonObject['object']['url'],
|
||||||
votes, fullPostFilename,
|
votes, fullPostFilename,
|
||||||
description, moderated, False,
|
description, moderated, False,
|
||||||
getHashtagsFromPost(postJsonObject),
|
tagsFromPost,
|
||||||
maxTags)
|
maxTags)
|
||||||
|
|
||||||
ctr += 1
|
ctr += 1
|
||||||
|
|
@ -827,7 +828,7 @@ def addAccountBlogsToNewswire(baseDir: str, nickname: str, domain: str,
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def addBlogsToNewswire(baseDir: str, domain: str, newswire: {},
|
def _addBlogsToNewswire(baseDir: str, domain: str, newswire: {},
|
||||||
maxBlogsPerAccount: int,
|
maxBlogsPerAccount: int,
|
||||||
maxTags: int) -> None:
|
maxTags: int) -> None:
|
||||||
"""Adds blogs from each user account into the newswire
|
"""Adds blogs from each user account into the newswire
|
||||||
|
|
@ -857,7 +858,7 @@ def addBlogsToNewswire(baseDir: str, domain: str, newswire: {},
|
||||||
blogsIndex = accountDir + '/tlblogs.index'
|
blogsIndex = accountDir + '/tlblogs.index'
|
||||||
if os.path.isfile(blogsIndex):
|
if os.path.isfile(blogsIndex):
|
||||||
domain = handle.split('@')[1]
|
domain = handle.split('@')[1]
|
||||||
addAccountBlogsToNewswire(baseDir, nickname, domain,
|
_addAccountBlogsToNewswire(baseDir, nickname, domain,
|
||||||
newswire, maxBlogsPerAccount,
|
newswire, maxBlogsPerAccount,
|
||||||
blogsIndex, maxTags)
|
blogsIndex, maxTags)
|
||||||
break
|
break
|
||||||
|
|
@ -926,7 +927,7 @@ def getDictFromNewswire(session, baseDir: str, domain: str,
|
||||||
result[dateStr] = item
|
result[dateStr] = item
|
||||||
|
|
||||||
# add blogs from each user account
|
# add blogs from each user account
|
||||||
addBlogsToNewswire(baseDir, domain, result,
|
_addBlogsToNewswire(baseDir, domain, result,
|
||||||
maxPostsPerSource, maxTags)
|
maxPostsPerSource, maxTags)
|
||||||
|
|
||||||
# sort into chronological order, latest first
|
# sort into chronological order, latest first
|
||||||
|
|
|
||||||
60
person.py
60
person.py
|
|
@ -134,7 +134,7 @@ def setOrganizationScheme(baseDir: str, nickname: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def accountExists(baseDir: str, nickname: str, domain: str) -> bool:
|
def _accountExists(baseDir: str, nickname: str, domain: str) -> bool:
|
||||||
"""Returns true if the given account exists
|
"""Returns true if the given account exists
|
||||||
"""
|
"""
|
||||||
if ':' in domain:
|
if ':' in domain:
|
||||||
|
|
@ -201,7 +201,7 @@ def getDefaultPersonContext() -> str:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def createPersonBase(baseDir: str, nickname: str, domain: str, port: int,
|
def _createPersonBase(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
httpPrefix: str, saveToFile: bool,
|
httpPrefix: str, saveToFile: bool,
|
||||||
manualFollowerApproval: bool,
|
manualFollowerApproval: bool,
|
||||||
password=None) -> (str, str, {}, {}):
|
password=None) -> (str, str, {}, {}):
|
||||||
|
|
@ -377,7 +377,7 @@ def registerAccount(baseDir: str, httpPrefix: str, domain: str, port: int,
|
||||||
manualFollowerApproval: bool) -> bool:
|
manualFollowerApproval: bool) -> bool:
|
||||||
"""Registers a new account from the web interface
|
"""Registers a new account from the web interface
|
||||||
"""
|
"""
|
||||||
if accountExists(baseDir, nickname, domain):
|
if _accountExists(baseDir, nickname, domain):
|
||||||
return False
|
return False
|
||||||
if not validNickname(domain, nickname):
|
if not validNickname(domain, nickname):
|
||||||
print('REGISTER: Nickname ' + nickname + ' is invalid')
|
print('REGISTER: Nickname ' + nickname + ' is invalid')
|
||||||
|
|
@ -449,7 +449,7 @@ def createPerson(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
return None, None, None, None
|
return None, None, None, None
|
||||||
|
|
||||||
(privateKeyPem, publicKeyPem,
|
(privateKeyPem, publicKeyPem,
|
||||||
newPerson, webfingerEndpoint) = createPersonBase(baseDir, nickname,
|
newPerson, webfingerEndpoint) = _createPersonBase(baseDir, nickname,
|
||||||
domain, port,
|
domain, port,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
saveToFile,
|
saveToFile,
|
||||||
|
|
@ -525,7 +525,7 @@ def createSharedInbox(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
httpPrefix: str) -> (str, str, {}, {}):
|
httpPrefix: str) -> (str, str, {}, {}):
|
||||||
"""Generates the shared inbox
|
"""Generates the shared inbox
|
||||||
"""
|
"""
|
||||||
return createPersonBase(baseDir, nickname, domain, port, httpPrefix,
|
return _createPersonBase(baseDir, nickname, domain, port, httpPrefix,
|
||||||
True, True, None)
|
True, True, None)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -722,48 +722,6 @@ def personBoxJson(recentPostsCache: {},
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def personInboxJson(recentPostsCache: {},
|
|
||||||
baseDir: str, domain: str, port: int, path: str,
|
|
||||||
httpPrefix: str, noOfItems: int) -> []:
|
|
||||||
"""Obtain the inbox feed for the given person
|
|
||||||
Authentication is expected to have already happened
|
|
||||||
"""
|
|
||||||
if '/inbox' not in path:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Only show the header by default
|
|
||||||
headerOnly = True
|
|
||||||
|
|
||||||
# handle page numbers
|
|
||||||
pageNumber = None
|
|
||||||
if '?page=' in path:
|
|
||||||
pageNumber = path.split('?page=')[1]
|
|
||||||
if pageNumber == 'true':
|
|
||||||
pageNumber = 1
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
pageNumber = int(pageNumber)
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
path = path.split('?page=')[0]
|
|
||||||
headerOnly = False
|
|
||||||
|
|
||||||
if not path.endswith('/inbox'):
|
|
||||||
return None
|
|
||||||
nickname = None
|
|
||||||
if path.startswith('/users/'):
|
|
||||||
nickname = path.replace('/users/', '', 1).replace('/inbox', '')
|
|
||||||
if path.startswith('/@'):
|
|
||||||
nickname = path.replace('/@', '', 1).replace('/inbox', '')
|
|
||||||
if not nickname:
|
|
||||||
return None
|
|
||||||
if not validNickname(domain, nickname):
|
|
||||||
return None
|
|
||||||
return createInbox(recentPostsCache, baseDir, nickname,
|
|
||||||
domain, port, httpPrefix,
|
|
||||||
noOfItems, headerOnly, pageNumber)
|
|
||||||
|
|
||||||
|
|
||||||
def setDisplayNickname(baseDir: str, nickname: str, domain: str,
|
def setDisplayNickname(baseDir: str, nickname: str, domain: str,
|
||||||
displayName: str) -> bool:
|
displayName: str) -> bool:
|
||||||
if len(displayName) > 32:
|
if len(displayName) > 32:
|
||||||
|
|
@ -800,7 +758,7 @@ def setBio(baseDir: str, nickname: str, domain: str, bio: str) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def unsuspendAccount(baseDir: str, nickname: str) -> None:
|
def reenableAccount(baseDir: str, nickname: str) -> None:
|
||||||
"""Removes an account suspention
|
"""Removes an account suspention
|
||||||
"""
|
"""
|
||||||
suspendedFilename = baseDir + '/accounts/suspended.txt'
|
suspendedFilename = baseDir + '/accounts/suspended.txt'
|
||||||
|
|
@ -887,7 +845,7 @@ def canRemovePost(baseDir: str, nickname: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def removeTagsForNickname(baseDir: str, nickname: str,
|
def _removeTagsForNickname(baseDir: str, nickname: str,
|
||||||
domain: str, port: int) -> None:
|
domain: str, port: int) -> None:
|
||||||
"""Removes tags for a nickname
|
"""Removes tags for a nickname
|
||||||
"""
|
"""
|
||||||
|
|
@ -939,10 +897,10 @@ def removeAccount(baseDir: str, nickname: str,
|
||||||
if moderator.strip('\n') == nickname:
|
if moderator.strip('\n') == nickname:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
unsuspendAccount(baseDir, nickname)
|
reenableAccount(baseDir, nickname)
|
||||||
handle = nickname + '@' + domain
|
handle = nickname + '@' + domain
|
||||||
removePassword(baseDir, nickname)
|
removePassword(baseDir, nickname)
|
||||||
removeTagsForNickname(baseDir, nickname, domain, port)
|
_removeTagsForNickname(baseDir, nickname, domain, port)
|
||||||
if os.path.isdir(baseDir + '/deactivated/' + handle):
|
if os.path.isdir(baseDir + '/deactivated/' + handle):
|
||||||
shutil.rmtree(baseDir + '/deactivated/' + handle)
|
shutil.rmtree(baseDir + '/deactivated/' + handle)
|
||||||
if os.path.isdir(baseDir + '/accounts/' + handle):
|
if os.path.isdir(baseDir + '/accounts/' + handle):
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ def getPetName(baseDir: str, nickname: str, domain: str,
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def getPetNameHandle(baseDir: str, nickname: str, domain: str,
|
def _getPetNameHandle(baseDir: str, nickname: str, domain: str,
|
||||||
petname: str) -> str:
|
petname: str) -> str:
|
||||||
"""Given a petname returns the handle
|
"""Given a petname returns the handle
|
||||||
"""
|
"""
|
||||||
|
|
@ -113,7 +113,7 @@ def resolvePetnames(baseDir: str, nickname: str, domain: str,
|
||||||
if not wrd.startswith('@'):
|
if not wrd.startswith('@'):
|
||||||
break
|
break
|
||||||
# does a petname handle exist for this?
|
# does a petname handle exist for this?
|
||||||
handle = getPetNameHandle(baseDir, nickname, domain, wrd)
|
handle = _getPetNameHandle(baseDir, nickname, domain, wrd)
|
||||||
if not handle:
|
if not handle:
|
||||||
continue
|
continue
|
||||||
# replace the petname with the handle
|
# replace the petname with the handle
|
||||||
|
|
|
||||||
243
posts.py
243
posts.py
|
|
@ -116,7 +116,7 @@ def noOfFollowersOnDomain(baseDir: str, handle: str,
|
||||||
return ctr
|
return ctr
|
||||||
|
|
||||||
|
|
||||||
def getPersonKey(nickname: str, domain: str, baseDir: str, keyType='public',
|
def _getPersonKey(nickname: str, domain: str, baseDir: str, keyType='public',
|
||||||
debug=False):
|
debug=False):
|
||||||
"""Returns the public or private key of a person
|
"""Returns the public or private key of a person
|
||||||
"""
|
"""
|
||||||
|
|
@ -136,7 +136,7 @@ def getPersonKey(nickname: str, domain: str, baseDir: str, keyType='public',
|
||||||
return keyPem
|
return keyPem
|
||||||
|
|
||||||
|
|
||||||
def cleanHtml(rawHtml: str) -> str:
|
def _cleanHtml(rawHtml: str) -> str:
|
||||||
# text=BeautifulSoup(rawHtml, 'html.parser').get_text()
|
# text=BeautifulSoup(rawHtml, 'html.parser').get_text()
|
||||||
text = rawHtml
|
text = rawHtml
|
||||||
return html.unescape(text)
|
return html.unescape(text)
|
||||||
|
|
@ -288,7 +288,7 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
|
||||||
avatarUrl, displayName
|
avatarUrl, displayName
|
||||||
|
|
||||||
|
|
||||||
def getPosts(session, outboxUrl: str, maxPosts: int,
|
def _getPosts(session, outboxUrl: str, maxPosts: int,
|
||||||
maxMentions: int,
|
maxMentions: int,
|
||||||
maxEmoji: int, maxAttachments: int,
|
maxEmoji: int, maxAttachments: int,
|
||||||
federationList: [],
|
federationList: [],
|
||||||
|
|
@ -445,7 +445,7 @@ def getPosts(session, outboxUrl: str, maxPosts: int,
|
||||||
sensitive = item['object']['sensitive']
|
sensitive = item['object']['sensitive']
|
||||||
|
|
||||||
if simple:
|
if simple:
|
||||||
print(cleanHtml(content) + '\n')
|
print(_cleanHtml(content) + '\n')
|
||||||
else:
|
else:
|
||||||
pprint(item)
|
pprint(item)
|
||||||
personPosts[item['id']] = {
|
personPosts[item['id']] = {
|
||||||
|
|
@ -453,7 +453,7 @@ def getPosts(session, outboxUrl: str, maxPosts: int,
|
||||||
"inreplyto": inReplyTo,
|
"inreplyto": inReplyTo,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
"html": content,
|
"html": content,
|
||||||
"plaintext": cleanHtml(content),
|
"plaintext": _cleanHtml(content),
|
||||||
"attachment": attachment,
|
"attachment": attachment,
|
||||||
"mentions": mentions,
|
"mentions": mentions,
|
||||||
"emoji": emoji,
|
"emoji": emoji,
|
||||||
|
|
@ -519,7 +519,7 @@ def getPostDomains(session, outboxUrl: str, maxPosts: int,
|
||||||
return postDomains
|
return postDomains
|
||||||
|
|
||||||
|
|
||||||
def getPostsForBlockedDomains(baseDir: str,
|
def _getPostsForBlockedDomains(baseDir: str,
|
||||||
session, outboxUrl: str, maxPosts: int,
|
session, outboxUrl: str, maxPosts: int,
|
||||||
maxMentions: int,
|
maxMentions: int,
|
||||||
maxEmoji: int, maxAttachments: int,
|
maxEmoji: int, maxAttachments: int,
|
||||||
|
|
@ -643,7 +643,7 @@ def savePostToBox(baseDir: str, httpPrefix: str, postId: str,
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def updateHashtagsIndex(baseDir: str, tag: {}, newPostId: str) -> None:
|
def _updateHashtagsIndex(baseDir: str, tag: {}, newPostId: str) -> None:
|
||||||
"""Writes the post url for hashtags to a file
|
"""Writes the post url for hashtags to a file
|
||||||
This allows posts for a hashtag to be quickly looked up
|
This allows posts for a hashtag to be quickly looked up
|
||||||
"""
|
"""
|
||||||
|
|
@ -677,7 +677,7 @@ def updateHashtagsIndex(baseDir: str, tag: {}, newPostId: str) -> None:
|
||||||
tagsFilename + ' ' + str(e))
|
tagsFilename + ' ' + str(e))
|
||||||
|
|
||||||
|
|
||||||
def addSchedulePost(baseDir: str, nickname: str, domain: str,
|
def _addSchedulePost(baseDir: str, nickname: str, domain: str,
|
||||||
eventDateStr: str, postId: str) -> None:
|
eventDateStr: str, postId: str) -> None:
|
||||||
"""Adds a scheduled post to the index
|
"""Adds a scheduled post to the index
|
||||||
"""
|
"""
|
||||||
|
|
@ -703,7 +703,7 @@ def addSchedulePost(baseDir: str, nickname: str, domain: str,
|
||||||
scheduleFile.close()
|
scheduleFile.close()
|
||||||
|
|
||||||
|
|
||||||
def appendEventFields(newPost: {},
|
def _appendEventFields(newPost: {},
|
||||||
eventUUID: str, eventStatus: str,
|
eventUUID: str, eventStatus: str,
|
||||||
anonymousParticipationEnabled: bool,
|
anonymousParticipationEnabled: bool,
|
||||||
repliesModerationOption: str,
|
repliesModerationOption: str,
|
||||||
|
|
@ -758,7 +758,7 @@ def validContentWarning(cw: str) -> str:
|
||||||
return cw
|
return cw
|
||||||
|
|
||||||
|
|
||||||
def loadAutoCW(baseDir: str, nickname: str, domain: str) -> []:
|
def _loadAutoCW(baseDir: str, nickname: str, domain: str) -> []:
|
||||||
"""Loads automatic CWs file and returns a list containing
|
"""Loads automatic CWs file and returns a list containing
|
||||||
the lines of the file
|
the lines of the file
|
||||||
"""
|
"""
|
||||||
|
|
@ -771,13 +771,13 @@ def loadAutoCW(baseDir: str, nickname: str, domain: str) -> []:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def addAutoCW(baseDir: str, nickname: str, domain: str,
|
def _addAutoCW(baseDir: str, nickname: str, domain: str,
|
||||||
subject: str, content: str) -> str:
|
subject: str, content: str) -> str:
|
||||||
"""Appends any automatic CW to the subject line
|
"""Appends any automatic CW to the subject line
|
||||||
and returns the new subject line
|
and returns the new subject line
|
||||||
"""
|
"""
|
||||||
newSubject = subject
|
newSubject = subject
|
||||||
autoCWList = loadAutoCW(baseDir, nickname, domain)
|
autoCWList = _loadAutoCW(baseDir, nickname, domain)
|
||||||
for cwRule in autoCWList:
|
for cwRule in autoCWList:
|
||||||
if '->' not in cwRule:
|
if '->' not in cwRule:
|
||||||
continue
|
continue
|
||||||
|
|
@ -793,10 +793,10 @@ def addAutoCW(baseDir: str, nickname: str, domain: str,
|
||||||
return newSubject
|
return newSubject
|
||||||
|
|
||||||
|
|
||||||
def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
def _createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
toUrl: str, ccUrl: str, httpPrefix: str, content: str,
|
toUrl: str, ccUrl: str, httpPrefix: str, content: str,
|
||||||
followersOnly: bool, saveToFile: bool, clientToServer: bool,
|
followersOnly: bool, saveToFile: bool,
|
||||||
commentsEnabled: bool,
|
clientToServer: bool, commentsEnabled: bool,
|
||||||
attachImageFilename: str,
|
attachImageFilename: str,
|
||||||
mediaType: str, imageDescription: str,
|
mediaType: str, imageDescription: str,
|
||||||
useBlurhash: bool, isModerationReport: bool,
|
useBlurhash: bool, isModerationReport: bool,
|
||||||
|
|
@ -812,7 +812,7 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
eventStatus=None, ticketUrl=None) -> {}:
|
eventStatus=None, ticketUrl=None) -> {}:
|
||||||
"""Creates a message
|
"""Creates a message
|
||||||
"""
|
"""
|
||||||
subject = addAutoCW(baseDir, nickname, domain, subject, content)
|
subject = _addAutoCW(baseDir, nickname, domain, subject, content)
|
||||||
|
|
||||||
if nickname != 'news':
|
if nickname != 'news':
|
||||||
mentionedRecipients = \
|
mentionedRecipients = \
|
||||||
|
|
@ -885,7 +885,7 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
if not tagExists(tag['type'], tag['name'], tags):
|
if not tagExists(tag['type'], tag['name'], tags):
|
||||||
tags.append(tag)
|
tags.append(tag)
|
||||||
if isPublic:
|
if isPublic:
|
||||||
updateHashtagsIndex(baseDir, tag, newPostId)
|
_updateHashtagsIndex(baseDir, tag, newPostId)
|
||||||
print('Content tags: ' + str(tags))
|
print('Content tags: ' + str(tags))
|
||||||
|
|
||||||
if inReplyTo and not sensitive:
|
if inReplyTo and not sensitive:
|
||||||
|
|
@ -1031,7 +1031,7 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
attachMedia(baseDir, httpPrefix, domain, port,
|
attachMedia(baseDir, httpPrefix, domain, port,
|
||||||
newPost['object'], attachImageFilename,
|
newPost['object'], attachImageFilename,
|
||||||
mediaType, imageDescription, useBlurhash)
|
mediaType, imageDescription, useBlurhash)
|
||||||
appendEventFields(newPost['object'], eventUUID, eventStatus,
|
_appendEventFields(newPost['object'], eventUUID, eventStatus,
|
||||||
anonymousParticipationEnabled,
|
anonymousParticipationEnabled,
|
||||||
repliesModerationOption,
|
repliesModerationOption,
|
||||||
category, joinMode,
|
category, joinMode,
|
||||||
|
|
@ -1079,7 +1079,7 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
attachMedia(baseDir, httpPrefix, domain, port,
|
attachMedia(baseDir, httpPrefix, domain, port,
|
||||||
newPost, attachImageFilename,
|
newPost, attachImageFilename,
|
||||||
mediaType, imageDescription, useBlurhash)
|
mediaType, imageDescription, useBlurhash)
|
||||||
appendEventFields(newPost, eventUUID, eventStatus,
|
_appendEventFields(newPost, eventUUID, eventStatus,
|
||||||
anonymousParticipationEnabled,
|
anonymousParticipationEnabled,
|
||||||
repliesModerationOption,
|
repliesModerationOption,
|
||||||
category, joinMode,
|
category, joinMode,
|
||||||
|
|
@ -1123,7 +1123,8 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
if schedulePost:
|
if schedulePost:
|
||||||
if eventDate and eventTime:
|
if eventDate and eventTime:
|
||||||
# add an item to the scheduled post index file
|
# add an item to the scheduled post index file
|
||||||
addSchedulePost(baseDir, nickname, domain, eventDateStr, newPostId)
|
_addSchedulePost(baseDir, nickname, domain,
|
||||||
|
eventDateStr, newPostId)
|
||||||
savePostToBox(baseDir, httpPrefix, newPostId,
|
savePostToBox(baseDir, httpPrefix, newPostId,
|
||||||
nickname, domain, newPost, 'scheduled')
|
nickname, domain, newPost, 'scheduled')
|
||||||
else:
|
else:
|
||||||
|
|
@ -1179,7 +1180,7 @@ def outboxMessageCreateWrap(httpPrefix: str,
|
||||||
return newPost
|
return newPost
|
||||||
|
|
||||||
|
|
||||||
def postIsAddressedToFollowers(baseDir: str,
|
def _postIsAddressedToFollowers(baseDir: str,
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
httpPrefix: str,
|
httpPrefix: str,
|
||||||
postJsonObject: {}) -> bool:
|
postJsonObject: {}) -> bool:
|
||||||
|
|
@ -1215,28 +1216,6 @@ def postIsAddressedToFollowers(baseDir: str,
|
||||||
return addressedToFollowers
|
return addressedToFollowers
|
||||||
|
|
||||||
|
|
||||||
def postIsAddressedToPublic(baseDir: str, postJsonObject: {}) -> bool:
|
|
||||||
"""Returns true if the given post is addressed to public
|
|
||||||
"""
|
|
||||||
if not postJsonObject.get('object'):
|
|
||||||
return False
|
|
||||||
if not postJsonObject['object'].get('to'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
publicUrl = 'https://www.w3.org/ns/activitystreams#Public'
|
|
||||||
|
|
||||||
# does the public url exist in 'to' or 'cc' lists?
|
|
||||||
addressedToPublic = False
|
|
||||||
if publicUrl in postJsonObject['object']['to']:
|
|
||||||
addressedToPublic = True
|
|
||||||
if not addressedToPublic:
|
|
||||||
if not postJsonObject['object'].get('cc'):
|
|
||||||
return False
|
|
||||||
if publicUrl in postJsonObject['object']['cc']:
|
|
||||||
addressedToPublic = True
|
|
||||||
return addressedToPublic
|
|
||||||
|
|
||||||
|
|
||||||
def createPublicPost(baseDir: str,
|
def createPublicPost(baseDir: str,
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
nickname: str, domain: str, port: int, httpPrefix: str,
|
||||||
content: str, followersOnly: bool, saveToFile: bool,
|
content: str, followersOnly: bool, saveToFile: bool,
|
||||||
|
|
@ -1249,7 +1228,7 @@ def createPublicPost(baseDir: str,
|
||||||
"""Public post
|
"""Public post
|
||||||
"""
|
"""
|
||||||
domainFull = getFullDomain(domain, port)
|
domainFull = getFullDomain(domain, port)
|
||||||
return createPostBase(baseDir, nickname, domain, port,
|
return _createPostBase(baseDir, nickname, domain, port,
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
httpPrefix + '://' + domainFull + '/users/' +
|
httpPrefix + '://' + domainFull + '/users/' +
|
||||||
nickname + '/followers',
|
nickname + '/followers',
|
||||||
|
|
@ -1350,7 +1329,7 @@ def createQuestionPost(baseDir: str,
|
||||||
"""
|
"""
|
||||||
domainFull = getFullDomain(domain, port)
|
domainFull = getFullDomain(domain, port)
|
||||||
messageJson = \
|
messageJson = \
|
||||||
createPostBase(baseDir, nickname, domain, port,
|
_createPostBase(baseDir, nickname, domain, port,
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
httpPrefix + '://' + domainFull + '/users/' +
|
httpPrefix + '://' + domainFull + '/users/' +
|
||||||
nickname + '/followers',
|
nickname + '/followers',
|
||||||
|
|
@ -1395,7 +1374,7 @@ def createUnlistedPost(baseDir: str,
|
||||||
"""Unlisted post. This has the #Public and followers links inverted.
|
"""Unlisted post. This has the #Public and followers links inverted.
|
||||||
"""
|
"""
|
||||||
domainFull = getFullDomain(domain, port)
|
domainFull = getFullDomain(domain, port)
|
||||||
return createPostBase(baseDir, nickname, domain, port,
|
return _createPostBase(baseDir, nickname, domain, port,
|
||||||
httpPrefix + '://' + domainFull + '/users/' +
|
httpPrefix + '://' + domainFull + '/users/' +
|
||||||
nickname + '/followers',
|
nickname + '/followers',
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
|
|
@ -1424,7 +1403,7 @@ def createFollowersOnlyPost(baseDir: str,
|
||||||
"""Followers only post
|
"""Followers only post
|
||||||
"""
|
"""
|
||||||
domainFull = getFullDomain(domain, port)
|
domainFull = getFullDomain(domain, port)
|
||||||
return createPostBase(baseDir, nickname, domain, port,
|
return _createPostBase(baseDir, nickname, domain, port,
|
||||||
httpPrefix + '://' + domainFull + '/users/' +
|
httpPrefix + '://' + domainFull + '/users/' +
|
||||||
nickname + '/followers',
|
nickname + '/followers',
|
||||||
None,
|
None,
|
||||||
|
|
@ -1473,7 +1452,7 @@ def createEventPost(baseDir: str,
|
||||||
if followersOnly:
|
if followersOnly:
|
||||||
toStr1 = toStr2
|
toStr1 = toStr2
|
||||||
toStr2 = None
|
toStr2 = None
|
||||||
return createPostBase(baseDir, nickname, domain, port,
|
return _createPostBase(baseDir, nickname, domain, port,
|
||||||
toStr1, toStr2,
|
toStr1, toStr2,
|
||||||
httpPrefix, content, followersOnly, saveToFile,
|
httpPrefix, content, followersOnly, saveToFile,
|
||||||
clientToServer, commentsEnabled,
|
clientToServer, commentsEnabled,
|
||||||
|
|
@ -1548,7 +1527,7 @@ def createDirectMessagePost(baseDir: str,
|
||||||
postTo = None
|
postTo = None
|
||||||
postCc = None
|
postCc = None
|
||||||
messageJson = \
|
messageJson = \
|
||||||
createPostBase(baseDir, nickname, domain, port,
|
_createPostBase(baseDir, nickname, domain, port,
|
||||||
postTo, postCc,
|
postTo, postCc,
|
||||||
httpPrefix, content, followersOnly, saveToFile,
|
httpPrefix, content, followersOnly, saveToFile,
|
||||||
clientToServer, commentsEnabled,
|
clientToServer, commentsEnabled,
|
||||||
|
|
@ -1638,7 +1617,7 @@ def createReportPost(baseDir: str,
|
||||||
handle = toNickname + '@' + domain
|
handle = toNickname + '@' + domain
|
||||||
|
|
||||||
postJsonObject = \
|
postJsonObject = \
|
||||||
createPostBase(baseDir, nickname, domain, port,
|
_createPostBase(baseDir, nickname, domain, port,
|
||||||
toUrl, postCc,
|
toUrl, postCc,
|
||||||
httpPrefix, content, followersOnly, saveToFile,
|
httpPrefix, content, followersOnly, saveToFile,
|
||||||
clientToServer, commentsEnabled,
|
clientToServer, commentsEnabled,
|
||||||
|
|
@ -1788,7 +1767,7 @@ def sendPost(projectVersion: str,
|
||||||
# sharedInbox is optional
|
# sharedInbox is optional
|
||||||
|
|
||||||
postJsonObject = \
|
postJsonObject = \
|
||||||
createPostBase(baseDir, nickname, domain, port,
|
_createPostBase(baseDir, nickname, domain, port,
|
||||||
toPersonId, cc, httpPrefix, content,
|
toPersonId, cc, httpPrefix, content,
|
||||||
followersOnly, saveToFile, clientToServer,
|
followersOnly, saveToFile, clientToServer,
|
||||||
commentsEnabled,
|
commentsEnabled,
|
||||||
|
|
@ -1801,7 +1780,7 @@ def sendPost(projectVersion: str,
|
||||||
None, None, None, None, None)
|
None, None, None, None, None)
|
||||||
|
|
||||||
# get the senders private key
|
# get the senders private key
|
||||||
privateKeyPem = getPersonKey(nickname, domain, baseDir, 'private')
|
privateKeyPem = _getPersonKey(nickname, domain, baseDir, 'private')
|
||||||
if len(privateKeyPem) == 0:
|
if len(privateKeyPem) == 0:
|
||||||
return 6
|
return 6
|
||||||
|
|
||||||
|
|
@ -1924,7 +1903,7 @@ def sendPostViaServer(projectVersion: str,
|
||||||
'/users/' + toNickname
|
'/users/' + toNickname
|
||||||
|
|
||||||
postJsonObject = \
|
postJsonObject = \
|
||||||
createPostBase(baseDir,
|
_createPostBase(baseDir,
|
||||||
fromNickname, fromDomain, fromPort,
|
fromNickname, fromDomain, fromPort,
|
||||||
toPersonId, cc, httpPrefix, content,
|
toPersonId, cc, httpPrefix, content,
|
||||||
followersOnly, saveToFile, clientToServer,
|
followersOnly, saveToFile, clientToServer,
|
||||||
|
|
@ -1957,8 +1936,9 @@ def sendPostViaServer(projectVersion: str,
|
||||||
'Content-type': 'application/json',
|
'Content-type': 'application/json',
|
||||||
'Authorization': authHeader
|
'Authorization': authHeader
|
||||||
}
|
}
|
||||||
|
postDumps = json.dumps(postJsonObject)
|
||||||
postResult = \
|
postResult = \
|
||||||
postJsonString(session, json.dumps(postJsonObject), [],
|
postJsonString(session, postDumps, [],
|
||||||
inboxUrl, headers, debug)
|
inboxUrl, headers, debug)
|
||||||
if not postResult:
|
if not postResult:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -1991,7 +1971,7 @@ def groupFollowersByDomain(baseDir: str, nickname: str, domain: str) -> {}:
|
||||||
return grouped
|
return grouped
|
||||||
|
|
||||||
|
|
||||||
def addFollowersToPublicPost(postJsonObject: {}) -> None:
|
def _addFollowersToPublicPost(postJsonObject: {}) -> None:
|
||||||
"""Adds followers entry to cc if it doesn't exist
|
"""Adds followers entry to cc if it doesn't exist
|
||||||
"""
|
"""
|
||||||
if not postJsonObject.get('actor'):
|
if not postJsonObject.get('actor'):
|
||||||
|
|
@ -2121,7 +2101,7 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
|
||||||
# sharedInbox is optional
|
# sharedInbox is optional
|
||||||
|
|
||||||
# get the senders private key
|
# get the senders private key
|
||||||
privateKeyPem = getPersonKey(nickname, domain, baseDir, 'private', debug)
|
privateKeyPem = _getPersonKey(nickname, domain, baseDir, 'private', debug)
|
||||||
if len(privateKeyPem) == 0:
|
if len(privateKeyPem) == 0:
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: Private key not found for ' +
|
print('DEBUG: Private key not found for ' +
|
||||||
|
|
@ -2134,7 +2114,7 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
|
||||||
return 7
|
return 7
|
||||||
postPath = inboxUrl.split(toDomain, 1)[1]
|
postPath = inboxUrl.split(toDomain, 1)[1]
|
||||||
|
|
||||||
addFollowersToPublicPost(postJsonObject)
|
_addFollowersToPublicPost(postJsonObject)
|
||||||
|
|
||||||
if not postJsonObject.get('signature'):
|
if not postJsonObject.get('signature'):
|
||||||
try:
|
try:
|
||||||
|
|
@ -2354,7 +2334,7 @@ def sendToNamedAddresses(session, baseDir: str,
|
||||||
personCache, debug, projectVersion)
|
personCache, debug, projectVersion)
|
||||||
|
|
||||||
|
|
||||||
def hasSharedInbox(session, httpPrefix: str, domain: str) -> bool:
|
def _hasSharedInbox(session, httpPrefix: str, domain: str) -> bool:
|
||||||
"""Returns true if the given domain has a shared inbox
|
"""Returns true if the given domain has a shared inbox
|
||||||
This tries the new and the old way of webfingering the shared inbox
|
This tries the new and the old way of webfingering the shared inbox
|
||||||
"""
|
"""
|
||||||
|
|
@ -2373,7 +2353,7 @@ def hasSharedInbox(session, httpPrefix: str, domain: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def sendingProfileUpdate(postJsonObject: {}) -> bool:
|
def _sendingProfileUpdate(postJsonObject: {}) -> bool:
|
||||||
"""Returns true if the given json is a profile update
|
"""Returns true if the given json is a profile update
|
||||||
"""
|
"""
|
||||||
if postJsonObject['type'] != 'Update':
|
if postJsonObject['type'] != 'Update':
|
||||||
|
|
@ -2408,7 +2388,7 @@ def sendToFollowers(session, baseDir: str,
|
||||||
if not session:
|
if not session:
|
||||||
print('WARN: No session for sendToFollowers')
|
print('WARN: No session for sendToFollowers')
|
||||||
return
|
return
|
||||||
if not postIsAddressedToFollowers(baseDir, nickname, domain,
|
if not _postIsAddressedToFollowers(baseDir, nickname, domain,
|
||||||
port, httpPrefix,
|
port, httpPrefix,
|
||||||
postJsonObject):
|
postJsonObject):
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -2450,7 +2430,7 @@ def sendToFollowers(session, baseDir: str,
|
||||||
print('Sending post to followers domain is active: ' +
|
print('Sending post to followers domain is active: ' +
|
||||||
followerDomainUrl)
|
followerDomainUrl)
|
||||||
|
|
||||||
withSharedInbox = hasSharedInbox(session, httpPrefix, followerDomain)
|
withSharedInbox = _hasSharedInbox(session, httpPrefix, followerDomain)
|
||||||
if debug:
|
if debug:
|
||||||
if withSharedInbox:
|
if withSharedInbox:
|
||||||
print(followerDomain + ' has shared inbox')
|
print(followerDomain + ' has shared inbox')
|
||||||
|
|
@ -2489,7 +2469,7 @@ def sendToFollowers(session, baseDir: str,
|
||||||
toNickname = 'inbox'
|
toNickname = 'inbox'
|
||||||
|
|
||||||
if toNickname != 'inbox' and postJsonObject.get('type'):
|
if toNickname != 'inbox' and postJsonObject.get('type'):
|
||||||
if sendingProfileUpdate(postJsonObject):
|
if _sendingProfileUpdate(postJsonObject):
|
||||||
print('Sending post to followers ' +
|
print('Sending post to followers ' +
|
||||||
'shared inbox of ' + toDomain)
|
'shared inbox of ' + toDomain)
|
||||||
toNickname = 'inbox'
|
toNickname = 'inbox'
|
||||||
|
|
@ -2576,7 +2556,7 @@ def createInbox(recentPostsCache: {},
|
||||||
session, baseDir: str, nickname: str, domain: str, port: int,
|
session, baseDir: str, nickname: str, domain: str, port: int,
|
||||||
httpPrefix: str, itemsPerPage: int, headerOnly: bool,
|
httpPrefix: str, itemsPerPage: int, headerOnly: bool,
|
||||||
pageNumber=None) -> {}:
|
pageNumber=None) -> {}:
|
||||||
return createBoxIndexed(recentPostsCache,
|
return _createBoxIndexed(recentPostsCache,
|
||||||
session, baseDir, 'inbox',
|
session, baseDir, 'inbox',
|
||||||
nickname, domain, port, httpPrefix,
|
nickname, domain, port, httpPrefix,
|
||||||
itemsPerPage, headerOnly, True,
|
itemsPerPage, headerOnly, True,
|
||||||
|
|
@ -2586,7 +2566,7 @@ def createInbox(recentPostsCache: {},
|
||||||
def createBookmarksTimeline(session, baseDir: str, nickname: str, domain: str,
|
def createBookmarksTimeline(session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str, itemsPerPage: int,
|
port: int, httpPrefix: str, itemsPerPage: int,
|
||||||
headerOnly: bool, pageNumber=None) -> {}:
|
headerOnly: bool, pageNumber=None) -> {}:
|
||||||
return createBoxIndexed({}, session, baseDir, 'tlbookmarks',
|
return _createBoxIndexed({}, session, baseDir, 'tlbookmarks',
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
port, httpPrefix, itemsPerPage, headerOnly,
|
port, httpPrefix, itemsPerPage, headerOnly,
|
||||||
True, 0, False, 0, pageNumber)
|
True, 0, False, 0, pageNumber)
|
||||||
|
|
@ -2596,7 +2576,7 @@ def createEventsTimeline(recentPostsCache: {},
|
||||||
session, baseDir: str, nickname: str, domain: str,
|
session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str, itemsPerPage: int,
|
port: int, httpPrefix: str, itemsPerPage: int,
|
||||||
headerOnly: bool, pageNumber=None) -> {}:
|
headerOnly: bool, pageNumber=None) -> {}:
|
||||||
return createBoxIndexed(recentPostsCache, session, baseDir, 'tlevents',
|
return _createBoxIndexed(recentPostsCache, session, baseDir, 'tlevents',
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
port, httpPrefix, itemsPerPage, headerOnly,
|
port, httpPrefix, itemsPerPage, headerOnly,
|
||||||
True, 0, False, 0, pageNumber)
|
True, 0, False, 0, pageNumber)
|
||||||
|
|
@ -2606,7 +2586,7 @@ def createDMTimeline(recentPostsCache: {},
|
||||||
session, baseDir: str, nickname: str, domain: str,
|
session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str, itemsPerPage: int,
|
port: int, httpPrefix: str, itemsPerPage: int,
|
||||||
headerOnly: bool, pageNumber=None) -> {}:
|
headerOnly: bool, pageNumber=None) -> {}:
|
||||||
return createBoxIndexed(recentPostsCache,
|
return _createBoxIndexed(recentPostsCache,
|
||||||
session, baseDir, 'dm', nickname,
|
session, baseDir, 'dm', nickname,
|
||||||
domain, port, httpPrefix, itemsPerPage,
|
domain, port, httpPrefix, itemsPerPage,
|
||||||
headerOnly, True, 0, False, 0, pageNumber)
|
headerOnly, True, 0, False, 0, pageNumber)
|
||||||
|
|
@ -2616,7 +2596,7 @@ def createRepliesTimeline(recentPostsCache: {},
|
||||||
session, baseDir: str, nickname: str, domain: str,
|
session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str, itemsPerPage: int,
|
port: int, httpPrefix: str, itemsPerPage: int,
|
||||||
headerOnly: bool, pageNumber=None) -> {}:
|
headerOnly: bool, pageNumber=None) -> {}:
|
||||||
return createBoxIndexed(recentPostsCache, session, baseDir, 'tlreplies',
|
return _createBoxIndexed(recentPostsCache, session, baseDir, 'tlreplies',
|
||||||
nickname, domain, port, httpPrefix,
|
nickname, domain, port, httpPrefix,
|
||||||
itemsPerPage, headerOnly, True,
|
itemsPerPage, headerOnly, True,
|
||||||
0, False, 0, pageNumber)
|
0, False, 0, pageNumber)
|
||||||
|
|
@ -2625,7 +2605,7 @@ def createRepliesTimeline(recentPostsCache: {},
|
||||||
def createBlogsTimeline(session, baseDir: str, nickname: str, domain: str,
|
def createBlogsTimeline(session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str, itemsPerPage: int,
|
port: int, httpPrefix: str, itemsPerPage: int,
|
||||||
headerOnly: bool, pageNumber=None) -> {}:
|
headerOnly: bool, pageNumber=None) -> {}:
|
||||||
return createBoxIndexed({}, session, baseDir, 'tlblogs', nickname,
|
return _createBoxIndexed({}, session, baseDir, 'tlblogs', nickname,
|
||||||
domain, port, httpPrefix,
|
domain, port, httpPrefix,
|
||||||
itemsPerPage, headerOnly, True,
|
itemsPerPage, headerOnly, True,
|
||||||
0, False, 0, pageNumber)
|
0, False, 0, pageNumber)
|
||||||
|
|
@ -2634,7 +2614,7 @@ def createBlogsTimeline(session, baseDir: str, nickname: str, domain: str,
|
||||||
def createFeaturesTimeline(session, baseDir: str, nickname: str, domain: str,
|
def createFeaturesTimeline(session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str, itemsPerPage: int,
|
port: int, httpPrefix: str, itemsPerPage: int,
|
||||||
headerOnly: bool, pageNumber=None) -> {}:
|
headerOnly: bool, pageNumber=None) -> {}:
|
||||||
return createBoxIndexed({}, session, baseDir, 'tlfeatures', nickname,
|
return _createBoxIndexed({}, session, baseDir, 'tlfeatures', nickname,
|
||||||
domain, port, httpPrefix,
|
domain, port, httpPrefix,
|
||||||
itemsPerPage, headerOnly, True,
|
itemsPerPage, headerOnly, True,
|
||||||
0, False, 0, pageNumber)
|
0, False, 0, pageNumber)
|
||||||
|
|
@ -2643,7 +2623,7 @@ def createFeaturesTimeline(session, baseDir: str, nickname: str, domain: str,
|
||||||
def createMediaTimeline(session, baseDir: str, nickname: str, domain: str,
|
def createMediaTimeline(session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str, itemsPerPage: int,
|
port: int, httpPrefix: str, itemsPerPage: int,
|
||||||
headerOnly: bool, pageNumber=None) -> {}:
|
headerOnly: bool, pageNumber=None) -> {}:
|
||||||
return createBoxIndexed({}, session, baseDir, 'tlmedia', nickname,
|
return _createBoxIndexed({}, session, baseDir, 'tlmedia', nickname,
|
||||||
domain, port, httpPrefix,
|
domain, port, httpPrefix,
|
||||||
itemsPerPage, headerOnly, True,
|
itemsPerPage, headerOnly, True,
|
||||||
0, False, 0, pageNumber)
|
0, False, 0, pageNumber)
|
||||||
|
|
@ -2654,7 +2634,7 @@ def createNewsTimeline(session, baseDir: str, nickname: str, domain: str,
|
||||||
headerOnly: bool, newswireVotesThreshold: int,
|
headerOnly: bool, newswireVotesThreshold: int,
|
||||||
positiveVoting: bool, votingTimeMins: int,
|
positiveVoting: bool, votingTimeMins: int,
|
||||||
pageNumber=None) -> {}:
|
pageNumber=None) -> {}:
|
||||||
return createBoxIndexed({}, session, baseDir, 'outbox', 'news',
|
return _createBoxIndexed({}, session, baseDir, 'outbox', 'news',
|
||||||
domain, port, httpPrefix,
|
domain, port, httpPrefix,
|
||||||
itemsPerPage, headerOnly, True,
|
itemsPerPage, headerOnly, True,
|
||||||
newswireVotesThreshold, positiveVoting,
|
newswireVotesThreshold, positiveVoting,
|
||||||
|
|
@ -2665,7 +2645,7 @@ def createOutbox(session, baseDir: str, nickname: str, domain: str,
|
||||||
port: int, httpPrefix: str,
|
port: int, httpPrefix: str,
|
||||||
itemsPerPage: int, headerOnly: bool, authorized: bool,
|
itemsPerPage: int, headerOnly: bool, authorized: bool,
|
||||||
pageNumber=None) -> {}:
|
pageNumber=None) -> {}:
|
||||||
return createBoxIndexed({}, session, baseDir, 'outbox',
|
return _createBoxIndexed({}, session, baseDir, 'outbox',
|
||||||
nickname, domain, port, httpPrefix,
|
nickname, domain, port, httpPrefix,
|
||||||
itemsPerPage, headerOnly, authorized,
|
itemsPerPage, headerOnly, authorized,
|
||||||
0, False, 0, pageNumber)
|
0, False, 0, pageNumber)
|
||||||
|
|
@ -2737,17 +2717,6 @@ def createModeration(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
return boxItems
|
return boxItems
|
||||||
|
|
||||||
|
|
||||||
def getStatusNumberFromPostFilename(filename) -> int:
|
|
||||||
"""Gets the status number from a post filename
|
|
||||||
eg. https:##testdomain.com:8085#users#testuser567#
|
|
||||||
statuses#1562958506952068.json
|
|
||||||
returns 156295850695206
|
|
||||||
"""
|
|
||||||
if '#statuses#' not in filename:
|
|
||||||
return None
|
|
||||||
return int(filename.split('#')[-1].replace('.json', ''))
|
|
||||||
|
|
||||||
|
|
||||||
def isDM(postJsonObject: {}) -> bool:
|
def isDM(postJsonObject: {}) -> bool:
|
||||||
"""Returns true if the given post is a DM
|
"""Returns true if the given post is a DM
|
||||||
"""
|
"""
|
||||||
|
|
@ -2849,67 +2818,7 @@ def isReply(postJsonObject: {}, actor: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def createBoxIndex(boxDir: str, postsInBoxDict: {}) -> int:
|
def _addPostStringToTimeline(postStr: str, boxname: str,
|
||||||
""" Creates an index for the given box
|
|
||||||
"""
|
|
||||||
postsCtr = 0
|
|
||||||
postsInPersonInbox = os.scandir(boxDir)
|
|
||||||
for postFilename in postsInPersonInbox:
|
|
||||||
postFilename = postFilename.name
|
|
||||||
if not postFilename.endswith('.json'):
|
|
||||||
continue
|
|
||||||
# extract the status number
|
|
||||||
statusNumber = getStatusNumberFromPostFilename(postFilename)
|
|
||||||
if statusNumber:
|
|
||||||
postsInBoxDict[statusNumber] = os.path.join(boxDir, postFilename)
|
|
||||||
postsCtr += 1
|
|
||||||
return postsCtr
|
|
||||||
|
|
||||||
|
|
||||||
def createSharedInboxIndex(baseDir: str, sharedBoxDir: str,
|
|
||||||
postsInBoxDict: {}, postsCtr: int,
|
|
||||||
nickname: str, domain: str) -> int:
|
|
||||||
""" Creates an index for the given shared inbox
|
|
||||||
"""
|
|
||||||
handle = nickname + '@' + domain
|
|
||||||
followingFilename = baseDir + '/accounts/' + handle + '/following.txt'
|
|
||||||
postsInSharedInbox = os.scandir(sharedBoxDir)
|
|
||||||
followingHandles = None
|
|
||||||
for postFilename in postsInSharedInbox:
|
|
||||||
postFilename = postFilename.name
|
|
||||||
if not postFilename.endswith('.json'):
|
|
||||||
continue
|
|
||||||
statusNumber = getStatusNumberFromPostFilename(postFilename)
|
|
||||||
if not statusNumber:
|
|
||||||
continue
|
|
||||||
|
|
||||||
sharedInboxFilename = os.path.join(sharedBoxDir, postFilename)
|
|
||||||
# get the actor from the shared post
|
|
||||||
postJsonObject = loadJson(sharedInboxFilename, 0)
|
|
||||||
if not postJsonObject:
|
|
||||||
print('WARN: json load exception createSharedInboxIndex')
|
|
||||||
continue
|
|
||||||
|
|
||||||
actorNickname = getNicknameFromActor(postJsonObject['actor'])
|
|
||||||
if not actorNickname:
|
|
||||||
continue
|
|
||||||
actorDomain, actorPort = getDomainFromActor(postJsonObject['actor'])
|
|
||||||
if not actorDomain:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# is the actor followed by this account?
|
|
||||||
if not followingHandles:
|
|
||||||
with open(followingFilename, 'r') as followingFile:
|
|
||||||
followingHandles = followingFile.read()
|
|
||||||
if actorNickname + '@' + actorDomain not in followingHandles:
|
|
||||||
continue
|
|
||||||
|
|
||||||
postsInBoxDict[statusNumber] = sharedInboxFilename
|
|
||||||
postsCtr += 1
|
|
||||||
return postsCtr
|
|
||||||
|
|
||||||
|
|
||||||
def addPostStringToTimeline(postStr: str, boxname: str,
|
|
||||||
postsInBox: [], boxActor: str) -> bool:
|
postsInBox: [], boxActor: str) -> bool:
|
||||||
""" is this a valid timeline post?
|
""" is this a valid timeline post?
|
||||||
"""
|
"""
|
||||||
|
|
@ -2946,7 +2855,7 @@ def addPostStringToTimeline(postStr: str, boxname: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def addPostToTimeline(filePath: str, boxname: str,
|
def _addPostToTimeline(filePath: str, boxname: str,
|
||||||
postsInBox: [], boxActor: str) -> bool:
|
postsInBox: [], boxActor: str) -> bool:
|
||||||
""" Reads a post from file and decides whether it is valid
|
""" Reads a post from file and decides whether it is valid
|
||||||
"""
|
"""
|
||||||
|
|
@ -2959,11 +2868,11 @@ def addPostToTimeline(filePath: str, boxname: str,
|
||||||
# append a replies identifier, which will later be removed
|
# append a replies identifier, which will later be removed
|
||||||
postStr += '<hasReplies>'
|
postStr += '<hasReplies>'
|
||||||
|
|
||||||
return addPostStringToTimeline(postStr, boxname, postsInBox, boxActor)
|
return _addPostStringToTimeline(postStr, boxname, postsInBox, boxActor)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def createBoxIndexed(recentPostsCache: {},
|
def _createBoxIndexed(recentPostsCache: {},
|
||||||
session, baseDir: str, boxname: str,
|
session, baseDir: str, boxname: str,
|
||||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
nickname: str, domain: str, port: int, httpPrefix: str,
|
||||||
itemsPerPage: int, headerOnly: bool, authorized: bool,
|
itemsPerPage: int, headerOnly: bool, authorized: bool,
|
||||||
|
|
@ -3099,7 +3008,7 @@ def createBoxIndexed(recentPostsCache: {},
|
||||||
if postUrl in recentPostsCache['index']:
|
if postUrl in recentPostsCache['index']:
|
||||||
if recentPostsCache['json'].get(postUrl):
|
if recentPostsCache['json'].get(postUrl):
|
||||||
url = recentPostsCache['json'][postUrl]
|
url = recentPostsCache['json'][postUrl]
|
||||||
addPostStringToTimeline(url,
|
_addPostStringToTimeline(url,
|
||||||
boxname, postsInBox,
|
boxname, postsInBox,
|
||||||
boxActor)
|
boxActor)
|
||||||
postsCtr += 1
|
postsCtr += 1
|
||||||
|
|
@ -3110,7 +3019,7 @@ def createBoxIndexed(recentPostsCache: {},
|
||||||
locatePost(baseDir, nickname,
|
locatePost(baseDir, nickname,
|
||||||
domain, postUrl, False)
|
domain, postUrl, False)
|
||||||
if fullPostFilename:
|
if fullPostFilename:
|
||||||
addPostToTimeline(fullPostFilename, boxname,
|
_addPostToTimeline(fullPostFilename, boxname,
|
||||||
postsInBox, boxActor)
|
postsInBox, boxActor)
|
||||||
else:
|
else:
|
||||||
# if this is the features timeline
|
# if this is the features timeline
|
||||||
|
|
@ -3119,7 +3028,7 @@ def createBoxIndexed(recentPostsCache: {},
|
||||||
locatePost(baseDir, timelineNickname,
|
locatePost(baseDir, timelineNickname,
|
||||||
domain, postUrl, False)
|
domain, postUrl, False)
|
||||||
if fullPostFilename:
|
if fullPostFilename:
|
||||||
addPostToTimeline(fullPostFilename, boxname,
|
_addPostToTimeline(fullPostFilename, boxname,
|
||||||
postsInBox, boxActor)
|
postsInBox, boxActor)
|
||||||
else:
|
else:
|
||||||
print('WARN: unable to locate post ' + postUrl)
|
print('WARN: unable to locate post ' + postUrl)
|
||||||
|
|
@ -3407,7 +3316,7 @@ def getPublicPostsOfPerson(baseDir: str, nickname: str, domain: str,
|
||||||
maxMentions = 10
|
maxMentions = 10
|
||||||
maxEmoji = 10
|
maxEmoji = 10
|
||||||
maxAttachments = 5
|
maxAttachments = 5
|
||||||
getPosts(session, personUrl, 30, maxMentions, maxEmoji,
|
_getPosts(session, personUrl, 30, maxMentions, maxEmoji,
|
||||||
maxAttachments, federationList,
|
maxAttachments, federationList,
|
||||||
personCache, raw, simple, debug,
|
personCache, raw, simple, debug,
|
||||||
projectVersion, httpPrefix, domain)
|
projectVersion, httpPrefix, domain)
|
||||||
|
|
@ -3506,7 +3415,7 @@ def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
|
||||||
domainsInfo[d] = []
|
domainsInfo[d] = []
|
||||||
|
|
||||||
blockedPosts = \
|
blockedPosts = \
|
||||||
getPostsForBlockedDomains(baseDir, session, personUrl, maxPosts,
|
_getPostsForBlockedDomains(baseDir, session, personUrl, maxPosts,
|
||||||
maxMentions,
|
maxMentions,
|
||||||
maxEmoji, maxAttachments,
|
maxEmoji, maxAttachments,
|
||||||
federationList,
|
federationList,
|
||||||
|
|
@ -3560,7 +3469,7 @@ def getPublicPostDomainsBlocked(session, baseDir: str,
|
||||||
return blockedDomains
|
return blockedDomains
|
||||||
|
|
||||||
|
|
||||||
def getNonMutualsOfPerson(baseDir: str,
|
def _getNonMutualsOfPerson(baseDir: str,
|
||||||
nickname: str, domain: str) -> []:
|
nickname: str, domain: str) -> []:
|
||||||
"""Returns the followers who are not mutuals of a person
|
"""Returns the followers who are not mutuals of a person
|
||||||
i.e. accounts which follow you but you don't follow them
|
i.e. accounts which follow you but you don't follow them
|
||||||
|
|
@ -3583,7 +3492,7 @@ def checkDomains(session, baseDir: str,
|
||||||
maxBlockedDomains: int, singleCheck: bool):
|
maxBlockedDomains: int, singleCheck: bool):
|
||||||
"""Checks follower accounts for references to globally blocked domains
|
"""Checks follower accounts for references to globally blocked domains
|
||||||
"""
|
"""
|
||||||
nonMutuals = getNonMutualsOfPerson(baseDir, nickname, domain)
|
nonMutuals = _getNonMutualsOfPerson(baseDir, nickname, domain)
|
||||||
if not nonMutuals:
|
if not nonMutuals:
|
||||||
print('No non-mutual followers were found')
|
print('No non-mutual followers were found')
|
||||||
return
|
return
|
||||||
|
|
@ -3707,7 +3616,7 @@ def populateRepliesJson(baseDir: str, nickname: str, domain: str,
|
||||||
repliesJson['orderedItems'].append(pjo)
|
repliesJson['orderedItems'].append(pjo)
|
||||||
|
|
||||||
|
|
||||||
def rejectAnnounce(announceFilename: str):
|
def _rejectAnnounce(announceFilename: str):
|
||||||
"""Marks an announce as rejected
|
"""Marks an announce as rejected
|
||||||
"""
|
"""
|
||||||
if not os.path.isfile(announceFilename + '.reject'):
|
if not os.path.isfile(announceFilename + '.reject'):
|
||||||
|
|
@ -3792,40 +3701,40 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
|
||||||
if not isinstance(announcedJson, dict):
|
if not isinstance(announcedJson, dict):
|
||||||
print('WARN: announce json is not a dict - ' +
|
print('WARN: announce json is not a dict - ' +
|
||||||
postJsonObject['object'])
|
postJsonObject['object'])
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
if not announcedJson.get('id'):
|
if not announcedJson.get('id'):
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
if '/statuses/' not in announcedJson['id']:
|
if '/statuses/' not in announcedJson['id']:
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
if '/users/' not in announcedJson['id'] and \
|
if '/users/' not in announcedJson['id'] and \
|
||||||
'/accounts/' not in announcedJson['id'] and \
|
'/accounts/' not in announcedJson['id'] and \
|
||||||
'/channel/' not in announcedJson['id'] and \
|
'/channel/' not in announcedJson['id'] and \
|
||||||
'/profile/' not in announcedJson['id']:
|
'/profile/' not in announcedJson['id']:
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
if not announcedJson.get('type'):
|
if not announcedJson.get('type'):
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
# pprint(announcedJson)
|
# pprint(announcedJson)
|
||||||
return None
|
return None
|
||||||
if announcedJson['type'] != 'Note' and \
|
if announcedJson['type'] != 'Note' and \
|
||||||
announcedJson['type'] != 'Article':
|
announcedJson['type'] != 'Article':
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
# pprint(announcedJson)
|
# pprint(announcedJson)
|
||||||
return None
|
return None
|
||||||
if not announcedJson.get('content'):
|
if not announcedJson.get('content'):
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
if not announcedJson.get('published'):
|
if not announcedJson.get('published'):
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
if not validPostDate(announcedJson['published']):
|
if not validPostDate(announcedJson['published']):
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
if isFiltered(baseDir, nickname, domain, announcedJson['content']):
|
if isFiltered(baseDir, nickname, domain, announcedJson['content']):
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
# remove any long words
|
# remove any long words
|
||||||
announcedJson['content'] = \
|
announcedJson['content'] = \
|
||||||
|
|
@ -3841,7 +3750,7 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
|
||||||
actorNickname, actorDomain, actorPort,
|
actorNickname, actorDomain, actorPort,
|
||||||
announcedJson)
|
announcedJson)
|
||||||
if announcedJson['type'] != 'Create':
|
if announcedJson['type'] != 'Create':
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
# pprint(announcedJson)
|
# pprint(announcedJson)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -3858,7 +3767,7 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
|
||||||
attributedDomain = getFullDomain(attributedDomain, attributedPort)
|
attributedDomain = getFullDomain(attributedDomain, attributedPort)
|
||||||
if isBlocked(baseDir, nickname, domain,
|
if isBlocked(baseDir, nickname, domain,
|
||||||
attributedNickname, attributedDomain):
|
attributedNickname, attributedDomain):
|
||||||
rejectAnnounce(announceFilename)
|
_rejectAnnounce(announceFilename)
|
||||||
return None
|
return None
|
||||||
postJsonObject = announcedJson
|
postJsonObject = announcedJson
|
||||||
replaceYouTube(postJsonObject, YTReplacementDomain)
|
replaceYouTube(postJsonObject, YTReplacementDomain)
|
||||||
|
|
|
||||||
16
roles.py
16
roles.py
|
|
@ -63,7 +63,7 @@ def clearEditorStatus(baseDir: str) -> None:
|
||||||
saveJson(actorJson, filename)
|
saveJson(actorJson, filename)
|
||||||
|
|
||||||
|
|
||||||
def addModerator(baseDir: str, nickname: str, domain: str) -> None:
|
def _addModerator(baseDir: str, nickname: str, domain: str) -> None:
|
||||||
"""Adds a moderator nickname to the file
|
"""Adds a moderator nickname to the file
|
||||||
"""
|
"""
|
||||||
if ':' in domain:
|
if ':' in domain:
|
||||||
|
|
@ -92,7 +92,7 @@ def addModerator(baseDir: str, nickname: str, domain: str) -> None:
|
||||||
f.write(nickname + '\n')
|
f.write(nickname + '\n')
|
||||||
|
|
||||||
|
|
||||||
def removeModerator(baseDir: str, nickname: str):
|
def _removeModerator(baseDir: str, nickname: str):
|
||||||
"""Removes a moderator nickname from the file
|
"""Removes a moderator nickname from the file
|
||||||
"""
|
"""
|
||||||
moderatorsFile = baseDir + '/accounts/moderators.txt'
|
moderatorsFile = baseDir + '/accounts/moderators.txt'
|
||||||
|
|
@ -125,7 +125,7 @@ def setRole(baseDir: str, nickname: str, domain: str,
|
||||||
if role:
|
if role:
|
||||||
# add the role
|
# add the role
|
||||||
if project == 'instance' and 'role' == 'moderator':
|
if project == 'instance' and 'role' == 'moderator':
|
||||||
addModerator(baseDir, nickname, domain)
|
_addModerator(baseDir, nickname, domain)
|
||||||
if actorJson['roles'].get(project):
|
if actorJson['roles'].get(project):
|
||||||
if role not in actorJson['roles'][project]:
|
if role not in actorJson['roles'][project]:
|
||||||
actorJson['roles'][project].append(role)
|
actorJson['roles'][project].append(role)
|
||||||
|
|
@ -134,7 +134,7 @@ def setRole(baseDir: str, nickname: str, domain: str,
|
||||||
else:
|
else:
|
||||||
# remove the role
|
# remove the role
|
||||||
if project == 'instance':
|
if project == 'instance':
|
||||||
removeModerator(baseDir, nickname)
|
_removeModerator(baseDir, nickname)
|
||||||
if actorJson['roles'].get(project):
|
if actorJson['roles'].get(project):
|
||||||
actorJson['roles'][project].remove(role)
|
actorJson['roles'][project].remove(role)
|
||||||
# if the project contains no roles then remove it
|
# if the project contains no roles then remove it
|
||||||
|
|
@ -144,7 +144,7 @@ def setRole(baseDir: str, nickname: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def getRoles(baseDir: str, nickname: str, domain: str,
|
def _getRoles(baseDir: str, nickname: str, domain: str,
|
||||||
project: str) -> []:
|
project: str) -> []:
|
||||||
"""Returns the roles for a given person on a given project
|
"""Returns the roles for a given person on a given project
|
||||||
"""
|
"""
|
||||||
|
|
@ -198,7 +198,7 @@ def outboxDelegate(baseDir: str, authenticatedNickname: str,
|
||||||
# instance delegators can delagate to other projects
|
# instance delegators can delagate to other projects
|
||||||
# than their own
|
# than their own
|
||||||
canDelegate = False
|
canDelegate = False
|
||||||
delegatorRoles = getRoles(baseDir, delegatorNickname,
|
delegatorRoles = _getRoles(baseDir, delegatorNickname,
|
||||||
domain, 'instance')
|
domain, 'instance')
|
||||||
if delegatorRoles:
|
if delegatorRoles:
|
||||||
if 'delegator' in delegatorRoles:
|
if 'delegator' in delegatorRoles:
|
||||||
|
|
@ -207,7 +207,7 @@ def outboxDelegate(baseDir: str, authenticatedNickname: str,
|
||||||
if not canDelegate:
|
if not canDelegate:
|
||||||
canDelegate = True
|
canDelegate = True
|
||||||
# non-instance delegators can only delegate within their project
|
# non-instance delegators can only delegate within their project
|
||||||
delegatorRoles = getRoles(baseDir, delegatorNickname,
|
delegatorRoles = _getRoles(baseDir, delegatorNickname,
|
||||||
domain, project)
|
domain, project)
|
||||||
if delegatorRoles:
|
if delegatorRoles:
|
||||||
if 'delegator' not in delegatorRoles:
|
if 'delegator' not in delegatorRoles:
|
||||||
|
|
@ -230,7 +230,7 @@ def outboxDelegate(baseDir: str, authenticatedNickname: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# what roles is this person already assigned to?
|
# what roles is this person already assigned to?
|
||||||
existingRoles = getRoles(baseDir, nickname, domain, project)
|
existingRoles = _getRoles(baseDir, nickname, domain, project)
|
||||||
if existingRoles:
|
if existingRoles:
|
||||||
if role in existingRoles:
|
if role in existingRoles:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ from utils import loadJson
|
||||||
from outbox import postMessageToOutbox
|
from outbox import postMessageToOutbox
|
||||||
|
|
||||||
|
|
||||||
def updatePostSchedule(baseDir: str, handle: str, httpd,
|
def _updatePostSchedule(baseDir: str, handle: str, httpd,
|
||||||
maxScheduledPosts: int) -> None:
|
maxScheduledPosts: int) -> None:
|
||||||
"""Checks if posts are due to be delivered and if so moves them to the outbox
|
"""Checks if posts are due to be delivered and if so moves them to the outbox
|
||||||
"""
|
"""
|
||||||
|
|
@ -104,7 +104,9 @@ def updatePostSchedule(baseDir: str, handle: str, httpd,
|
||||||
httpd.proxyType,
|
httpd.proxyType,
|
||||||
httpd.projectVersion,
|
httpd.projectVersion,
|
||||||
httpd.debug,
|
httpd.debug,
|
||||||
httpd.YTReplacementDomain):
|
httpd.YTReplacementDomain,
|
||||||
|
httpd.showPublishedDateOnly,
|
||||||
|
httpd.allowLocalNetworkAccess):
|
||||||
indexLines.remove(line)
|
indexLines.remove(line)
|
||||||
os.remove(postFilename)
|
os.remove(postFilename)
|
||||||
continue
|
continue
|
||||||
|
|
@ -145,7 +147,7 @@ def runPostSchedule(baseDir: str, httpd, maxScheduledPosts: int):
|
||||||
baseDir + '/accounts/' + account + '/schedule.index'
|
baseDir + '/accounts/' + account + '/schedule.index'
|
||||||
if not os.path.isfile(scheduleIndexFilename):
|
if not os.path.isfile(scheduleIndexFilename):
|
||||||
continue
|
continue
|
||||||
updatePostSchedule(baseDir, account, httpd, maxScheduledPosts)
|
_updatePostSchedule(baseDir, account, httpd, maxScheduledPosts)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,11 +176,11 @@ def expireShares(baseDir: str) -> None:
|
||||||
continue
|
continue
|
||||||
nickname = account.split('@')[0]
|
nickname = account.split('@')[0]
|
||||||
domain = account.split('@')[1]
|
domain = account.split('@')[1]
|
||||||
expireSharesForAccount(baseDir, nickname, domain)
|
_expireSharesForAccount(baseDir, nickname, domain)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def expireSharesForAccount(baseDir: str, nickname: str, domain: str) -> None:
|
def _expireSharesForAccount(baseDir: str, nickname: str, domain: str) -> None:
|
||||||
"""Removes expired items from shares for a particular account
|
"""Removes expired items from shares for a particular account
|
||||||
"""
|
"""
|
||||||
handleDomain = domain
|
handleDomain = domain
|
||||||
|
|
|
||||||
11
skills.py
11
skills.py
|
|
@ -41,17 +41,6 @@ def setSkillLevel(baseDir: str, nickname: str, domain: str,
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def setSkills(baseDir: str, nickname: str, domain: str, skills: {}) -> None:
|
|
||||||
actorFilename = baseDir + '/accounts/' + nickname + '@' + domain + '.json'
|
|
||||||
if not os.path.isfile(actorFilename):
|
|
||||||
return False
|
|
||||||
|
|
||||||
actorJson = loadJson(actorFilename)
|
|
||||||
if actorJson:
|
|
||||||
actorJson['skills'] = skills
|
|
||||||
saveJson(actorJson, actorFilename)
|
|
||||||
|
|
||||||
|
|
||||||
def getSkills(baseDir: str, nickname: str, domain: str) -> []:
|
def getSkills(baseDir: str, nickname: str, domain: str) -> []:
|
||||||
"""Returns the skills for a given person
|
"""Returns the skills for a given person
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
373
tests.py
373
tests.py
|
|
@ -49,8 +49,8 @@ from utils import getStatusNumber
|
||||||
from utils import getFollowersOfPerson
|
from utils import getFollowersOfPerson
|
||||||
from utils import removeHtml
|
from utils import removeHtml
|
||||||
from follow import followerOfPerson
|
from follow import followerOfPerson
|
||||||
from follow import unfollowPerson
|
from follow import unfollowAccount
|
||||||
from follow import unfollowerOfPerson
|
from follow import unfollowerOfAccount
|
||||||
from follow import sendFollowRequest
|
from follow import sendFollowRequest
|
||||||
from person import createPerson
|
from person import createPerson
|
||||||
from person import setDisplayNickname
|
from person import setDisplayNickname
|
||||||
|
|
@ -73,7 +73,7 @@ from delete import sendDeleteViaServer
|
||||||
from inbox import jsonPostAllowsComments
|
from inbox import jsonPostAllowsComments
|
||||||
from inbox import validInbox
|
from inbox import validInbox
|
||||||
from inbox import validInboxFilenames
|
from inbox import validInboxFilenames
|
||||||
from inbox import guessHashtagCategory
|
from categories import guessHashtagCategory
|
||||||
from content import htmlReplaceEmailQuote
|
from content import htmlReplaceEmailQuote
|
||||||
from content import htmlReplaceQuoteMarks
|
from content import htmlReplaceQuoteMarks
|
||||||
from content import dangerousMarkup
|
from content import dangerousMarkup
|
||||||
|
|
@ -101,7 +101,7 @@ thrBob = None
|
||||||
thrEve = None
|
thrEve = None
|
||||||
|
|
||||||
|
|
||||||
def testHttpsigBase(withDigest):
|
def _testHttpsigBase(withDigest):
|
||||||
print('testHttpsig(' + str(withDigest) + ')')
|
print('testHttpsig(' + str(withDigest) + ')')
|
||||||
|
|
||||||
baseDir = os.getcwd()
|
baseDir = os.getcwd()
|
||||||
|
|
@ -206,8 +206,8 @@ def testHttpsigBase(withDigest):
|
||||||
|
|
||||||
|
|
||||||
def testHttpsig():
|
def testHttpsig():
|
||||||
testHttpsigBase(True)
|
_testHttpsigBase(True)
|
||||||
testHttpsigBase(False)
|
_testHttpsigBase(False)
|
||||||
|
|
||||||
|
|
||||||
def testCache():
|
def testCache():
|
||||||
|
|
@ -274,19 +274,44 @@ def createServerAlice(path: str, domain: str, port: int,
|
||||||
followerOfPerson(path, nickname, domain, 'bob', bobAddress,
|
followerOfPerson(path, nickname, domain, 'bob', bobAddress,
|
||||||
federationList, False)
|
federationList, False)
|
||||||
if hasPosts:
|
if hasPosts:
|
||||||
|
testFollowersOnly = False
|
||||||
|
testSaveToFile = True
|
||||||
clientToServer = False
|
clientToServer = False
|
||||||
|
testCommentsEnabled = True
|
||||||
|
testAttachImageFilename = None
|
||||||
|
testMediaType = None
|
||||||
|
testImageDescription = None
|
||||||
createPublicPost(path, nickname, domain, port, httpPrefix,
|
createPublicPost(path, nickname, domain, port, httpPrefix,
|
||||||
"No wise fish would go anywhere without a porpoise",
|
"No wise fish would go anywhere without a porpoise",
|
||||||
False, True, clientToServer, True,
|
testFollowersOnly,
|
||||||
None, None, useBlurhash)
|
testSaveToFile,
|
||||||
|
clientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType,
|
||||||
|
testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(path, nickname, domain, port, httpPrefix,
|
createPublicPost(path, nickname, domain, port, httpPrefix,
|
||||||
"Curiouser and curiouser!", False, True,
|
"Curiouser and curiouser!",
|
||||||
clientToServer, True, None, None, useBlurhash)
|
testFollowersOnly,
|
||||||
|
testSaveToFile,
|
||||||
|
clientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType,
|
||||||
|
testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(path, nickname, domain, port, httpPrefix,
|
createPublicPost(path, nickname, domain, port, httpPrefix,
|
||||||
"In the gardens of memory, in the palace " +
|
"In the gardens of memory, in the palace " +
|
||||||
"of dreams, that is where you and I shall meet",
|
"of dreams, that is where you and I shall meet",
|
||||||
False, True, clientToServer, True,
|
testFollowersOnly,
|
||||||
None, None, useBlurhash)
|
testSaveToFile,
|
||||||
|
clientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType,
|
||||||
|
testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
global testServerAliceRunning
|
global testServerAliceRunning
|
||||||
testServerAliceRunning = True
|
testServerAliceRunning = True
|
||||||
maxMentions = 10
|
maxMentions = 10
|
||||||
|
|
@ -346,19 +371,43 @@ def createServerBob(path: str, domain: str, port: int,
|
||||||
followerOfPerson(path, nickname, domain,
|
followerOfPerson(path, nickname, domain,
|
||||||
'alice', aliceAddress, federationList, False)
|
'alice', aliceAddress, federationList, False)
|
||||||
if hasPosts:
|
if hasPosts:
|
||||||
|
testFollowersOnly = False
|
||||||
|
testSaveToFile = True
|
||||||
|
testCommentsEnabled = True
|
||||||
|
testAttachImageFilename = None
|
||||||
|
testImageDescription = None
|
||||||
|
testMediaType = None
|
||||||
createPublicPost(path, nickname, domain, port, httpPrefix,
|
createPublicPost(path, nickname, domain, port, httpPrefix,
|
||||||
"It's your life, live it your way.",
|
"It's your life, live it your way.",
|
||||||
False, True, clientToServer, True,
|
testFollowersOnly,
|
||||||
None, None, useBlurhash)
|
testSaveToFile,
|
||||||
|
clientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType,
|
||||||
|
testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(path, nickname, domain, port, httpPrefix,
|
createPublicPost(path, nickname, domain, port, httpPrefix,
|
||||||
"One of the things I've realised is that " +
|
"One of the things I've realised is that " +
|
||||||
"I am very simple",
|
"I am very simple",
|
||||||
False, True, clientToServer, True,
|
testFollowersOnly,
|
||||||
None, None, useBlurhash)
|
testSaveToFile,
|
||||||
|
clientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType,
|
||||||
|
testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
createPublicPost(path, nickname, domain, port, httpPrefix,
|
createPublicPost(path, nickname, domain, port, httpPrefix,
|
||||||
"Quantum physics is a bit of a passion of mine",
|
"Quantum physics is a bit of a passion of mine",
|
||||||
False, True, clientToServer, True,
|
testFollowersOnly,
|
||||||
None, None, useBlurhash)
|
testSaveToFile,
|
||||||
|
clientToServer,
|
||||||
|
testCommentsEnabled,
|
||||||
|
testAttachImageFilename,
|
||||||
|
testMediaType,
|
||||||
|
testImageDescription,
|
||||||
|
useBlurhash)
|
||||||
global testServerBobRunning
|
global testServerBobRunning
|
||||||
testServerBobRunning = True
|
testServerBobRunning = True
|
||||||
maxMentions = 10
|
maxMentions = 10
|
||||||
|
|
@ -601,10 +650,12 @@ def testPostMessageBetweenServers():
|
||||||
print('\n\n*******************************************************')
|
print('\n\n*******************************************************')
|
||||||
print("Bob likes Alice's post")
|
print("Bob likes Alice's post")
|
||||||
|
|
||||||
|
aliceDomainStr = aliceDomain + ':' + str(alicePort)
|
||||||
followerOfPerson(bobDir, 'bob', bobDomain, 'alice',
|
followerOfPerson(bobDir, 'bob', bobDomain, 'alice',
|
||||||
aliceDomain + ':' + str(alicePort), federationList, False)
|
aliceDomainStr, federationList, False)
|
||||||
|
bobDomainStr = bobDomain + ':' + str(bobPort)
|
||||||
followPerson(aliceDir, 'alice', aliceDomain, 'bob',
|
followPerson(aliceDir, 'alice', aliceDomain, 'bob',
|
||||||
bobDomain + ':' + str(bobPort), federationList, False)
|
bobDomainStr, federationList, False)
|
||||||
|
|
||||||
sessionBob = createSession(proxyType)
|
sessionBob = createSession(proxyType)
|
||||||
bobPostLog = []
|
bobPostLog = []
|
||||||
|
|
@ -978,7 +1029,7 @@ def testNoOfFollowersOnDomain():
|
||||||
noOfFollowersOnDomain(baseDir, nickname + '@' + domain, otherdomain)
|
noOfFollowersOnDomain(baseDir, nickname + '@' + domain, otherdomain)
|
||||||
assert followersOnOtherDomain == 3
|
assert followersOnOtherDomain == 3
|
||||||
|
|
||||||
unfollowerOfPerson(baseDir, nickname, domain, 'sausagedog', otherdomain)
|
unfollowerOfAccount(baseDir, nickname, domain, 'sausagedog', otherdomain)
|
||||||
followersOnOtherDomain = \
|
followersOnOtherDomain = \
|
||||||
noOfFollowersOnDomain(baseDir, nickname + '@' + domain, otherdomain)
|
noOfFollowersOnDomain(baseDir, nickname + '@' + domain, otherdomain)
|
||||||
assert followersOnOtherDomain == 2
|
assert followersOnOtherDomain == 2
|
||||||
|
|
@ -1074,7 +1125,7 @@ def testFollows():
|
||||||
assert(False)
|
assert(False)
|
||||||
|
|
||||||
assert(domainFound)
|
assert(domainFound)
|
||||||
unfollowPerson(baseDir, nickname, domain, 'batman', 'mesh.com')
|
unfollowAccount(baseDir, nickname, domain, 'batman', 'mesh.com')
|
||||||
|
|
||||||
domainFound = False
|
domainFound = False
|
||||||
for followingDomain in f:
|
for followingDomain in f:
|
||||||
|
|
@ -1753,7 +1804,8 @@ def testWebLinks():
|
||||||
'they prefer to cling to their customs, beliefs, and traditions ' + \
|
'they prefer to cling to their customs, beliefs, and traditions ' + \
|
||||||
'rather than to accept the teachings of a war of each ' + \
|
'rather than to accept the teachings of a war of each ' + \
|
||||||
'against all"\n\n--Peter Kropotkin'
|
'against all"\n\n--Peter Kropotkin'
|
||||||
resultText = removeLongWords(addWebLinks(exampleText), 40, [])
|
testFnStr = addWebLinks(exampleText)
|
||||||
|
resultText = removeLongWords(testFnStr, 40, [])
|
||||||
assert resultText == exampleText
|
assert resultText == exampleText
|
||||||
assert 'ellipsis' not in resultText
|
assert 'ellipsis' not in resultText
|
||||||
|
|
||||||
|
|
@ -1767,7 +1819,8 @@ def testWebLinks():
|
||||||
|
|
||||||
exampleText = \
|
exampleText = \
|
||||||
'<p>Test1 test2 #YetAnotherExcessivelyLongwindedAndBoringHashtag</p>'
|
'<p>Test1 test2 #YetAnotherExcessivelyLongwindedAndBoringHashtag</p>'
|
||||||
resultText = removeLongWords(addWebLinks(exampleText), 40, [])
|
testFnStr = addWebLinks(exampleText)
|
||||||
|
resultText = removeLongWords(testFnStr, 40, [])
|
||||||
assert(resultText ==
|
assert(resultText ==
|
||||||
'<p>Test1 test2 '
|
'<p>Test1 test2 '
|
||||||
'#YetAnotherExcessivelyLongwindedAndBorin\ngHashtag</p>')
|
'#YetAnotherExcessivelyLongwindedAndBorin\ngHashtag</p>')
|
||||||
|
|
@ -1776,7 +1829,8 @@ def testWebLinks():
|
||||||
"<p>Don't remove a p2p link " + \
|
"<p>Don't remove a p2p link " + \
|
||||||
"rad:git:hwd1yrerc3mcgn8ga9rho3dqi4w33nep7kxmqezss4topyfgmexihp" + \
|
"rad:git:hwd1yrerc3mcgn8ga9rho3dqi4w33nep7kxmqezss4topyfgmexihp" + \
|
||||||
"33xcw</p>"
|
"33xcw</p>"
|
||||||
resultText = removeLongWords(addWebLinks(exampleText), 40, [])
|
testFnStr = addWebLinks(exampleText)
|
||||||
|
resultText = removeLongWords(testFnStr, 40, [])
|
||||||
assert resultText == exampleText
|
assert resultText == exampleText
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2533,8 +2587,277 @@ def testReplyToPublicPost() -> None:
|
||||||
httpPrefix + '://rat.site/users/ninjarodent'
|
httpPrefix + '://rat.site/users/ninjarodent'
|
||||||
|
|
||||||
|
|
||||||
|
def getFunctionCallArgs(name: str, lines: [], startLineCtr: int) -> []:
|
||||||
|
"""Returns the arguments of a function call given lines
|
||||||
|
of source code and a starting line number
|
||||||
|
"""
|
||||||
|
argsStr = lines[startLineCtr].split(name + '(')[1]
|
||||||
|
if ')' in argsStr:
|
||||||
|
argsStr = argsStr.split(')')[0].replace(' ', '').split(',')
|
||||||
|
return argsStr
|
||||||
|
for lineCtr in range(startLineCtr + 1, len(lines)):
|
||||||
|
if ')' not in lines[lineCtr]:
|
||||||
|
argsStr += lines[lineCtr]
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
argsStr += lines[lineCtr].split(')')[0]
|
||||||
|
break
|
||||||
|
return argsStr.replace('\n', '').replace(' ', '').split(',')
|
||||||
|
|
||||||
|
|
||||||
|
def getFunctionCalls(name: str, lines: [], startLineCtr: int,
|
||||||
|
functionProperties: {}) -> []:
|
||||||
|
"""Returns the functions called by the given one,
|
||||||
|
Starting with the given source code at the given line
|
||||||
|
"""
|
||||||
|
callsFunctions = []
|
||||||
|
functionContentStr = ''
|
||||||
|
for lineCtr in range(startLineCtr + 1, len(lines)):
|
||||||
|
if lines[lineCtr].startswith('def '):
|
||||||
|
break
|
||||||
|
if lines[lineCtr].startswith('class '):
|
||||||
|
break
|
||||||
|
functionContentStr += lines[lineCtr]
|
||||||
|
for funcName, properties in functionProperties.items():
|
||||||
|
if funcName + '(' in functionContentStr:
|
||||||
|
callsFunctions.append(funcName)
|
||||||
|
return callsFunctions
|
||||||
|
|
||||||
|
|
||||||
|
def functionArgsMatch(callArgs: [], funcArgs: []):
|
||||||
|
"""Do the function artuments match the function call arguments
|
||||||
|
"""
|
||||||
|
if len(callArgs) == len(funcArgs):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# count non-optional arguments
|
||||||
|
callArgsCtr = 0
|
||||||
|
for a in callArgs:
|
||||||
|
if a == 'self':
|
||||||
|
continue
|
||||||
|
if '=' not in a:
|
||||||
|
callArgsCtr += 1
|
||||||
|
|
||||||
|
funcArgsCtr = 0
|
||||||
|
for a in funcArgs:
|
||||||
|
if a == 'self':
|
||||||
|
continue
|
||||||
|
if '=' not in a:
|
||||||
|
funcArgsCtr += 1
|
||||||
|
|
||||||
|
return callArgsCtr >= funcArgsCtr
|
||||||
|
|
||||||
|
|
||||||
|
def testFunctions():
|
||||||
|
print('testFunctions')
|
||||||
|
function = {}
|
||||||
|
functionProperties = {}
|
||||||
|
modules = {}
|
||||||
|
|
||||||
|
for subdir, dirs, files in os.walk('.'):
|
||||||
|
for sourceFile in files:
|
||||||
|
if not sourceFile.endswith('.py'):
|
||||||
|
continue
|
||||||
|
modName = sourceFile.replace('.py', '')
|
||||||
|
modules[modName] = {
|
||||||
|
'functions': []
|
||||||
|
}
|
||||||
|
sourceStr = ''
|
||||||
|
with open(sourceFile, "r") as f:
|
||||||
|
sourceStr = f.read()
|
||||||
|
modules[modName]['source'] = sourceStr
|
||||||
|
with open(sourceFile, "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
modules[modName]['lines'] = lines
|
||||||
|
for line in lines:
|
||||||
|
if not line.startswith('def '):
|
||||||
|
continue
|
||||||
|
methodName = line.split('def ', 1)[1].split('(')[0]
|
||||||
|
methodArgs = \
|
||||||
|
sourceStr.split('def ' + methodName + '(')[1]
|
||||||
|
methodArgs = methodArgs.split(')')[0]
|
||||||
|
methodArgs = methodArgs.replace(' ', '').split(',')
|
||||||
|
if function.get(modName):
|
||||||
|
function[modName].append(methodName)
|
||||||
|
else:
|
||||||
|
function[modName] = [methodName]
|
||||||
|
if methodName not in modules[modName]['functions']:
|
||||||
|
modules[modName]['functions'].append(methodName)
|
||||||
|
functionProperties[methodName] = {
|
||||||
|
"args": methodArgs,
|
||||||
|
"module": modName,
|
||||||
|
"calledInModule": []
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
excludeFuncArgs = [
|
||||||
|
'pyjsonld'
|
||||||
|
]
|
||||||
|
excludeFuncs = [
|
||||||
|
'link'
|
||||||
|
]
|
||||||
|
# which modules is each function used within?
|
||||||
|
for modName, modProperties in modules.items():
|
||||||
|
print('Module: ' + modName + ' ✓')
|
||||||
|
for name, properties in functionProperties.items():
|
||||||
|
lineCtr = 0
|
||||||
|
for line in modules[modName]['lines']:
|
||||||
|
if line.startswith('def '):
|
||||||
|
lineCtr += 1
|
||||||
|
continue
|
||||||
|
if name + '(' in line:
|
||||||
|
modList = \
|
||||||
|
functionProperties[name]['calledInModule']
|
||||||
|
if modName not in modList:
|
||||||
|
modList.append(modName)
|
||||||
|
if modName in excludeFuncArgs:
|
||||||
|
lineCtr += 1
|
||||||
|
continue
|
||||||
|
if name in excludeFuncs:
|
||||||
|
lineCtr += 1
|
||||||
|
continue
|
||||||
|
callArgs = \
|
||||||
|
getFunctionCallArgs(name,
|
||||||
|
modules[modName]['lines'],
|
||||||
|
lineCtr)
|
||||||
|
if not functionArgsMatch(callArgs,
|
||||||
|
functionProperties[name]['args']):
|
||||||
|
print('Call to function ' + name +
|
||||||
|
' does not match its arguments')
|
||||||
|
print('def args: ' +
|
||||||
|
str(len(functionProperties[name]['args'])) +
|
||||||
|
'\n' + str(functionProperties[name]['args']))
|
||||||
|
print('Call args: ' + str(len(callArgs)) + '\n' +
|
||||||
|
str(callArgs))
|
||||||
|
print('module ' + modName + ' line ' + str(lineCtr))
|
||||||
|
assert False
|
||||||
|
lineCtr += 1
|
||||||
|
|
||||||
|
# don't check these functions, because they are procedurally called
|
||||||
|
exclusions = [
|
||||||
|
'set_document_loader',
|
||||||
|
'normalize',
|
||||||
|
'get_document_loader',
|
||||||
|
'runInboxQueueWatchdog',
|
||||||
|
'runInboxQueue',
|
||||||
|
'runPostSchedule',
|
||||||
|
'runPostScheduleWatchdog',
|
||||||
|
'str2bool',
|
||||||
|
'runNewswireDaemon',
|
||||||
|
'runNewswireWatchdog',
|
||||||
|
'threadSendPost',
|
||||||
|
'sendToFollowers',
|
||||||
|
'expireCache',
|
||||||
|
'migrateAccount',
|
||||||
|
'getMutualsOfPerson',
|
||||||
|
'runPostsQueue',
|
||||||
|
'runSharesExpire',
|
||||||
|
'runPostsWatchdog',
|
||||||
|
'runSharesExpireWatchdog',
|
||||||
|
'getThisWeeksEvents',
|
||||||
|
'getAvailability',
|
||||||
|
'testThreadsFunction',
|
||||||
|
'createServerAlice',
|
||||||
|
'createServerBob',
|
||||||
|
'createServerEve',
|
||||||
|
'E2EEremoveDevice',
|
||||||
|
'setOrganizationScheme'
|
||||||
|
]
|
||||||
|
excludeImports = [
|
||||||
|
'link'
|
||||||
|
]
|
||||||
|
excludeLocal = [
|
||||||
|
'pyjsonld',
|
||||||
|
'daemon',
|
||||||
|
'tests'
|
||||||
|
]
|
||||||
|
# check that functions are called somewhere
|
||||||
|
for name, properties in functionProperties.items():
|
||||||
|
if name in exclusions:
|
||||||
|
continue
|
||||||
|
isLocalFunction = False
|
||||||
|
if not properties['calledInModule']:
|
||||||
|
print('function ' + name +
|
||||||
|
' in module ' + properties['module'] +
|
||||||
|
' is not called anywhere')
|
||||||
|
assert properties['calledInModule']
|
||||||
|
|
||||||
|
if len(properties['calledInModule']) == 1:
|
||||||
|
modName = properties['calledInModule'][0]
|
||||||
|
if modName not in excludeLocal and \
|
||||||
|
modName == properties['module']:
|
||||||
|
isLocalFunction = True
|
||||||
|
if not name.startswith('_'):
|
||||||
|
print('Local function ' + name +
|
||||||
|
' in ' + modName + '.py does not begin with _')
|
||||||
|
assert False
|
||||||
|
|
||||||
|
if name not in excludeImports:
|
||||||
|
for modName in properties['calledInModule']:
|
||||||
|
if modName == properties['module']:
|
||||||
|
continue
|
||||||
|
importStr = 'from ' + properties['module'] + ' import ' + name
|
||||||
|
if importStr not in modules[modName]['source']:
|
||||||
|
print(importStr + ' not found in ' + modName + '.py')
|
||||||
|
assert False
|
||||||
|
|
||||||
|
if not isLocalFunction:
|
||||||
|
if name.startswith('_'):
|
||||||
|
excludePublic = [
|
||||||
|
'pyjsonld',
|
||||||
|
'daemon',
|
||||||
|
'tests'
|
||||||
|
]
|
||||||
|
modName = properties['module']
|
||||||
|
if modName not in excludePublic:
|
||||||
|
print('Public function ' + name + ' in ' +
|
||||||
|
modName + '.py begins with _')
|
||||||
|
assert False
|
||||||
|
print('Function: ' + name + ' ✓')
|
||||||
|
|
||||||
|
print('Constructing call graph')
|
||||||
|
for modName, modProperties in modules.items():
|
||||||
|
lineCtr = 0
|
||||||
|
for line in modules[modName]['lines']:
|
||||||
|
if line.startswith('def '):
|
||||||
|
name = line.split('def ')[1].split('(')[0]
|
||||||
|
callsList = \
|
||||||
|
getFunctionCalls(name, modules[modName]['lines'],
|
||||||
|
lineCtr, functionProperties)
|
||||||
|
functionProperties[name]['calls'] = callsList.copy()
|
||||||
|
lineCtr += 1
|
||||||
|
callGraphStr = 'digraph Epicyon {\n\n'
|
||||||
|
callGraphStr += ' graph [fontsize=10 fontname="Verdana" compound=true];\n'
|
||||||
|
callGraphStr += ' node [shape=record fontsize=10 fontname="Verdana"];\n\n'
|
||||||
|
|
||||||
|
for modName, modProperties in modules.items():
|
||||||
|
callGraphStr += ' subgraph cluster_' + modName + ' {\n'
|
||||||
|
callGraphStr += ' label = "' + modName + '";\n'
|
||||||
|
callGraphStr += ' node [style=filled];\n'
|
||||||
|
callGraphStr += ' '
|
||||||
|
for name in modProperties['functions']:
|
||||||
|
callGraphStr += '"' + name + '" '
|
||||||
|
callGraphStr += ';\n'
|
||||||
|
callGraphStr += ' color=blue;\n'
|
||||||
|
callGraphStr += ' }\n\n'
|
||||||
|
|
||||||
|
for name, properties in functionProperties.items():
|
||||||
|
if not properties['calls']:
|
||||||
|
continue
|
||||||
|
for calledFunc in properties['calls']:
|
||||||
|
callGraphStr += ' "' + name + '" -> "' + calledFunc + '";\n'
|
||||||
|
|
||||||
|
callGraphStr += '\n}\n'
|
||||||
|
with open('epicyon.dot', 'w+') as fp:
|
||||||
|
fp.write(callGraphStr)
|
||||||
|
print('Call graph saved to epicyon.dot')
|
||||||
|
print('Convert to image with: ' +
|
||||||
|
'dot -Tjpg epicyon.dot -o epicyon_diagram.jpg')
|
||||||
|
|
||||||
|
|
||||||
def runAllTests():
|
def runAllTests():
|
||||||
print('Running tests...')
|
print('Running tests...')
|
||||||
|
testFunctions()
|
||||||
testReplyToPublicPost()
|
testReplyToPublicPost()
|
||||||
testGetMentionedPeople()
|
testGetMentionedPeople()
|
||||||
testGuessHashtagCategory()
|
testGuessHashtagCategory()
|
||||||
|
|
|
||||||
125
theme.py
125
theme.py
|
|
@ -14,7 +14,7 @@ from shutil import copyfile
|
||||||
from content import dangerousCSS
|
from content import dangerousCSS
|
||||||
|
|
||||||
|
|
||||||
def getThemeFiles() -> []:
|
def _getThemeFiles() -> []:
|
||||||
return ('epicyon.css', 'login.css', 'follow.css',
|
return ('epicyon.css', 'login.css', 'follow.css',
|
||||||
'suspended.css', 'calendar.css', 'blog.css',
|
'suspended.css', 'calendar.css', 'blog.css',
|
||||||
'options.css', 'search.css', 'links.css')
|
'options.css', 'search.css', 'links.css')
|
||||||
|
|
@ -38,7 +38,7 @@ def getThemesList(baseDir: str) -> []:
|
||||||
return themes
|
return themes
|
||||||
|
|
||||||
|
|
||||||
def setThemeInConfig(baseDir: str, name: str) -> bool:
|
def _setThemeInConfig(baseDir: str, name: str) -> bool:
|
||||||
configFilename = baseDir + '/config.json'
|
configFilename = baseDir + '/config.json'
|
||||||
if not os.path.isfile(configFilename):
|
if not os.path.isfile(configFilename):
|
||||||
return False
|
return False
|
||||||
|
|
@ -49,7 +49,7 @@ def setThemeInConfig(baseDir: str, name: str) -> bool:
|
||||||
return saveJson(configJson, configFilename)
|
return saveJson(configJson, configFilename)
|
||||||
|
|
||||||
|
|
||||||
def setNewswirePublishAsIcon(baseDir: str, useIcon: bool) -> bool:
|
def _setNewswirePublishAsIcon(baseDir: str, useIcon: bool) -> bool:
|
||||||
"""Shows the newswire publish action as an icon or a button
|
"""Shows the newswire publish action as an icon or a button
|
||||||
"""
|
"""
|
||||||
configFilename = baseDir + '/config.json'
|
configFilename = baseDir + '/config.json'
|
||||||
|
|
@ -62,7 +62,7 @@ def setNewswirePublishAsIcon(baseDir: str, useIcon: bool) -> bool:
|
||||||
return saveJson(configJson, configFilename)
|
return saveJson(configJson, configFilename)
|
||||||
|
|
||||||
|
|
||||||
def setIconsAsButtons(baseDir: str, useButtons: bool) -> bool:
|
def _setIconsAsButtons(baseDir: str, useButtons: bool) -> bool:
|
||||||
"""Whether to show icons in the header (inbox, outbox, etc)
|
"""Whether to show icons in the header (inbox, outbox, etc)
|
||||||
as buttons
|
as buttons
|
||||||
"""
|
"""
|
||||||
|
|
@ -76,7 +76,7 @@ def setIconsAsButtons(baseDir: str, useButtons: bool) -> bool:
|
||||||
return saveJson(configJson, configFilename)
|
return saveJson(configJson, configFilename)
|
||||||
|
|
||||||
|
|
||||||
def setRssIconAtTop(baseDir: str, atTop: bool) -> bool:
|
def _setRssIconAtTop(baseDir: str, atTop: bool) -> bool:
|
||||||
"""Whether to show RSS icon at the top of the timeline
|
"""Whether to show RSS icon at the top of the timeline
|
||||||
"""
|
"""
|
||||||
configFilename = baseDir + '/config.json'
|
configFilename = baseDir + '/config.json'
|
||||||
|
|
@ -89,7 +89,7 @@ def setRssIconAtTop(baseDir: str, atTop: bool) -> bool:
|
||||||
return saveJson(configJson, configFilename)
|
return saveJson(configJson, configFilename)
|
||||||
|
|
||||||
|
|
||||||
def setPublishButtonAtTop(baseDir: str, atTop: bool) -> bool:
|
def _setPublishButtonAtTop(baseDir: str, atTop: bool) -> bool:
|
||||||
"""Whether to show the publish button above the title image
|
"""Whether to show the publish button above the title image
|
||||||
in the newswire column
|
in the newswire column
|
||||||
"""
|
"""
|
||||||
|
|
@ -103,7 +103,7 @@ def setPublishButtonAtTop(baseDir: str, atTop: bool) -> bool:
|
||||||
return saveJson(configJson, configFilename)
|
return saveJson(configJson, configFilename)
|
||||||
|
|
||||||
|
|
||||||
def setFullWidthTimelineButtonHeader(baseDir: str, fullWidth: bool) -> bool:
|
def _setFullWidthTimelineButtonHeader(baseDir: str, fullWidth: bool) -> bool:
|
||||||
"""Shows the timeline button header containing inbox, outbox,
|
"""Shows the timeline button header containing inbox, outbox,
|
||||||
calendar, etc as full width
|
calendar, etc as full width
|
||||||
"""
|
"""
|
||||||
|
|
@ -127,8 +127,8 @@ def getTheme(baseDir: str) -> str:
|
||||||
return 'default'
|
return 'default'
|
||||||
|
|
||||||
|
|
||||||
def removeTheme(baseDir: str):
|
def _removeTheme(baseDir: str):
|
||||||
themeFiles = getThemeFiles()
|
themeFiles = _getThemeFiles()
|
||||||
for filename in themeFiles:
|
for filename in themeFiles:
|
||||||
if os.path.isfile(baseDir + '/' + filename):
|
if os.path.isfile(baseDir + '/' + filename):
|
||||||
os.remove(baseDir + '/' + filename)
|
os.remove(baseDir + '/' + filename)
|
||||||
|
|
@ -183,14 +183,14 @@ def setCSSparam(css: str, param: str, value: str) -> str:
|
||||||
return newcss.strip()
|
return newcss.strip()
|
||||||
|
|
||||||
|
|
||||||
def setThemeFromDict(baseDir: str, name: str,
|
def _setThemeFromDict(baseDir: str, name: str,
|
||||||
themeParams: {}, bgParams: {},
|
themeParams: {}, bgParams: {},
|
||||||
allowLocalNetworkAccess: bool) -> None:
|
allowLocalNetworkAccess: bool) -> None:
|
||||||
"""Uses a dictionary to set a theme
|
"""Uses a dictionary to set a theme
|
||||||
"""
|
"""
|
||||||
if name:
|
if name:
|
||||||
setThemeInConfig(baseDir, name)
|
_setThemeInConfig(baseDir, name)
|
||||||
themeFiles = getThemeFiles()
|
themeFiles = _getThemeFiles()
|
||||||
for filename in themeFiles:
|
for filename in themeFiles:
|
||||||
# check for custom css within the theme directory
|
# check for custom css within the theme directory
|
||||||
templateFilename = baseDir + '/theme/' + name + '/epicyon-' + filename
|
templateFilename = baseDir + '/theme/' + name + '/epicyon-' + filename
|
||||||
|
|
@ -215,33 +215,33 @@ def setThemeFromDict(baseDir: str, name: str,
|
||||||
for paramName, paramValue in themeParams.items():
|
for paramName, paramValue in themeParams.items():
|
||||||
if paramName == 'newswire-publish-icon':
|
if paramName == 'newswire-publish-icon':
|
||||||
if paramValue.lower() == 'true':
|
if paramValue.lower() == 'true':
|
||||||
setNewswirePublishAsIcon(baseDir, True)
|
_setNewswirePublishAsIcon(baseDir, True)
|
||||||
else:
|
else:
|
||||||
setNewswirePublishAsIcon(baseDir, False)
|
_setNewswirePublishAsIcon(baseDir, False)
|
||||||
continue
|
continue
|
||||||
elif paramName == 'full-width-timeline-buttons':
|
elif paramName == 'full-width-timeline-buttons':
|
||||||
if paramValue.lower() == 'true':
|
if paramValue.lower() == 'true':
|
||||||
setFullWidthTimelineButtonHeader(baseDir, True)
|
_setFullWidthTimelineButtonHeader(baseDir, True)
|
||||||
else:
|
else:
|
||||||
setFullWidthTimelineButtonHeader(baseDir, False)
|
_setFullWidthTimelineButtonHeader(baseDir, False)
|
||||||
continue
|
continue
|
||||||
elif paramName == 'icons-as-buttons':
|
elif paramName == 'icons-as-buttons':
|
||||||
if paramValue.lower() == 'true':
|
if paramValue.lower() == 'true':
|
||||||
setIconsAsButtons(baseDir, True)
|
_setIconsAsButtons(baseDir, True)
|
||||||
else:
|
else:
|
||||||
setIconsAsButtons(baseDir, False)
|
_setIconsAsButtons(baseDir, False)
|
||||||
continue
|
continue
|
||||||
elif paramName == 'rss-icon-at-top':
|
elif paramName == 'rss-icon-at-top':
|
||||||
if paramValue.lower() == 'true':
|
if paramValue.lower() == 'true':
|
||||||
setRssIconAtTop(baseDir, True)
|
_setRssIconAtTop(baseDir, True)
|
||||||
else:
|
else:
|
||||||
setRssIconAtTop(baseDir, False)
|
_setRssIconAtTop(baseDir, False)
|
||||||
continue
|
continue
|
||||||
elif paramName == 'publish-button-at-top':
|
elif paramName == 'publish-button-at-top':
|
||||||
if paramValue.lower() == 'true':
|
if paramValue.lower() == 'true':
|
||||||
setPublishButtonAtTop(baseDir, True)
|
_setPublishButtonAtTop(baseDir, True)
|
||||||
else:
|
else:
|
||||||
setPublishButtonAtTop(baseDir, False)
|
_setPublishButtonAtTop(baseDir, False)
|
||||||
continue
|
continue
|
||||||
css = setCSSparam(css, paramName, paramValue)
|
css = setCSSparam(css, paramName, paramValue)
|
||||||
filename = baseDir + '/' + filename
|
filename = baseDir + '/' + filename
|
||||||
|
|
@ -249,16 +249,16 @@ def setThemeFromDict(baseDir: str, name: str,
|
||||||
cssfile.write(css)
|
cssfile.write(css)
|
||||||
|
|
||||||
if bgParams.get('login'):
|
if bgParams.get('login'):
|
||||||
setBackgroundFormat(baseDir, name, 'login', bgParams['login'])
|
_setBackgroundFormat(baseDir, name, 'login', bgParams['login'])
|
||||||
if bgParams.get('follow'):
|
if bgParams.get('follow'):
|
||||||
setBackgroundFormat(baseDir, name, 'follow', bgParams['follow'])
|
_setBackgroundFormat(baseDir, name, 'follow', bgParams['follow'])
|
||||||
if bgParams.get('options'):
|
if bgParams.get('options'):
|
||||||
setBackgroundFormat(baseDir, name, 'options', bgParams['options'])
|
_setBackgroundFormat(baseDir, name, 'options', bgParams['options'])
|
||||||
if bgParams.get('search'):
|
if bgParams.get('search'):
|
||||||
setBackgroundFormat(baseDir, name, 'search', bgParams['search'])
|
_setBackgroundFormat(baseDir, name, 'search', bgParams['search'])
|
||||||
|
|
||||||
|
|
||||||
def setBackgroundFormat(baseDir: str, name: str,
|
def _setBackgroundFormat(baseDir: str, name: str,
|
||||||
backgroundType: str, extension: str) -> None:
|
backgroundType: str, extension: str) -> None:
|
||||||
"""Sets the background file extension
|
"""Sets the background file extension
|
||||||
"""
|
"""
|
||||||
|
|
@ -277,7 +277,7 @@ def setBackgroundFormat(baseDir: str, name: str,
|
||||||
def enableGrayscale(baseDir: str) -> None:
|
def enableGrayscale(baseDir: str) -> None:
|
||||||
"""Enables grayscale for the current theme
|
"""Enables grayscale for the current theme
|
||||||
"""
|
"""
|
||||||
themeFiles = getThemeFiles()
|
themeFiles = _getThemeFiles()
|
||||||
for filename in themeFiles:
|
for filename in themeFiles:
|
||||||
templateFilename = baseDir + '/' + filename
|
templateFilename = baseDir + '/' + filename
|
||||||
if not os.path.isfile(templateFilename):
|
if not os.path.isfile(templateFilename):
|
||||||
|
|
@ -300,7 +300,7 @@ def enableGrayscale(baseDir: str) -> None:
|
||||||
def disableGrayscale(baseDir: str) -> None:
|
def disableGrayscale(baseDir: str) -> None:
|
||||||
"""Disables grayscale for the current theme
|
"""Disables grayscale for the current theme
|
||||||
"""
|
"""
|
||||||
themeFiles = getThemeFiles()
|
themeFiles = _getThemeFiles()
|
||||||
for filename in themeFiles:
|
for filename in themeFiles:
|
||||||
templateFilename = baseDir + '/' + filename
|
templateFilename = baseDir + '/' + filename
|
||||||
if not os.path.isfile(templateFilename):
|
if not os.path.isfile(templateFilename):
|
||||||
|
|
@ -318,7 +318,7 @@ def disableGrayscale(baseDir: str) -> None:
|
||||||
os.remove(grayscaleFilename)
|
os.remove(grayscaleFilename)
|
||||||
|
|
||||||
|
|
||||||
def setCustomFont(baseDir: str):
|
def _setCustomFont(baseDir: str):
|
||||||
"""Uses a dictionary to set a theme
|
"""Uses a dictionary to set a theme
|
||||||
"""
|
"""
|
||||||
customFontExt = None
|
customFontExt = None
|
||||||
|
|
@ -337,7 +337,7 @@ def setCustomFont(baseDir: str):
|
||||||
if not customFontExt:
|
if not customFontExt:
|
||||||
return
|
return
|
||||||
|
|
||||||
themeFiles = getThemeFiles()
|
themeFiles = _getThemeFiles()
|
||||||
for filename in themeFiles:
|
for filename in themeFiles:
|
||||||
templateFilename = baseDir + '/' + filename
|
templateFilename = baseDir + '/' + filename
|
||||||
if not os.path.isfile(templateFilename):
|
if not os.path.isfile(templateFilename):
|
||||||
|
|
@ -356,7 +356,7 @@ def setCustomFont(baseDir: str):
|
||||||
cssfile.write(css)
|
cssfile.write(css)
|
||||||
|
|
||||||
|
|
||||||
def readVariablesFile(baseDir: str, themeName: str,
|
def _readVariablesFile(baseDir: str, themeName: str,
|
||||||
variablesFile: str,
|
variablesFile: str,
|
||||||
allowLocalNetworkAccess: bool) -> None:
|
allowLocalNetworkAccess: bool) -> None:
|
||||||
"""Reads variables from a file in the theme directory
|
"""Reads variables from a file in the theme directory
|
||||||
|
|
@ -370,14 +370,14 @@ def readVariablesFile(baseDir: str, themeName: str,
|
||||||
"options": "jpg",
|
"options": "jpg",
|
||||||
"search": "jpg"
|
"search": "jpg"
|
||||||
}
|
}
|
||||||
setThemeFromDict(baseDir, themeName, themeParams, bgParams,
|
_setThemeFromDict(baseDir, themeName, themeParams, bgParams,
|
||||||
allowLocalNetworkAccess)
|
allowLocalNetworkAccess)
|
||||||
|
|
||||||
|
|
||||||
def setThemeDefault(baseDir: str, allowLocalNetworkAccess: bool):
|
def _setThemeDefault(baseDir: str, allowLocalNetworkAccess: bool):
|
||||||
name = 'default'
|
name = 'default'
|
||||||
removeTheme(baseDir)
|
_removeTheme(baseDir)
|
||||||
setThemeInConfig(baseDir, name)
|
_setThemeInConfig(baseDir, name)
|
||||||
bgParams = {
|
bgParams = {
|
||||||
"login": "jpg",
|
"login": "jpg",
|
||||||
"follow": "jpg",
|
"follow": "jpg",
|
||||||
|
|
@ -394,44 +394,11 @@ def setThemeDefault(baseDir: str, allowLocalNetworkAccess: bool):
|
||||||
"banner-height-mobile": "10vh",
|
"banner-height-mobile": "10vh",
|
||||||
"search-banner-height-mobile": "15vh"
|
"search-banner-height-mobile": "15vh"
|
||||||
}
|
}
|
||||||
setThemeFromDict(baseDir, name, themeParams, bgParams,
|
_setThemeFromDict(baseDir, name, themeParams, bgParams,
|
||||||
allowLocalNetworkAccess)
|
allowLocalNetworkAccess)
|
||||||
|
|
||||||
|
|
||||||
def setThemeHighVis(baseDir: str, allowLocalNetworkAccess: bool):
|
def _setThemeFonts(baseDir: str, themeName: str) -> None:
|
||||||
name = 'highvis'
|
|
||||||
themeParams = {
|
|
||||||
"newswire-publish-icon": True,
|
|
||||||
"full-width-timeline-buttons": False,
|
|
||||||
"icons-as-buttons": False,
|
|
||||||
"rss-icon-at-top": True,
|
|
||||||
"publish-button-at-top": False,
|
|
||||||
"font-size-header": "22px",
|
|
||||||
"font-size-header-mobile": "32px",
|
|
||||||
"font-size": "45px",
|
|
||||||
"font-size2": "45px",
|
|
||||||
"font-size3": "45px",
|
|
||||||
"font-size4": "35px",
|
|
||||||
"font-size5": "29px",
|
|
||||||
"gallery-font-size": "35px",
|
|
||||||
"gallery-font-size-mobile": "55px",
|
|
||||||
"hashtag-vertical-spacing3": "100px",
|
|
||||||
"hashtag-vertical-spacing4": "150px",
|
|
||||||
"time-vertical-align": "-10px",
|
|
||||||
"*font-family": "'LinBiolinum_Rah'",
|
|
||||||
"*src": "url('./fonts/LinBiolinum_Rah.woff2') format('woff2')"
|
|
||||||
}
|
|
||||||
bgParams = {
|
|
||||||
"login": "jpg",
|
|
||||||
"follow": "jpg",
|
|
||||||
"options": "jpg",
|
|
||||||
"search": "jpg"
|
|
||||||
}
|
|
||||||
setThemeFromDict(baseDir, name, themeParams, bgParams,
|
|
||||||
allowLocalNetworkAccess)
|
|
||||||
|
|
||||||
|
|
||||||
def setThemeFonts(baseDir: str, themeName: str) -> None:
|
|
||||||
"""Adds custom theme fonts
|
"""Adds custom theme fonts
|
||||||
"""
|
"""
|
||||||
themeNameLower = themeName.lower()
|
themeNameLower = themeName.lower()
|
||||||
|
|
@ -455,7 +422,7 @@ def setThemeFonts(baseDir: str, themeName: str) -> None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def setThemeImages(baseDir: str, name: str) -> None:
|
def _setThemeImages(baseDir: str, name: str) -> None:
|
||||||
"""Changes the profile background image
|
"""Changes the profile background image
|
||||||
and banner to the defaults
|
and banner to the defaults
|
||||||
"""
|
"""
|
||||||
|
|
@ -590,7 +557,7 @@ def setTheme(baseDir: str, name: str, domain: str,
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
prevThemeName = getTheme(baseDir)
|
prevThemeName = getTheme(baseDir)
|
||||||
removeTheme(baseDir)
|
_removeTheme(baseDir)
|
||||||
|
|
||||||
themes = getThemesList(baseDir)
|
themes = getThemesList(baseDir)
|
||||||
for themeName in themes:
|
for themeName in themes:
|
||||||
|
|
@ -606,21 +573,21 @@ def setTheme(baseDir: str, name: str, domain: str,
|
||||||
if prevThemeName.lower() != themeNameLower:
|
if prevThemeName.lower() != themeNameLower:
|
||||||
# change the banner and profile image
|
# change the banner and profile image
|
||||||
# to the default for the theme
|
# to the default for the theme
|
||||||
setThemeImages(baseDir, name)
|
_setThemeImages(baseDir, name)
|
||||||
setThemeFonts(baseDir, name)
|
_setThemeFonts(baseDir, name)
|
||||||
result = True
|
result = True
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
# default
|
# default
|
||||||
setThemeDefault(baseDir)
|
_setThemeDefault(baseDir, allowLocalNetworkAccess)
|
||||||
result = True
|
result = True
|
||||||
|
|
||||||
variablesFile = baseDir + '/theme/' + name + '/theme.json'
|
variablesFile = baseDir + '/theme/' + name + '/theme.json'
|
||||||
if os.path.isfile(variablesFile):
|
if os.path.isfile(variablesFile):
|
||||||
readVariablesFile(baseDir, name, variablesFile,
|
_readVariablesFile(baseDir, name, variablesFile,
|
||||||
allowLocalNetworkAccess)
|
allowLocalNetworkAccess)
|
||||||
|
|
||||||
setCustomFont(baseDir)
|
_setCustomFont(baseDir)
|
||||||
|
|
||||||
# set the news avatar
|
# set the news avatar
|
||||||
newsAvatarThemeFilename = \
|
newsAvatarThemeFilename = \
|
||||||
|
|
@ -637,5 +604,5 @@ def setTheme(baseDir: str, name: str, domain: str,
|
||||||
else:
|
else:
|
||||||
disableGrayscale(baseDir)
|
disableGrayscale(baseDir)
|
||||||
|
|
||||||
setThemeInConfig(baseDir, name)
|
_setThemeInConfig(baseDir, name)
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
168
utils.py
168
utils.py
|
|
@ -78,152 +78,6 @@ def isDormant(baseDir: str, nickname: str, domain: str, actor: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def getHashtagCategory(baseDir: str, hashtag: str) -> str:
|
|
||||||
"""Returns the category for the hashtag
|
|
||||||
"""
|
|
||||||
categoryFilename = baseDir + '/tags/' + hashtag + '.category'
|
|
||||||
if not os.path.isfile(categoryFilename):
|
|
||||||
categoryFilename = baseDir + '/tags/' + hashtag.title() + '.category'
|
|
||||||
if not os.path.isfile(categoryFilename):
|
|
||||||
categoryFilename = \
|
|
||||||
baseDir + '/tags/' + hashtag.upper() + '.category'
|
|
||||||
if not os.path.isfile(categoryFilename):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
with open(categoryFilename, 'r') as fp:
|
|
||||||
categoryStr = fp.read()
|
|
||||||
if categoryStr:
|
|
||||||
return categoryStr
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def getHashtagCategories(baseDir: str, recent=False, category=None) -> None:
|
|
||||||
"""Returns a dictionary containing hashtag categories
|
|
||||||
"""
|
|
||||||
hashtagCategories = {}
|
|
||||||
|
|
||||||
if recent:
|
|
||||||
currTime = datetime.datetime.utcnow()
|
|
||||||
daysSinceEpoch = (currTime - datetime.datetime(1970, 1, 1)).days
|
|
||||||
recently = daysSinceEpoch - 1
|
|
||||||
|
|
||||||
for subdir, dirs, files in os.walk(baseDir + '/tags'):
|
|
||||||
for f in files:
|
|
||||||
if not f.endswith('.category'):
|
|
||||||
continue
|
|
||||||
categoryFilename = os.path.join(baseDir + '/tags', f)
|
|
||||||
if not os.path.isfile(categoryFilename):
|
|
||||||
continue
|
|
||||||
hashtag = f.split('.')[0]
|
|
||||||
with open(categoryFilename, 'r') as fp:
|
|
||||||
categoryStr = fp.read()
|
|
||||||
|
|
||||||
if not categoryStr:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if category:
|
|
||||||
# only return a dictionary for a specific category
|
|
||||||
if categoryStr != category:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if recent:
|
|
||||||
tagsFilename = baseDir + '/tags/' + hashtag + '.txt'
|
|
||||||
if not os.path.isfile(tagsFilename):
|
|
||||||
continue
|
|
||||||
modTimesinceEpoc = \
|
|
||||||
os.path.getmtime(tagsFilename)
|
|
||||||
lastModifiedDate = \
|
|
||||||
datetime.datetime.fromtimestamp(modTimesinceEpoc)
|
|
||||||
fileDaysSinceEpoch = \
|
|
||||||
(lastModifiedDate -
|
|
||||||
datetime.datetime(1970, 1, 1)).days
|
|
||||||
if fileDaysSinceEpoch < recently:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not hashtagCategories.get(categoryStr):
|
|
||||||
hashtagCategories[categoryStr] = [hashtag]
|
|
||||||
else:
|
|
||||||
if hashtag not in hashtagCategories[categoryStr]:
|
|
||||||
hashtagCategories[categoryStr].append(hashtag)
|
|
||||||
break
|
|
||||||
return hashtagCategories
|
|
||||||
|
|
||||||
|
|
||||||
def updateHashtagCategories(baseDir: str) -> None:
|
|
||||||
"""Regenerates the list of hashtag categories
|
|
||||||
"""
|
|
||||||
categoryListFilename = baseDir + '/accounts/categoryList.txt'
|
|
||||||
hashtagCategories = getHashtagCategories(baseDir)
|
|
||||||
if not hashtagCategories:
|
|
||||||
if os.path.isfile(categoryListFilename):
|
|
||||||
os.remove(categoryListFilename)
|
|
||||||
return
|
|
||||||
|
|
||||||
categoryList = []
|
|
||||||
for categoryStr, hashtagList in hashtagCategories.items():
|
|
||||||
categoryList.append(categoryStr)
|
|
||||||
categoryList.sort()
|
|
||||||
|
|
||||||
categoryListStr = ''
|
|
||||||
for categoryStr in categoryList:
|
|
||||||
categoryListStr += categoryStr + '\n'
|
|
||||||
|
|
||||||
# save a list of available categories for quick lookup
|
|
||||||
with open(categoryListFilename, 'w+') as fp:
|
|
||||||
fp.write(categoryListStr)
|
|
||||||
|
|
||||||
|
|
||||||
def validHashtagCategory(category: str) -> bool:
|
|
||||||
"""Returns true if the category name is valid
|
|
||||||
"""
|
|
||||||
if not category:
|
|
||||||
return False
|
|
||||||
|
|
||||||
invalidChars = (',', ' ', '<', ';', '\\')
|
|
||||||
for ch in invalidChars:
|
|
||||||
if ch in category:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# too long
|
|
||||||
if len(category) > 40:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def setHashtagCategory(baseDir: str, hashtag: str, category: str,
|
|
||||||
force=False) -> bool:
|
|
||||||
"""Sets the category for the hashtag
|
|
||||||
"""
|
|
||||||
if not validHashtagCategory(category):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not force:
|
|
||||||
hashtagFilename = baseDir + '/tags/' + hashtag + '.txt'
|
|
||||||
if not os.path.isfile(hashtagFilename):
|
|
||||||
hashtag = hashtag.title()
|
|
||||||
hashtagFilename = baseDir + '/tags/' + hashtag + '.txt'
|
|
||||||
if not os.path.isfile(hashtagFilename):
|
|
||||||
hashtag = hashtag.upper()
|
|
||||||
hashtagFilename = baseDir + '/tags/' + hashtag + '.txt'
|
|
||||||
if not os.path.isfile(hashtagFilename):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not os.path.isdir(baseDir + '/tags'):
|
|
||||||
os.mkdir(baseDir + '/tags')
|
|
||||||
categoryFilename = baseDir + '/tags/' + hashtag + '.category'
|
|
||||||
if force:
|
|
||||||
# don't overwrite any existing categories
|
|
||||||
if os.path.isfile(categoryFilename):
|
|
||||||
return False
|
|
||||||
with open(categoryFilename, 'w+') as fp:
|
|
||||||
fp.write(category)
|
|
||||||
updateHashtagCategories(baseDir)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def isEditor(baseDir: str, nickname: str) -> bool:
|
def isEditor(baseDir: str, nickname: str) -> bool:
|
||||||
"""Returns true if the given nickname is an editor
|
"""Returns true if the given nickname is an editor
|
||||||
"""
|
"""
|
||||||
|
|
@ -343,7 +197,7 @@ def isSystemAccount(nickname: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def createConfig(baseDir: str) -> None:
|
def _createConfig(baseDir: str) -> None:
|
||||||
"""Creates a configuration file
|
"""Creates a configuration file
|
||||||
"""
|
"""
|
||||||
configFilename = baseDir + '/config.json'
|
configFilename = baseDir + '/config.json'
|
||||||
|
|
@ -357,7 +211,7 @@ def createConfig(baseDir: str) -> None:
|
||||||
def setConfigParam(baseDir: str, variableName: str, variableValue) -> None:
|
def setConfigParam(baseDir: str, variableName: str, variableValue) -> None:
|
||||||
"""Sets a configuration value
|
"""Sets a configuration value
|
||||||
"""
|
"""
|
||||||
createConfig(baseDir)
|
_createConfig(baseDir)
|
||||||
configFilename = baseDir + '/config.json'
|
configFilename = baseDir + '/config.json'
|
||||||
configJson = {}
|
configJson = {}
|
||||||
if os.path.isfile(configFilename):
|
if os.path.isfile(configFilename):
|
||||||
|
|
@ -369,7 +223,7 @@ def setConfigParam(baseDir: str, variableName: str, variableValue) -> None:
|
||||||
def getConfigParam(baseDir: str, variableName: str):
|
def getConfigParam(baseDir: str, variableName: str):
|
||||||
"""Gets a configuration value
|
"""Gets a configuration value
|
||||||
"""
|
"""
|
||||||
createConfig(baseDir)
|
_createConfig(baseDir)
|
||||||
configFilename = baseDir + '/config.json'
|
configFilename = baseDir + '/config.json'
|
||||||
configJson = loadJson(configFilename)
|
configJson = loadJson(configFilename)
|
||||||
if configJson:
|
if configJson:
|
||||||
|
|
@ -756,7 +610,7 @@ def getDomainFromActor(actor: str) -> (str, int):
|
||||||
return domain, port
|
return domain, port
|
||||||
|
|
||||||
|
|
||||||
def setDefaultPetName(baseDir: str, nickname: str, domain: str,
|
def _setDefaultPetName(baseDir: str, nickname: str, domain: str,
|
||||||
followNickname: str, followDomain: str) -> None:
|
followNickname: str, followDomain: str) -> None:
|
||||||
"""Sets a default petname
|
"""Sets a default petname
|
||||||
This helps especially when using onion or i2p address
|
This helps especially when using onion or i2p address
|
||||||
|
|
@ -793,7 +647,8 @@ def followPerson(baseDir: str, nickname: str, domain: str,
|
||||||
followFile='following.txt') -> bool:
|
followFile='following.txt') -> bool:
|
||||||
"""Adds a person to the follow list
|
"""Adds a person to the follow list
|
||||||
"""
|
"""
|
||||||
if not domainPermitted(followDomain.lower().replace('\n', ''),
|
followDomainStrLower = followDomain.lower().replace('\n', '')
|
||||||
|
if not domainPermitted(followDomainStrLower,
|
||||||
federationList):
|
federationList):
|
||||||
if debug:
|
if debug:
|
||||||
print('DEBUG: follow of domain ' +
|
print('DEBUG: follow of domain ' +
|
||||||
|
|
@ -869,7 +724,7 @@ def followPerson(baseDir: str, nickname: str, domain: str,
|
||||||
addPersonToCalendar(baseDir, nickname, domain,
|
addPersonToCalendar(baseDir, nickname, domain,
|
||||||
followNickname, followDomain)
|
followNickname, followDomain)
|
||||||
# add a default petname
|
# add a default petname
|
||||||
setDefaultPetName(baseDir, nickname, domain,
|
_setDefaultPetName(baseDir, nickname, domain,
|
||||||
followNickname, followDomain)
|
followNickname, followDomain)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -1010,7 +865,8 @@ def locatePost(baseDir: str, nickname: str, domain: str,
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def removeAttachment(baseDir: str, httpPrefix: str, domain: str, postJson: {}):
|
def _removeAttachment(baseDir: str, httpPrefix: str, domain: str,
|
||||||
|
postJson: {}):
|
||||||
if not postJson.get('attachment'):
|
if not postJson.get('attachment'):
|
||||||
return
|
return
|
||||||
if not postJson['attachment'][0].get('url'):
|
if not postJson['attachment'][0].get('url'):
|
||||||
|
|
@ -1053,7 +909,7 @@ def removeModerationPostFromIndex(baseDir: str, postUrl: str,
|
||||||
' from moderation index')
|
' from moderation index')
|
||||||
|
|
||||||
|
|
||||||
def isReplyToBlogPost(baseDir: str, nickname: str, domain: str,
|
def _isReplyToBlogPost(baseDir: str, nickname: str, domain: str,
|
||||||
postJsonObject: str):
|
postJsonObject: str):
|
||||||
"""Is the given post a reply to a blog post?
|
"""Is the given post a reply to a blog post?
|
||||||
"""
|
"""
|
||||||
|
|
@ -1093,7 +949,7 @@ def deletePost(baseDir: str, httpPrefix: str,
|
||||||
return
|
return
|
||||||
|
|
||||||
# don't remove replies to blog posts
|
# don't remove replies to blog posts
|
||||||
if isReplyToBlogPost(baseDir, nickname, domain,
|
if _isReplyToBlogPost(baseDir, nickname, domain,
|
||||||
postJsonObject):
|
postJsonObject):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -1112,7 +968,7 @@ def deletePost(baseDir: str, httpPrefix: str,
|
||||||
del recentPostsCache['html'][postId]
|
del recentPostsCache['html'][postId]
|
||||||
|
|
||||||
# remove any attachment
|
# remove any attachment
|
||||||
removeAttachment(baseDir, httpPrefix, domain, postJsonObject)
|
_removeAttachment(baseDir, httpPrefix, domain, postJsonObject)
|
||||||
|
|
||||||
extensions = ('votes', 'arrived', 'muted')
|
extensions = ('votes', 'arrived', 'muted')
|
||||||
for ext in extensions:
|
for ext in extensions:
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ def htmlCalendarDeleteConfirm(cssCache: {}, translate: {}, baseDir: str,
|
||||||
return deletePostStr
|
return deletePostStr
|
||||||
|
|
||||||
|
|
||||||
def htmlCalendarDay(cssCache: {}, translate: {},
|
def _htmlCalendarDay(cssCache: {}, translate: {},
|
||||||
baseDir: str, path: str,
|
baseDir: str, path: str,
|
||||||
year: int, monthNumber: int, dayNumber: int,
|
year: int, monthNumber: int, dayNumber: int,
|
||||||
nickname: str, domain: str, dayEvents: [],
|
nickname: str, domain: str, dayEvents: [],
|
||||||
|
|
@ -251,7 +251,7 @@ def htmlCalendar(cssCache: {}, translate: {},
|
||||||
if events:
|
if events:
|
||||||
if events.get(str(dayNumber)):
|
if events.get(str(dayNumber)):
|
||||||
dayEvents = events[str(dayNumber)]
|
dayEvents = events[str(dayNumber)]
|
||||||
return htmlCalendarDay(cssCache, translate, baseDir, path,
|
return _htmlCalendarDay(cssCache, translate, baseDir, path,
|
||||||
year, monthNumber, dayNumber,
|
year, monthNumber, dayNumber,
|
||||||
nickname, domain, dayEvents,
|
nickname, domain, dayEvents,
|
||||||
monthName, actor)
|
monthName, actor)
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ from webapp_utils import htmlFooter
|
||||||
from webapp_utils import getBannerFile
|
from webapp_utils import getBannerFile
|
||||||
|
|
||||||
|
|
||||||
def linksExist(baseDir: str) -> bool:
|
def _linksExist(baseDir: str) -> bool:
|
||||||
"""Returns true if links have been created
|
"""Returns true if links have been created
|
||||||
"""
|
"""
|
||||||
linksFilename = baseDir + '/accounts/links.txt'
|
linksFilename = baseDir + '/accounts/links.txt'
|
||||||
return os.path.isfile(linksFilename)
|
return os.path.isfile(linksFilename)
|
||||||
|
|
||||||
|
|
||||||
def getLeftColumnShares(baseDir: str,
|
def _getLeftColumnShares(baseDir: str,
|
||||||
httpPrefix: str, domainFull: str,
|
httpPrefix: str, domainFull: str,
|
||||||
nickname: str,
|
nickname: str,
|
||||||
maxSharesInLeftColumn: int,
|
maxSharesInLeftColumn: int,
|
||||||
|
|
@ -164,7 +164,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||||
# show a number of shares
|
# show a number of shares
|
||||||
maxSharesInLeftColumn = 3
|
maxSharesInLeftColumn = 3
|
||||||
sharesList = \
|
sharesList = \
|
||||||
getLeftColumnShares(baseDir,
|
_getLeftColumnShares(baseDir,
|
||||||
httpPrefix, domainFull, nickname,
|
httpPrefix, domainFull, nickname,
|
||||||
maxSharesInLeftColumn, translate)
|
maxSharesInLeftColumn, translate)
|
||||||
if linksList and sharesList:
|
if linksList and sharesList:
|
||||||
|
|
@ -271,7 +271,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
|
||||||
headerButtonsFrontScreen(translate, nickname,
|
headerButtonsFrontScreen(translate, nickname,
|
||||||
'links', authorized,
|
'links', authorized,
|
||||||
iconsAsButtons) + '</center>'
|
iconsAsButtons) + '</center>'
|
||||||
if linksExist(baseDir):
|
if _linksExist(baseDir):
|
||||||
htmlStr += \
|
htmlStr += \
|
||||||
getLeftColumnContent(baseDir, nickname, domainFull,
|
getLeftColumnContent(baseDir, nickname, domainFull,
|
||||||
httpPrefix, translate,
|
httpPrefix, translate,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from webapp_utils import htmlPostSeparator
|
||||||
from webapp_utils import headerButtonsFrontScreen
|
from webapp_utils import headerButtonsFrontScreen
|
||||||
|
|
||||||
|
|
||||||
def votesIndicator(totalVotes: int, positiveVoting: bool) -> str:
|
def _votesIndicator(totalVotes: int, positiveVoting: bool) -> str:
|
||||||
"""Returns an indicator of the number of votes on a newswire item
|
"""Returns an indicator of the number of votes on a newswire item
|
||||||
"""
|
"""
|
||||||
if totalVotes <= 0:
|
if totalVotes <= 0:
|
||||||
|
|
@ -177,7 +177,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||||
|
|
||||||
# show the newswire lines
|
# show the newswire lines
|
||||||
newswireContentStr = \
|
newswireContentStr = \
|
||||||
htmlNewswire(baseDir, newswire, nickname, moderator, translate,
|
_htmlNewswire(baseDir, newswire, nickname, moderator, translate,
|
||||||
positiveVoting)
|
positiveVoting)
|
||||||
htmlStr += newswireContentStr
|
htmlStr += newswireContentStr
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||||
return htmlStr
|
return htmlStr
|
||||||
|
|
||||||
|
|
||||||
def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
|
def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
|
||||||
translate: {}, positiveVoting: bool) -> str:
|
translate: {}, positiveVoting: bool) -> str:
|
||||||
"""Converts a newswire dict into html
|
"""Converts a newswire dict into html
|
||||||
"""
|
"""
|
||||||
|
|
@ -220,7 +220,7 @@ def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
|
||||||
if moderator:
|
if moderator:
|
||||||
totalVotes = votesOnNewswireItem(item[2])
|
totalVotes = votesOnNewswireItem(item[2])
|
||||||
totalVotesStr = \
|
totalVotesStr = \
|
||||||
votesIndicator(totalVotes, positiveVoting)
|
_votesIndicator(totalVotes, positiveVoting)
|
||||||
|
|
||||||
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
|
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
|
||||||
htmlStr += '<p class="newswireItemVotedOn">' + \
|
htmlStr += '<p class="newswireItemVotedOn">' + \
|
||||||
|
|
@ -247,7 +247,7 @@ def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
|
||||||
# show a number of ticks or crosses for how many
|
# show a number of ticks or crosses for how many
|
||||||
# votes for or against
|
# votes for or against
|
||||||
totalVotesStr = \
|
totalVotesStr = \
|
||||||
votesIndicator(totalVotes, positiveVoting)
|
_votesIndicator(totalVotes, positiveVoting)
|
||||||
|
|
||||||
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
|
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
|
||||||
if moderator and moderatedItem:
|
if moderator and moderatedItem:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from webapp_utils import htmlHeaderWithExternalStyle
|
||||||
from webapp_utils import htmlFooter
|
from webapp_utils import htmlFooter
|
||||||
|
|
||||||
|
|
||||||
def htmlFollowingDataList(baseDir: str, nickname: str,
|
def _htmlFollowingDataList(baseDir: str, nickname: str,
|
||||||
domain: str, domainFull: str) -> str:
|
domain: str, domainFull: str) -> str:
|
||||||
"""Returns a datalist of handles being followed
|
"""Returns a datalist of handles being followed
|
||||||
"""
|
"""
|
||||||
|
|
@ -57,7 +57,7 @@ def htmlFollowingDataList(baseDir: str, nickname: str,
|
||||||
return listStr
|
return listStr
|
||||||
|
|
||||||
|
|
||||||
def htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
|
def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
|
||||||
replyStr: str,
|
replyStr: str,
|
||||||
translate: {},
|
translate: {},
|
||||||
showPublicOnDropdown: bool,
|
showPublicOnDropdown: bool,
|
||||||
|
|
@ -617,7 +617,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
|
||||||
dropDownContent = ''
|
dropDownContent = ''
|
||||||
if not reportUrl and not shareDescription:
|
if not reportUrl and not shareDescription:
|
||||||
dropDownContent = \
|
dropDownContent = \
|
||||||
htmlNewPostDropDown(scopeIcon, scopeDescription,
|
_htmlNewPostDropDown(scopeIcon, scopeDescription,
|
||||||
replyStr,
|
replyStr,
|
||||||
translate,
|
translate,
|
||||||
showPublicOnDropdown,
|
showPublicOnDropdown,
|
||||||
|
|
@ -717,7 +717,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
|
||||||
' <input type="text" name="mentions" ' + \
|
' <input type="text" name="mentions" ' + \
|
||||||
'list="followingHandles" value="' + mentionsStr + '" selected>\n'
|
'list="followingHandles" value="' + mentionsStr + '" selected>\n'
|
||||||
newPostForm += \
|
newPostForm += \
|
||||||
htmlFollowingDataList(baseDir, nickname, domain, domainFull)
|
_htmlFollowingDataList(baseDir, nickname, domain, domainFull)
|
||||||
newPostForm += ''
|
newPostForm += ''
|
||||||
selectedStr = ''
|
selectedStr = ''
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ from webapp_column_right import getRightColumnContent
|
||||||
from webapp_post import individualPostAsHtml
|
from webapp_post import individualPostAsHtml
|
||||||
|
|
||||||
|
|
||||||
def htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
|
def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
translate: {},
|
translate: {},
|
||||||
baseDir: str, httpPrefix: str,
|
baseDir: str, httpPrefix: str,
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
|
|
@ -40,10 +40,12 @@ def htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
boxName = 'tlfeatures'
|
boxName = 'tlfeatures'
|
||||||
authorized = True
|
authorized = True
|
||||||
while ctr < maxItems and currPage < 4:
|
while ctr < maxItems and currPage < 4:
|
||||||
|
outboxFeedPathStr = \
|
||||||
|
'/users/' + nickname + '/' + boxName + \
|
||||||
|
'?page=' + str(currPage)
|
||||||
outboxFeed = \
|
outboxFeed = \
|
||||||
personBoxJson({}, session, baseDir, domain, port,
|
personBoxJson({}, session, baseDir, domain, port,
|
||||||
'/users/' + nickname + '/' + boxName +
|
outboxFeedPathStr,
|
||||||
'?page=' + str(currPage),
|
|
||||||
httpPrefix, 10, boxName,
|
httpPrefix, 10, boxName,
|
||||||
authorized, 0, False, 0)
|
authorized, 0, False, 0)
|
||||||
if not outboxFeed:
|
if not outboxFeed:
|
||||||
|
|
@ -139,7 +141,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
|
||||||
bannerFile, bannerFilename = \
|
bannerFile, bannerFilename = \
|
||||||
getBannerFile(baseDir, nickname, domain, theme)
|
getBannerFile(baseDir, nickname, domain, theme)
|
||||||
profileStr += \
|
profileStr += \
|
||||||
htmlFrontScreenPosts(recentPostsCache, maxRecentPosts,
|
_htmlFrontScreenPosts(recentPostsCache, maxRecentPosts,
|
||||||
translate,
|
translate,
|
||||||
baseDir, httpPrefix,
|
baseDir, httpPrefix,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import os
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from utils import getNicknameFromActor
|
from utils import getNicknameFromActor
|
||||||
from utils import getHashtagCategories
|
from categories import getHashtagCategories
|
||||||
from utils import getHashtagCategory
|
from categories import getHashtagCategory
|
||||||
from webapp_utils import getSearchBannerFile
|
from webapp_utils import getSearchBannerFile
|
||||||
from webapp_utils import getContentWarningButton
|
from webapp_utils import getContentWarningButton
|
||||||
from webapp_utils import htmlHeaderWithExternalStyle
|
from webapp_utils import htmlHeaderWithExternalStyle
|
||||||
|
|
@ -51,7 +51,7 @@ def getHashtagCategoriesFeed(baseDir: str,
|
||||||
return rssStr
|
return rssStr
|
||||||
|
|
||||||
|
|
||||||
def getHashtagDomainMax(domainHistogram: {}) -> str:
|
def _getHashtagDomainMax(domainHistogram: {}) -> str:
|
||||||
"""Returns the domain with the maximum number of hashtags
|
"""Returns the domain with the maximum number of hashtags
|
||||||
"""
|
"""
|
||||||
maxCount = 1
|
maxCount = 1
|
||||||
|
|
@ -63,7 +63,7 @@ def getHashtagDomainMax(domainHistogram: {}) -> str:
|
||||||
return maxDomain
|
return maxDomain
|
||||||
|
|
||||||
|
|
||||||
def getHashtagDomainHistogram(domainHistogram: {}, translate: {}) -> str:
|
def _getHashtagDomainHistogram(domainHistogram: {}, translate: {}) -> str:
|
||||||
"""Returns the html for a histogram of domains
|
"""Returns the html for a histogram of domains
|
||||||
from which hashtags are coming
|
from which hashtags are coming
|
||||||
"""
|
"""
|
||||||
|
|
@ -88,7 +88,7 @@ def getHashtagDomainHistogram(domainHistogram: {}, translate: {}) -> str:
|
||||||
rightColStr = ''
|
rightColStr = ''
|
||||||
|
|
||||||
for i in range(len(domainHistogram)):
|
for i in range(len(domainHistogram)):
|
||||||
domain = getHashtagDomainMax(domainHistogram)
|
domain = _getHashtagDomainMax(domainHistogram)
|
||||||
if not domain:
|
if not domain:
|
||||||
break
|
break
|
||||||
percent = int(domainHistogram[domain] * 100 / totalCount)
|
percent = int(domainHistogram[domain] * 100 / totalCount)
|
||||||
|
|
@ -224,7 +224,7 @@ def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str:
|
||||||
getContentWarningButton('alltags', translate, tagSwarmStr)
|
getContentWarningButton('alltags', translate, tagSwarmStr)
|
||||||
|
|
||||||
tagSwarmHtml = categorySwarmStr + tagSwarmStr.strip() + '\n'
|
tagSwarmHtml = categorySwarmStr + tagSwarmStr.strip() + '\n'
|
||||||
# tagSwarmHtml += getHashtagDomainHistogram(domainHistogram, translate)
|
# tagSwarmHtml += _getHashtagDomainHistogram(domainHistogram, translate)
|
||||||
return tagSwarmHtml
|
return tagSwarmHtml
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ __email__ = "bob@freedombone.net"
|
||||||
__status__ = "Production"
|
__status__ = "Production"
|
||||||
|
|
||||||
|
|
||||||
def addEmbeddedVideoFromSites(translate: {}, content: str,
|
def _addEmbeddedVideoFromSites(translate: {}, content: str,
|
||||||
width=400, height=300) -> str:
|
width=400, height=300) -> str:
|
||||||
"""Adds embedded videos
|
"""Adds embedded videos
|
||||||
"""
|
"""
|
||||||
|
|
@ -122,7 +122,7 @@ def addEmbeddedVideoFromSites(translate: {}, content: str,
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def addEmbeddedAudio(translate: {}, content: str) -> str:
|
def _addEmbeddedAudio(translate: {}, content: str) -> str:
|
||||||
"""Adds embedded audio for mp3/ogg
|
"""Adds embedded audio for mp3/ogg
|
||||||
"""
|
"""
|
||||||
if not ('.mp3' in content or '.ogg' in content):
|
if not ('.mp3' in content or '.ogg' in content):
|
||||||
|
|
@ -167,7 +167,7 @@ def addEmbeddedAudio(translate: {}, content: str) -> str:
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
def addEmbeddedVideo(translate: {}, content: str,
|
def _addEmbeddedVideo(translate: {}, content: str,
|
||||||
width=400, height=300) -> str:
|
width=400, height=300) -> str:
|
||||||
"""Adds embedded video for mp4/webm/ogv
|
"""Adds embedded video for mp4/webm/ogv
|
||||||
"""
|
"""
|
||||||
|
|
@ -219,6 +219,6 @@ def addEmbeddedVideo(translate: {}, content: str,
|
||||||
def addEmbeddedElements(translate: {}, content: str) -> str:
|
def addEmbeddedElements(translate: {}, content: str) -> str:
|
||||||
"""Adds embedded elements for various media types
|
"""Adds embedded elements for various media types
|
||||||
"""
|
"""
|
||||||
content = addEmbeddedVideoFromSites(translate, content)
|
content = _addEmbeddedVideoFromSites(translate, content)
|
||||||
content = addEmbeddedAudio(translate, content)
|
content = _addEmbeddedAudio(translate, content)
|
||||||
return addEmbeddedVideo(translate, content)
|
return _addEmbeddedVideo(translate, content)
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,9 @@ def htmlAccountInfo(cssCache: {}, translate: {},
|
||||||
urlCtr += 1
|
urlCtr += 1
|
||||||
blockedPostsHtml = ''
|
blockedPostsHtml = ''
|
||||||
if blockedPostsLinks:
|
if blockedPostsLinks:
|
||||||
|
blockNoStr = 'blockNumber' + str(ctr)
|
||||||
blockedPostsHtml = \
|
blockedPostsHtml = \
|
||||||
getContentWarningButton('blockNumber' + str(ctr),
|
getContentWarningButton(blockNoStr,
|
||||||
translate, blockedPostsLinks)
|
translate, blockedPostsLinks)
|
||||||
ctr += 1
|
ctr += 1
|
||||||
|
|
||||||
|
|
|
||||||
177
webapp_post.py
177
webapp_post.py
|
|
@ -63,7 +63,7 @@ from webapp_question import insertQuestion
|
||||||
from devices import E2EEdecryptMessageFromDevice
|
from devices import E2EEdecryptMessageFromDevice
|
||||||
|
|
||||||
|
|
||||||
def logPostTiming(enableTimingLog: bool, postStartTime, debugId: str) -> None:
|
def _logPostTiming(enableTimingLog: bool, postStartTime, debugId: str) -> None:
|
||||||
"""Create a log of timings for performance tuning
|
"""Create a log of timings for performance tuning
|
||||||
"""
|
"""
|
||||||
if not enableTimingLog:
|
if not enableTimingLog:
|
||||||
|
|
@ -92,7 +92,7 @@ def preparePostFromHtmlCache(postHtml: str, boxName: str,
|
||||||
return withPageNumber
|
return withPageNumber
|
||||||
|
|
||||||
|
|
||||||
def saveIndividualPostAsHtmlToCache(baseDir: str,
|
def _saveIndividualPostAsHtmlToCache(baseDir: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
postHtml: str) -> bool:
|
postHtml: str) -> bool:
|
||||||
|
|
@ -118,7 +118,7 @@ def saveIndividualPostAsHtmlToCache(baseDir: str,
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def getPostFromRecentCache(session,
|
def _getPostFromRecentCache(session,
|
||||||
baseDir: str,
|
baseDir: str,
|
||||||
httpPrefix: str,
|
httpPrefix: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
|
|
@ -157,13 +157,13 @@ def getPostFromRecentCache(session,
|
||||||
getPersonAvatarUrl(baseDir, postActor, personCache,
|
getPersonAvatarUrl(baseDir, postActor, personCache,
|
||||||
allowDownloads)
|
allowDownloads)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '2.1')
|
_logPostTiming(enableTimingLog, postStartTime, '2.1')
|
||||||
|
|
||||||
updateAvatarImageCache(session, baseDir, httpPrefix,
|
updateAvatarImageCache(session, baseDir, httpPrefix,
|
||||||
postActor, avatarUrl, personCache,
|
postActor, avatarUrl, personCache,
|
||||||
allowDownloads)
|
allowDownloads)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '2.2')
|
_logPostTiming(enableTimingLog, postStartTime, '2.2')
|
||||||
|
|
||||||
postHtml = \
|
postHtml = \
|
||||||
loadIndividualPostAsHtmlFromCache(baseDir, nickname, domain,
|
loadIndividualPostAsHtmlFromCache(baseDir, nickname, domain,
|
||||||
|
|
@ -174,11 +174,11 @@ def getPostFromRecentCache(session,
|
||||||
postHtml = preparePostFromHtmlCache(postHtml, boxName, pageNumber)
|
postHtml = preparePostFromHtmlCache(postHtml, boxName, pageNumber)
|
||||||
updateRecentPostsCache(recentPostsCache, maxRecentPosts,
|
updateRecentPostsCache(recentPostsCache, maxRecentPosts,
|
||||||
postJsonObject, postHtml)
|
postJsonObject, postHtml)
|
||||||
logPostTiming(enableTimingLog, postStartTime, '3')
|
_logPostTiming(enableTimingLog, postStartTime, '3')
|
||||||
return postHtml
|
return postHtml
|
||||||
|
|
||||||
|
|
||||||
def getAvatarImageHtml(showAvatarOptions: bool,
|
def _getAvatarImageHtml(showAvatarOptions: bool,
|
||||||
nickname: str, domainFull: str,
|
nickname: str, domainFull: str,
|
||||||
avatarUrl: str, postActor: str,
|
avatarUrl: str, postActor: str,
|
||||||
translate: {}, avatarPosition: str,
|
translate: {}, avatarPosition: str,
|
||||||
|
|
@ -215,7 +215,7 @@ def getAvatarImageHtml(showAvatarOptions: bool,
|
||||||
return avatarLink.strip()
|
return avatarLink.strip()
|
||||||
|
|
||||||
|
|
||||||
def getReplyIconHtml(nickname: str, isPublicRepeat: bool,
|
def _getReplyIconHtml(nickname: str, isPublicRepeat: bool,
|
||||||
showIcons: bool, commentsEnabled: bool,
|
showIcons: bool, commentsEnabled: bool,
|
||||||
postJsonObject: {}, pageNumberParam: str,
|
postJsonObject: {}, pageNumberParam: str,
|
||||||
translate: {}) -> str:
|
translate: {}) -> str:
|
||||||
|
|
@ -274,7 +274,7 @@ def getReplyIconHtml(nickname: str, isPublicRepeat: bool,
|
||||||
return replyStr
|
return replyStr
|
||||||
|
|
||||||
|
|
||||||
def getEditIconHtml(baseDir: str, nickname: str, domainFull: str,
|
def _getEditIconHtml(baseDir: str, nickname: str, domainFull: str,
|
||||||
postJsonObject: {}, actorNickname: str,
|
postJsonObject: {}, actorNickname: str,
|
||||||
translate: {}, isEvent: bool) -> str:
|
translate: {}, isEvent: bool) -> str:
|
||||||
"""Returns html for the edit icon/button
|
"""Returns html for the edit icon/button
|
||||||
|
|
@ -330,7 +330,7 @@ def getEditIconHtml(baseDir: str, nickname: str, domainFull: str,
|
||||||
return editStr
|
return editStr
|
||||||
|
|
||||||
|
|
||||||
def getAnnounceIconHtml(nickname: str, domainFull: str,
|
def _getAnnounceIconHtml(nickname: str, domainFull: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
isPublicRepeat: bool,
|
isPublicRepeat: bool,
|
||||||
isModerationPost: bool,
|
isModerationPost: bool,
|
||||||
|
|
@ -372,7 +372,7 @@ def getAnnounceIconHtml(nickname: str, domainFull: str,
|
||||||
return announceStr
|
return announceStr
|
||||||
|
|
||||||
|
|
||||||
def getLikeIconHtml(nickname: str, domainFull: str,
|
def _getLikeIconHtml(nickname: str, domainFull: str,
|
||||||
isModerationPost: bool,
|
isModerationPost: bool,
|
||||||
showLikeButton: bool,
|
showLikeButton: bool,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
|
|
@ -390,7 +390,7 @@ def getLikeIconHtml(nickname: str, domainFull: str,
|
||||||
likeTitle = translate['Like this post']
|
likeTitle = translate['Like this post']
|
||||||
likeCount = noOfLikes(postJsonObject)
|
likeCount = noOfLikes(postJsonObject)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '12.1')
|
_logPostTiming(enableTimingLog, postStartTime, '12.1')
|
||||||
|
|
||||||
likeCountStr = ''
|
likeCountStr = ''
|
||||||
if likeCount > 0:
|
if likeCount > 0:
|
||||||
|
|
@ -406,7 +406,7 @@ def getLikeIconHtml(nickname: str, domainFull: str,
|
||||||
likeLink = 'unlike'
|
likeLink = 'unlike'
|
||||||
likeTitle = translate['Undo the like']
|
likeTitle = translate['Undo the like']
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '12.2')
|
_logPostTiming(enableTimingLog, postStartTime, '12.2')
|
||||||
|
|
||||||
likeStr = ''
|
likeStr = ''
|
||||||
if likeCountStr:
|
if likeCountStr:
|
||||||
|
|
@ -430,7 +430,7 @@ def getLikeIconHtml(nickname: str, domainFull: str,
|
||||||
return likeStr
|
return likeStr
|
||||||
|
|
||||||
|
|
||||||
def getBookmarkIconHtml(nickname: str, domainFull: str,
|
def _getBookmarkIconHtml(nickname: str, domainFull: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
isModerationPost: bool,
|
isModerationPost: bool,
|
||||||
translate: {},
|
translate: {},
|
||||||
|
|
@ -452,7 +452,7 @@ def getBookmarkIconHtml(nickname: str, domainFull: str,
|
||||||
bookmarkIcon = 'bookmark.png'
|
bookmarkIcon = 'bookmark.png'
|
||||||
bookmarkLink = 'unbookmark'
|
bookmarkLink = 'unbookmark'
|
||||||
bookmarkTitle = translate['Undo the bookmark']
|
bookmarkTitle = translate['Undo the bookmark']
|
||||||
logPostTiming(enableTimingLog, postStartTime, '12.6')
|
_logPostTiming(enableTimingLog, postStartTime, '12.6')
|
||||||
bookmarkStr = \
|
bookmarkStr = \
|
||||||
' <a class="imageAnchor" href="/users/' + nickname + '?' + \
|
' <a class="imageAnchor" href="/users/' + nickname + '?' + \
|
||||||
bookmarkLink + '=' + postJsonObject['object']['id'] + \
|
bookmarkLink + '=' + postJsonObject['object']['id'] + \
|
||||||
|
|
@ -468,7 +468,7 @@ def getBookmarkIconHtml(nickname: str, domainFull: str,
|
||||||
return bookmarkStr
|
return bookmarkStr
|
||||||
|
|
||||||
|
|
||||||
def getMuteIconHtml(isMuted: bool,
|
def _getMuteIconHtml(isMuted: bool,
|
||||||
postActor: str,
|
postActor: str,
|
||||||
messageId: str,
|
messageId: str,
|
||||||
nickname: str, domainFull: str,
|
nickname: str, domainFull: str,
|
||||||
|
|
@ -512,7 +512,7 @@ def getMuteIconHtml(isMuted: bool,
|
||||||
return muteStr
|
return muteStr
|
||||||
|
|
||||||
|
|
||||||
def getDeleteIconHtml(nickname: str, domainFull: str,
|
def _getDeleteIconHtml(nickname: str, domainFull: str,
|
||||||
allowDeletion: bool,
|
allowDeletion: bool,
|
||||||
postActor: str,
|
postActor: str,
|
||||||
messageId: str,
|
messageId: str,
|
||||||
|
|
@ -541,7 +541,7 @@ def getDeleteIconHtml(nickname: str, domainFull: str,
|
||||||
return deleteStr
|
return deleteStr
|
||||||
|
|
||||||
|
|
||||||
def getPublishedDateStr(postJsonObject: {},
|
def _getPublishedDateStr(postJsonObject: {},
|
||||||
showPublishedDateOnly: bool) -> str:
|
showPublishedDateOnly: bool) -> str:
|
||||||
"""Return the html for the published date on a post
|
"""Return the html for the published date on a post
|
||||||
"""
|
"""
|
||||||
|
|
@ -575,7 +575,7 @@ def getPublishedDateStr(postJsonObject: {},
|
||||||
return publishedStr
|
return publishedStr
|
||||||
|
|
||||||
|
|
||||||
def getBlogCitationsHtml(boxName: str,
|
def _getBlogCitationsHtml(boxName: str,
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
translate: {}) -> str:
|
translate: {}) -> str:
|
||||||
"""Returns blog citations as html
|
"""Returns blog citations as html
|
||||||
|
|
@ -609,7 +609,7 @@ def getBlogCitationsHtml(boxName: str,
|
||||||
return citationsStr
|
return citationsStr
|
||||||
|
|
||||||
|
|
||||||
def boostOwnTootHtml(translate: {}) -> str:
|
def _boostOwnTootHtml(translate: {}) -> str:
|
||||||
"""The html title for announcing your own post
|
"""The html title for announcing your own post
|
||||||
"""
|
"""
|
||||||
return ' <img loading="lazy" title="' + \
|
return ' <img loading="lazy" title="' + \
|
||||||
|
|
@ -619,7 +619,7 @@ def boostOwnTootHtml(translate: {}) -> str:
|
||||||
'/repeat_inactive.png" class="announceOrReply"/>\n'
|
'/repeat_inactive.png" class="announceOrReply"/>\n'
|
||||||
|
|
||||||
|
|
||||||
def announceUnattributedHtml(translate: {},
|
def _announceUnattributedHtml(translate: {},
|
||||||
postJsonObject: {}) -> str:
|
postJsonObject: {}) -> str:
|
||||||
"""Returns the html for an announce title where there
|
"""Returns the html for an announce title where there
|
||||||
is no attribution on the announced post
|
is no attribution on the announced post
|
||||||
|
|
@ -634,7 +634,7 @@ def announceUnattributedHtml(translate: {},
|
||||||
'" class="announceOrReply">@unattributed</a>\n'
|
'" class="announceOrReply">@unattributed</a>\n'
|
||||||
|
|
||||||
|
|
||||||
def announceWithoutDisplayNameHtml(translate: {},
|
def _announceWithoutDisplayNameHtml(translate: {},
|
||||||
announceNickname: str,
|
announceNickname: str,
|
||||||
announceDomain: str,
|
announceDomain: str,
|
||||||
postJsonObject: {}) -> str:
|
postJsonObject: {}) -> str:
|
||||||
|
|
@ -650,7 +650,7 @@ def announceWithoutDisplayNameHtml(translate: {},
|
||||||
announceNickname + '@' + announceDomain + '</a>\n'
|
announceNickname + '@' + announceDomain + '</a>\n'
|
||||||
|
|
||||||
|
|
||||||
def announceWithDisplayNameHtml(translate: {},
|
def _announceWithDisplayNameHtml(translate: {},
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
announceDisplayName: str) -> str:
|
announceDisplayName: str) -> str:
|
||||||
"""Returns html for an announce having a display name
|
"""Returns html for an announce having a display name
|
||||||
|
|
@ -665,7 +665,7 @@ def announceWithDisplayNameHtml(translate: {},
|
||||||
'class="announceOrReply">' + announceDisplayName + '</a>\n'
|
'class="announceOrReply">' + announceDisplayName + '</a>\n'
|
||||||
|
|
||||||
|
|
||||||
def getPostTitleAnnounceHtml(baseDir: str,
|
def _getPostTitleAnnounceHtml(baseDir: str,
|
||||||
httpPrefix: str,
|
httpPrefix: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
showRepeatIcon: bool,
|
showRepeatIcon: bool,
|
||||||
|
|
@ -695,10 +695,10 @@ def getPostTitleAnnounceHtml(baseDir: str,
|
||||||
attributedTo = postJsonObject['object']['attributedTo']
|
attributedTo = postJsonObject['object']['attributedTo']
|
||||||
|
|
||||||
if attributedTo.startswith(postActor):
|
if attributedTo.startswith(postActor):
|
||||||
titleStr += boostOwnTootHtml(translate)
|
titleStr += _boostOwnTootHtml(translate)
|
||||||
else:
|
else:
|
||||||
# boosting another person's post
|
# boosting another person's post
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.2')
|
_logPostTiming(enableTimingLog, postStartTime, '13.2')
|
||||||
announceNickname = None
|
announceNickname = None
|
||||||
if attributedTo:
|
if attributedTo:
|
||||||
announceNickname = getNicknameFromActor(attributedTo)
|
announceNickname = getNicknameFromActor(attributedTo)
|
||||||
|
|
@ -710,7 +710,7 @@ def getPostTitleAnnounceHtml(baseDir: str,
|
||||||
announceDisplayName = \
|
announceDisplayName = \
|
||||||
getDisplayName(baseDir, attributedTo, personCache)
|
getDisplayName(baseDir, attributedTo, personCache)
|
||||||
if announceDisplayName:
|
if announceDisplayName:
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.3')
|
_logPostTiming(enableTimingLog, postStartTime, '13.3')
|
||||||
|
|
||||||
# add any emoji to the display name
|
# add any emoji to the display name
|
||||||
if ':' in announceDisplayName:
|
if ':' in announceDisplayName:
|
||||||
|
|
@ -719,9 +719,9 @@ def getPostTitleAnnounceHtml(baseDir: str,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
announceDisplayName,
|
announceDisplayName,
|
||||||
False)
|
False)
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.3.1')
|
_logPostTiming(enableTimingLog, postStartTime, '13.3.1')
|
||||||
titleStr += \
|
titleStr += \
|
||||||
announceWithDisplayNameHtml(translate,
|
_announceWithDisplayNameHtml(translate,
|
||||||
postJsonObject,
|
postJsonObject,
|
||||||
announceDisplayName)
|
announceDisplayName)
|
||||||
# show avatar of person replied to
|
# show avatar of person replied to
|
||||||
|
|
@ -731,7 +731,7 @@ def getPostTitleAnnounceHtml(baseDir: str,
|
||||||
getPersonAvatarUrl(baseDir, announceActor,
|
getPersonAvatarUrl(baseDir, announceActor,
|
||||||
personCache, allowDownloads)
|
personCache, allowDownloads)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.4')
|
_logPostTiming(enableTimingLog, postStartTime, '13.4')
|
||||||
|
|
||||||
if announceAvatarUrl:
|
if announceAvatarUrl:
|
||||||
idx = 'Show options for this person'
|
idx = 'Show options for this person'
|
||||||
|
|
@ -756,23 +756,23 @@ def getPostTitleAnnounceHtml(baseDir: str,
|
||||||
'/></a>\n </div>\n'
|
'/></a>\n </div>\n'
|
||||||
else:
|
else:
|
||||||
titleStr += \
|
titleStr += \
|
||||||
announceWithoutDisplayNameHtml(translate,
|
_announceWithoutDisplayNameHtml(translate,
|
||||||
announceNickname,
|
announceNickname,
|
||||||
announceDomain,
|
announceDomain,
|
||||||
postJsonObject)
|
postJsonObject)
|
||||||
else:
|
else:
|
||||||
titleStr += \
|
titleStr += \
|
||||||
announceUnattributedHtml(translate,
|
_announceUnattributedHtml(translate,
|
||||||
postJsonObject)
|
postJsonObject)
|
||||||
else:
|
else:
|
||||||
titleStr += \
|
titleStr += \
|
||||||
announceUnattributedHtml(translate, postJsonObject)
|
_announceUnattributedHtml(translate, postJsonObject)
|
||||||
|
|
||||||
return (titleStr, replyAvatarImageInPost,
|
return (titleStr, replyAvatarImageInPost,
|
||||||
containerClassIcons, containerClass)
|
containerClassIcons, containerClass)
|
||||||
|
|
||||||
|
|
||||||
def replyToYourselfHtml(translate: {}, ) -> str:
|
def _replyToYourselfHtml(translate: {}) -> str:
|
||||||
"""Returns html for a title which is a reply to yourself
|
"""Returns html for a title which is a reply to yourself
|
||||||
"""
|
"""
|
||||||
return ' <img loading="lazy" title="' + \
|
return ' <img loading="lazy" title="' + \
|
||||||
|
|
@ -782,7 +782,7 @@ def replyToYourselfHtml(translate: {}, ) -> str:
|
||||||
'/reply.png" class="announceOrReply"/>\n'
|
'/reply.png" class="announceOrReply"/>\n'
|
||||||
|
|
||||||
|
|
||||||
def replyToUnknownHtml(translate: {},
|
def _replyToUnknownHtml(translate: {},
|
||||||
postJsonObject: {}) -> str:
|
postJsonObject: {}) -> str:
|
||||||
"""Returns the html title for a reply to an unknown handle
|
"""Returns the html title for a reply to an unknown handle
|
||||||
"""
|
"""
|
||||||
|
|
@ -795,7 +795,7 @@ def replyToUnknownHtml(translate: {},
|
||||||
'" class="announceOrReply">@unknown</a>\n'
|
'" class="announceOrReply">@unknown</a>\n'
|
||||||
|
|
||||||
|
|
||||||
def replyWithUnknownPathHtml(translate: {},
|
def _replyWithUnknownPathHtml(translate: {},
|
||||||
postJsonObject: {},
|
postJsonObject: {},
|
||||||
postDomain: str) -> str:
|
postDomain: str) -> str:
|
||||||
"""Returns html title for a reply with an unknown path
|
"""Returns html title for a reply with an unknown path
|
||||||
|
|
@ -812,7 +812,7 @@ def replyWithUnknownPathHtml(translate: {},
|
||||||
postDomain + '</a>\n'
|
postDomain + '</a>\n'
|
||||||
|
|
||||||
|
|
||||||
def getReplyHtml(translate: {},
|
def _getReplyHtml(translate: {},
|
||||||
inReplyTo: str, replyDisplayName: str) -> str:
|
inReplyTo: str, replyDisplayName: str) -> str:
|
||||||
"""Returns html title for a reply
|
"""Returns html title for a reply
|
||||||
"""
|
"""
|
||||||
|
|
@ -827,7 +827,7 @@ def getReplyHtml(translate: {},
|
||||||
replyDisplayName + '</a>\n'
|
replyDisplayName + '</a>\n'
|
||||||
|
|
||||||
|
|
||||||
def getReplyWithoutDisplayName(translate: {},
|
def _getReplyWithoutDisplayName(translate: {},
|
||||||
inReplyTo: str,
|
inReplyTo: str,
|
||||||
replyNickname: str, replyDomain: str) -> str:
|
replyNickname: str, replyDomain: str) -> str:
|
||||||
"""Returns html for a reply without a display name,
|
"""Returns html for a reply without a display name,
|
||||||
|
|
@ -842,7 +842,7 @@ def getReplyWithoutDisplayName(translate: {},
|
||||||
replyNickname + '@' + replyDomain + '</a>\n'
|
replyNickname + '@' + replyDomain + '</a>\n'
|
||||||
|
|
||||||
|
|
||||||
def getPostTitleReplyHtml(baseDir: str,
|
def _getPostTitleReplyHtml(baseDir: str,
|
||||||
httpPrefix: str,
|
httpPrefix: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
showRepeatIcon: bool,
|
showRepeatIcon: bool,
|
||||||
|
|
@ -873,7 +873,7 @@ def getPostTitleReplyHtml(baseDir: str,
|
||||||
containerClassIcons = 'containericons darker'
|
containerClassIcons = 'containericons darker'
|
||||||
containerClass = 'container darker'
|
containerClass = 'container darker'
|
||||||
if postJsonObject['object']['inReplyTo'].startswith(postActor):
|
if postJsonObject['object']['inReplyTo'].startswith(postActor):
|
||||||
titleStr += replyToYourselfHtml(translate)
|
titleStr += _replyToYourselfHtml(translate)
|
||||||
return (titleStr, replyAvatarImageInPost,
|
return (titleStr, replyAvatarImageInPost,
|
||||||
containerClassIcons, containerClass)
|
containerClassIcons, containerClass)
|
||||||
|
|
||||||
|
|
@ -894,7 +894,7 @@ def getPostTitleReplyHtml(baseDir: str,
|
||||||
if replyDisplayName:
|
if replyDisplayName:
|
||||||
# add emoji to the display name
|
# add emoji to the display name
|
||||||
if ':' in replyDisplayName:
|
if ':' in replyDisplayName:
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.5')
|
_logPostTiming(enableTimingLog, postStartTime, '13.5')
|
||||||
|
|
||||||
replyDisplayName = \
|
replyDisplayName = \
|
||||||
addEmojiToDisplayName(baseDir,
|
addEmojiToDisplayName(baseDir,
|
||||||
|
|
@ -903,12 +903,12 @@ def getPostTitleReplyHtml(baseDir: str,
|
||||||
domain,
|
domain,
|
||||||
replyDisplayName,
|
replyDisplayName,
|
||||||
False)
|
False)
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.6')
|
_logPostTiming(enableTimingLog, postStartTime, '13.6')
|
||||||
|
|
||||||
titleStr += \
|
titleStr += \
|
||||||
getReplyHtml(translate, inReplyTo, replyDisplayName)
|
_getReplyHtml(translate, inReplyTo, replyDisplayName)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.7')
|
_logPostTiming(enableTimingLog, postStartTime, '13.7')
|
||||||
|
|
||||||
# show avatar of person replied to
|
# show avatar of person replied to
|
||||||
replyAvatarUrl = \
|
replyAvatarUrl = \
|
||||||
|
|
@ -917,7 +917,7 @@ def getPostTitleReplyHtml(baseDir: str,
|
||||||
personCache,
|
personCache,
|
||||||
allowDownloads)
|
allowDownloads)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.8')
|
_logPostTiming(enableTimingLog, postStartTime, '13.8')
|
||||||
|
|
||||||
if replyAvatarUrl:
|
if replyAvatarUrl:
|
||||||
replyAvatarImageInPost = \
|
replyAvatarImageInPost = \
|
||||||
|
|
@ -947,12 +947,13 @@ def getPostTitleReplyHtml(baseDir: str,
|
||||||
inReplyTo = \
|
inReplyTo = \
|
||||||
postJsonObject['object']['inReplyTo']
|
postJsonObject['object']['inReplyTo']
|
||||||
titleStr += \
|
titleStr += \
|
||||||
getReplyWithoutDisplayName(translate,
|
_getReplyWithoutDisplayName(translate,
|
||||||
inReplyTo,
|
inReplyTo,
|
||||||
replyNickname, replyDomain)
|
replyNickname,
|
||||||
|
replyDomain)
|
||||||
else:
|
else:
|
||||||
titleStr += \
|
titleStr += \
|
||||||
replyToUnknownHtml(translate, postJsonObject)
|
_replyToUnknownHtml(translate, postJsonObject)
|
||||||
else:
|
else:
|
||||||
postDomain = \
|
postDomain = \
|
||||||
postJsonObject['object']['inReplyTo']
|
postJsonObject['object']['inReplyTo']
|
||||||
|
|
@ -963,14 +964,14 @@ def getPostTitleReplyHtml(baseDir: str,
|
||||||
postDomain = postDomain.split('/', 1)[0]
|
postDomain = postDomain.split('/', 1)[0]
|
||||||
if postDomain:
|
if postDomain:
|
||||||
titleStr += \
|
titleStr += \
|
||||||
replyWithUnknownPathHtml(translate,
|
_replyWithUnknownPathHtml(translate,
|
||||||
postJsonObject, postDomain)
|
postJsonObject, postDomain)
|
||||||
|
|
||||||
return (titleStr, replyAvatarImageInPost,
|
return (titleStr, replyAvatarImageInPost,
|
||||||
containerClassIcons, containerClass)
|
containerClassIcons, containerClass)
|
||||||
|
|
||||||
|
|
||||||
def getPostTitleHtml(baseDir: str,
|
def _getPostTitleHtml(baseDir: str,
|
||||||
httpPrefix: str,
|
httpPrefix: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
showRepeatIcon: bool,
|
showRepeatIcon: bool,
|
||||||
|
|
@ -998,7 +999,7 @@ def getPostTitleHtml(baseDir: str,
|
||||||
containerClassIcons, containerClass)
|
containerClassIcons, containerClass)
|
||||||
|
|
||||||
if isAnnounced:
|
if isAnnounced:
|
||||||
return getPostTitleAnnounceHtml(baseDir,
|
return _getPostTitleAnnounceHtml(baseDir,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
showRepeatIcon,
|
showRepeatIcon,
|
||||||
|
|
@ -1017,7 +1018,7 @@ def getPostTitleHtml(baseDir: str,
|
||||||
containerClassIcons,
|
containerClassIcons,
|
||||||
containerClass)
|
containerClass)
|
||||||
|
|
||||||
return getPostTitleReplyHtml(baseDir,
|
return _getPostTitleReplyHtml(baseDir,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
showRepeatIcon,
|
showRepeatIcon,
|
||||||
|
|
@ -1037,7 +1038,7 @@ def getPostTitleHtml(baseDir: str,
|
||||||
containerClass)
|
containerClass)
|
||||||
|
|
||||||
|
|
||||||
def getFooterWithIcons(showIcons: bool,
|
def _getFooterWithIcons(showIcons: bool,
|
||||||
containerClassIcons: str,
|
containerClassIcons: str,
|
||||||
replyStr: str, announceStr: str,
|
replyStr: str, announceStr: str,
|
||||||
likeStr: str, bookmarkStr: str,
|
likeStr: str, bookmarkStr: str,
|
||||||
|
|
@ -1098,14 +1099,14 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
# accurate timing of different parts of the code
|
# accurate timing of different parts of the code
|
||||||
enableTimingLog = not allowDownloads
|
enableTimingLog = not allowDownloads
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '1')
|
_logPostTiming(enableTimingLog, postStartTime, '1')
|
||||||
|
|
||||||
avatarPosition = ''
|
avatarPosition = ''
|
||||||
messageId = ''
|
messageId = ''
|
||||||
if postJsonObject.get('id'):
|
if postJsonObject.get('id'):
|
||||||
messageId = removeIdEnding(postJsonObject['id'])
|
messageId = removeIdEnding(postJsonObject['id'])
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '2')
|
_logPostTiming(enableTimingLog, postStartTime, '2')
|
||||||
|
|
||||||
messageIdStr = ''
|
messageIdStr = ''
|
||||||
if messageId:
|
if messageId:
|
||||||
|
|
@ -1119,7 +1120,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
|
|
||||||
# get the html post from the recent posts cache if it exists there
|
# get the html post from the recent posts cache if it exists there
|
||||||
postHtml = \
|
postHtml = \
|
||||||
getPostFromRecentCache(session, baseDir,
|
_getPostFromRecentCache(session, baseDir,
|
||||||
httpPrefix, nickname, domain,
|
httpPrefix, nickname, domain,
|
||||||
postJsonObject,
|
postJsonObject,
|
||||||
postActor,
|
postActor,
|
||||||
|
|
@ -1137,7 +1138,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
if postHtml:
|
if postHtml:
|
||||||
return postHtml
|
return postHtml
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '4')
|
_logPostTiming(enableTimingLog, postStartTime, '4')
|
||||||
|
|
||||||
avatarUrl = \
|
avatarUrl = \
|
||||||
getAvatarImageUrl(session,
|
getAvatarImageUrl(session,
|
||||||
|
|
@ -1145,7 +1146,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
postActor, personCache,
|
postActor, personCache,
|
||||||
avatarUrl, allowDownloads)
|
avatarUrl, allowDownloads)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '5')
|
_logPostTiming(enableTimingLog, postStartTime, '5')
|
||||||
|
|
||||||
# get the display name
|
# get the display name
|
||||||
if domainFull not in postActor:
|
if domainFull not in postActor:
|
||||||
|
|
@ -1156,7 +1157,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
projectVersion, httpPrefix,
|
projectVersion, httpPrefix,
|
||||||
nickname, domain, 'outbox',
|
nickname, domain, 'outbox',
|
||||||
72367)
|
72367)
|
||||||
logPostTiming(enableTimingLog, postStartTime, '6')
|
_logPostTiming(enableTimingLog, postStartTime, '6')
|
||||||
|
|
||||||
if avatarUrl2:
|
if avatarUrl2:
|
||||||
avatarUrl = avatarUrl2
|
avatarUrl = avatarUrl2
|
||||||
|
|
@ -1168,10 +1169,10 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
displayName, False)
|
displayName, False)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '7')
|
_logPostTiming(enableTimingLog, postStartTime, '7')
|
||||||
|
|
||||||
avatarLink = \
|
avatarLink = \
|
||||||
getAvatarImageHtml(showAvatarOptions,
|
_getAvatarImageHtml(showAvatarOptions,
|
||||||
nickname, domainFull,
|
nickname, domainFull,
|
||||||
avatarUrl, postActor,
|
avatarUrl, postActor,
|
||||||
translate, avatarPosition,
|
translate, avatarPosition,
|
||||||
|
|
@ -1215,7 +1216,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
postJsonObject = postJsonAnnounce
|
postJsonObject = postJsonAnnounce
|
||||||
isAnnounced = True
|
isAnnounced = True
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '8')
|
_logPostTiming(enableTimingLog, postStartTime, '8')
|
||||||
|
|
||||||
if not isinstance(postJsonObject['object'], dict):
|
if not isinstance(postJsonObject['object'], dict):
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -1266,7 +1267,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'">@' + actorNickname + '@' + actorDomain + '</a>\n'
|
'">@' + actorNickname + '@' + actorDomain + '</a>\n'
|
||||||
|
|
||||||
# benchmark 9
|
# benchmark 9
|
||||||
logPostTiming(enableTimingLog, postStartTime, '9')
|
_logPostTiming(enableTimingLog, postStartTime, '9')
|
||||||
|
|
||||||
# Show a DM icon for DMs in the inbox timeline
|
# Show a DM icon for DMs in the inbox timeline
|
||||||
if showDMicon:
|
if showDMicon:
|
||||||
|
|
@ -1280,23 +1281,23 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
if postJsonObject['object']['commentsEnabled'] is False:
|
if postJsonObject['object']['commentsEnabled'] is False:
|
||||||
commentsEnabled = False
|
commentsEnabled = False
|
||||||
|
|
||||||
replyStr = getReplyIconHtml(nickname, isPublicRepeat,
|
replyStr = _getReplyIconHtml(nickname, isPublicRepeat,
|
||||||
showIcons, commentsEnabled,
|
showIcons, commentsEnabled,
|
||||||
postJsonObject, pageNumberParam,
|
postJsonObject, pageNumberParam,
|
||||||
translate)
|
translate)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '10')
|
_logPostTiming(enableTimingLog, postStartTime, '10')
|
||||||
|
|
||||||
isEvent = isEventPost(postJsonObject)
|
isEvent = isEventPost(postJsonObject)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '11')
|
_logPostTiming(enableTimingLog, postStartTime, '11')
|
||||||
|
|
||||||
editStr = getEditIconHtml(baseDir, nickname, domainFull,
|
editStr = _getEditIconHtml(baseDir, nickname, domainFull,
|
||||||
postJsonObject, actorNickname,
|
postJsonObject, actorNickname,
|
||||||
translate, isEvent)
|
translate, isEvent)
|
||||||
|
|
||||||
announceStr = \
|
announceStr = \
|
||||||
getAnnounceIconHtml(nickname, domainFull,
|
_getAnnounceIconHtml(nickname, domainFull,
|
||||||
postJsonObject,
|
postJsonObject,
|
||||||
isPublicRepeat,
|
isPublicRepeat,
|
||||||
isModerationPost,
|
isModerationPost,
|
||||||
|
|
@ -1306,7 +1307,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
timelinePostBookmark,
|
timelinePostBookmark,
|
||||||
boxName)
|
boxName)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '12')
|
_logPostTiming(enableTimingLog, postStartTime, '12')
|
||||||
|
|
||||||
# whether to show a like button
|
# whether to show a like button
|
||||||
hideLikeButtonFile = \
|
hideLikeButtonFile = \
|
||||||
|
|
@ -1315,7 +1316,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
if os.path.isfile(hideLikeButtonFile):
|
if os.path.isfile(hideLikeButtonFile):
|
||||||
showLikeButton = False
|
showLikeButton = False
|
||||||
|
|
||||||
likeStr = getLikeIconHtml(nickname, domainFull,
|
likeStr = _getLikeIconHtml(nickname, domainFull,
|
||||||
isModerationPost,
|
isModerationPost,
|
||||||
showLikeButton,
|
showLikeButton,
|
||||||
postJsonObject,
|
postJsonObject,
|
||||||
|
|
@ -1325,10 +1326,10 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
timelinePostBookmark,
|
timelinePostBookmark,
|
||||||
boxName)
|
boxName)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '12.5')
|
_logPostTiming(enableTimingLog, postStartTime, '12.5')
|
||||||
|
|
||||||
bookmarkStr = \
|
bookmarkStr = \
|
||||||
getBookmarkIconHtml(nickname, domainFull,
|
_getBookmarkIconHtml(nickname, domainFull,
|
||||||
postJsonObject,
|
postJsonObject,
|
||||||
isModerationPost,
|
isModerationPost,
|
||||||
translate,
|
translate,
|
||||||
|
|
@ -1337,14 +1338,14 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
pageNumberParam,
|
pageNumberParam,
|
||||||
timelinePostBookmark)
|
timelinePostBookmark)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '12.9')
|
_logPostTiming(enableTimingLog, postStartTime, '12.9')
|
||||||
|
|
||||||
isMuted = postIsMuted(baseDir, nickname, domain, postJsonObject, messageId)
|
isMuted = postIsMuted(baseDir, nickname, domain, postJsonObject, messageId)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13')
|
_logPostTiming(enableTimingLog, postStartTime, '13')
|
||||||
|
|
||||||
muteStr = \
|
muteStr = \
|
||||||
getMuteIconHtml(isMuted,
|
_getMuteIconHtml(isMuted,
|
||||||
postActor,
|
postActor,
|
||||||
messageId,
|
messageId,
|
||||||
nickname, domainFull,
|
nickname, domainFull,
|
||||||
|
|
@ -1355,7 +1356,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
translate)
|
translate)
|
||||||
|
|
||||||
deleteStr = \
|
deleteStr = \
|
||||||
getDeleteIconHtml(nickname, domainFull,
|
_getDeleteIconHtml(nickname, domainFull,
|
||||||
allowDeletion,
|
allowDeletion,
|
||||||
postActor,
|
postActor,
|
||||||
messageId,
|
messageId,
|
||||||
|
|
@ -1363,13 +1364,13 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
pageNumberParam,
|
pageNumberParam,
|
||||||
translate)
|
translate)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '13.1')
|
_logPostTiming(enableTimingLog, postStartTime, '13.1')
|
||||||
|
|
||||||
# get the title: x replies to y, x announces y, etc
|
# get the title: x replies to y, x announces y, etc
|
||||||
(titleStr2,
|
(titleStr2,
|
||||||
replyAvatarImageInPost,
|
replyAvatarImageInPost,
|
||||||
containerClassIcons,
|
containerClassIcons,
|
||||||
containerClass) = getPostTitleHtml(baseDir,
|
containerClass) = _getPostTitleHtml(baseDir,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
showRepeatIcon,
|
showRepeatIcon,
|
||||||
|
|
@ -1389,7 +1390,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
containerClass)
|
containerClass)
|
||||||
titleStr += titleStr2
|
titleStr += titleStr2
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '14')
|
_logPostTiming(enableTimingLog, postStartTime, '14')
|
||||||
|
|
||||||
attachmentStr, galleryStr = \
|
attachmentStr, galleryStr = \
|
||||||
getPostAttachmentsAsHtml(postJsonObject, boxName, translate,
|
getPostAttachmentsAsHtml(postJsonObject, boxName, translate,
|
||||||
|
|
@ -1398,9 +1399,9 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
bookmarkStr, deleteStr, muteStr)
|
bookmarkStr, deleteStr, muteStr)
|
||||||
|
|
||||||
publishedStr = \
|
publishedStr = \
|
||||||
getPublishedDateStr(postJsonObject, showPublishedDateOnly)
|
_getPublishedDateStr(postJsonObject, showPublishedDateOnly)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '15')
|
_logPostTiming(enableTimingLog, postStartTime, '15')
|
||||||
|
|
||||||
publishedLink = messageId
|
publishedLink = messageId
|
||||||
# blog posts should have no /statuses/ in their link
|
# blog posts should have no /statuses/ in their link
|
||||||
|
|
@ -1427,7 +1428,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
containerClassIcons = 'containericons dm'
|
containerClassIcons = 'containericons dm'
|
||||||
containerClass = 'container dm'
|
containerClass = 'container dm'
|
||||||
|
|
||||||
newFooterStr = getFooterWithIcons(showIcons,
|
newFooterStr = _getFooterWithIcons(showIcons,
|
||||||
containerClassIcons,
|
containerClassIcons,
|
||||||
replyStr, announceStr,
|
replyStr, announceStr,
|
||||||
likeStr, bookmarkStr,
|
likeStr, bookmarkStr,
|
||||||
|
|
@ -1466,7 +1467,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
postJsonObject['object']['summary'],
|
postJsonObject['object']['summary'],
|
||||||
postJsonObject['object']['content'])
|
postJsonObject['object']['content'])
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '16')
|
_logPostTiming(enableTimingLog, postStartTime, '16')
|
||||||
|
|
||||||
if not isPatch:
|
if not isPatch:
|
||||||
objectContent = \
|
objectContent = \
|
||||||
|
|
@ -1509,7 +1510,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
else:
|
else:
|
||||||
contentStr += cwContentStr
|
contentStr += cwContentStr
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '17')
|
_logPostTiming(enableTimingLog, postStartTime, '17')
|
||||||
|
|
||||||
if postJsonObject['object'].get('tag') and not isPatch:
|
if postJsonObject['object'].get('tag') and not isPatch:
|
||||||
contentStr = \
|
contentStr = \
|
||||||
|
|
@ -1531,7 +1532,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
|
|
||||||
# show blog citations
|
# show blog citations
|
||||||
citationsStr = \
|
citationsStr = \
|
||||||
getBlogCitationsHtml(boxName, postJsonObject, translate)
|
_getBlogCitationsHtml(boxName, postJsonObject, translate)
|
||||||
|
|
||||||
postHtml = ''
|
postHtml = ''
|
||||||
if boxName != 'tlmedia':
|
if boxName != 'tlmedia':
|
||||||
|
|
@ -1546,18 +1547,18 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
else:
|
else:
|
||||||
postHtml = galleryStr
|
postHtml = galleryStr
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '18')
|
_logPostTiming(enableTimingLog, postStartTime, '18')
|
||||||
|
|
||||||
# save the created html to the recent posts cache
|
# save the created html to the recent posts cache
|
||||||
if not showPublicOnly and storeToCache and \
|
if not showPublicOnly and storeToCache and \
|
||||||
boxName != 'tlmedia' and boxName != 'tlbookmarks' and \
|
boxName != 'tlmedia' and boxName != 'tlbookmarks' and \
|
||||||
boxName != 'bookmarks':
|
boxName != 'bookmarks':
|
||||||
saveIndividualPostAsHtmlToCache(baseDir, nickname, domain,
|
_saveIndividualPostAsHtmlToCache(baseDir, nickname, domain,
|
||||||
postJsonObject, postHtml)
|
postJsonObject, postHtml)
|
||||||
updateRecentPostsCache(recentPostsCache, maxRecentPosts,
|
updateRecentPostsCache(recentPostsCache, maxRecentPosts,
|
||||||
postJsonObject, postHtml)
|
postJsonObject, postHtml)
|
||||||
|
|
||||||
logPostTiming(enableTimingLog, postStartTime, '19')
|
_logPostTiming(enableTimingLog, postStartTime, '19')
|
||||||
|
|
||||||
return postHtml
|
return postHtml
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ def htmlProfileAfterSearch(cssCache: {},
|
||||||
imageUrl = profileJson['image']['url']
|
imageUrl = profileJson['image']['url']
|
||||||
|
|
||||||
profileStr = \
|
profileStr = \
|
||||||
getProfileHeaderAfterSearch(baseDir,
|
_getProfileHeaderAfterSearch(baseDir,
|
||||||
nickname, defaultTimeline,
|
nickname, defaultTimeline,
|
||||||
searchNickname,
|
searchNickname,
|
||||||
searchDomainFull,
|
searchDomainFull,
|
||||||
|
|
@ -287,7 +287,7 @@ def htmlProfileAfterSearch(cssCache: {},
|
||||||
return htmlHeaderWithExternalStyle(cssFilename) + profileStr + htmlFooter()
|
return htmlHeaderWithExternalStyle(cssFilename) + profileStr + htmlFooter()
|
||||||
|
|
||||||
|
|
||||||
def getProfileHeader(baseDir: str, nickname: str, domain: str,
|
def _getProfileHeader(baseDir: str, nickname: str, domain: str,
|
||||||
domainFull: str, translate: {},
|
domainFull: str, translate: {},
|
||||||
defaultTimeline: str,
|
defaultTimeline: str,
|
||||||
displayName: str,
|
displayName: str,
|
||||||
|
|
@ -327,7 +327,7 @@ def getProfileHeader(baseDir: str, nickname: str, domain: str,
|
||||||
return htmlStr
|
return htmlStr
|
||||||
|
|
||||||
|
|
||||||
def getProfileHeaderAfterSearch(baseDir: str,
|
def _getProfileHeaderAfterSearch(baseDir: str,
|
||||||
nickname: str, defaultTimeline: str,
|
nickname: str, defaultTimeline: str,
|
||||||
searchNickname: str,
|
searchNickname: str,
|
||||||
searchDomainFull: str,
|
searchDomainFull: str,
|
||||||
|
|
@ -568,7 +568,7 @@ def htmlProfile(rssIconAtTop: bool,
|
||||||
|
|
||||||
avatarUrl = profileJson['icon']['url']
|
avatarUrl = profileJson['icon']['url']
|
||||||
profileHeaderStr = \
|
profileHeaderStr = \
|
||||||
getProfileHeader(baseDir, nickname, domain,
|
_getProfileHeader(baseDir, nickname, domain,
|
||||||
domainFull, translate,
|
domainFull, translate,
|
||||||
defaultTimeline, displayName,
|
defaultTimeline, displayName,
|
||||||
avatarDescription,
|
avatarDescription,
|
||||||
|
|
@ -621,7 +621,7 @@ def htmlProfile(rssIconAtTop: bool,
|
||||||
|
|
||||||
if selected == 'posts':
|
if selected == 'posts':
|
||||||
profileStr += \
|
profileStr += \
|
||||||
htmlProfilePosts(recentPostsCache, maxRecentPosts,
|
_htmlProfilePosts(recentPostsCache, maxRecentPosts,
|
||||||
translate,
|
translate,
|
||||||
baseDir, httpPrefix, authorized,
|
baseDir, httpPrefix, authorized,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
|
|
@ -631,7 +631,7 @@ def htmlProfile(rssIconAtTop: bool,
|
||||||
showPublishedDateOnly) + licenseStr
|
showPublishedDateOnly) + licenseStr
|
||||||
elif selected == 'following':
|
elif selected == 'following':
|
||||||
profileStr += \
|
profileStr += \
|
||||||
htmlProfileFollowing(translate, baseDir, httpPrefix,
|
_htmlProfileFollowing(translate, baseDir, httpPrefix,
|
||||||
authorized, nickname,
|
authorized, nickname,
|
||||||
domain, port, session,
|
domain, port, session,
|
||||||
wfRequest, personCache, extraJson,
|
wfRequest, personCache, extraJson,
|
||||||
|
|
@ -640,7 +640,7 @@ def htmlProfile(rssIconAtTop: bool,
|
||||||
dormantMonths)
|
dormantMonths)
|
||||||
elif selected == 'followers':
|
elif selected == 'followers':
|
||||||
profileStr += \
|
profileStr += \
|
||||||
htmlProfileFollowing(translate, baseDir, httpPrefix,
|
_htmlProfileFollowing(translate, baseDir, httpPrefix,
|
||||||
authorized, nickname,
|
authorized, nickname,
|
||||||
domain, port, session,
|
domain, port, session,
|
||||||
wfRequest, personCache, extraJson,
|
wfRequest, personCache, extraJson,
|
||||||
|
|
@ -649,14 +649,14 @@ def htmlProfile(rssIconAtTop: bool,
|
||||||
maxItemsPerPage, dormantMonths)
|
maxItemsPerPage, dormantMonths)
|
||||||
elif selected == 'roles':
|
elif selected == 'roles':
|
||||||
profileStr += \
|
profileStr += \
|
||||||
htmlProfileRoles(translate, nickname, domainFull,
|
_htmlProfileRoles(translate, nickname, domainFull,
|
||||||
extraJson)
|
extraJson)
|
||||||
elif selected == 'skills':
|
elif selected == 'skills':
|
||||||
profileStr += \
|
profileStr += \
|
||||||
htmlProfileSkills(translate, nickname, domainFull, extraJson)
|
_htmlProfileSkills(translate, nickname, domainFull, extraJson)
|
||||||
elif selected == 'shares':
|
elif selected == 'shares':
|
||||||
profileStr += \
|
profileStr += \
|
||||||
htmlProfileShares(actor, translate,
|
_htmlProfileShares(actor, translate,
|
||||||
nickname, domainFull,
|
nickname, domainFull,
|
||||||
extraJson) + licenseStr
|
extraJson) + licenseStr
|
||||||
|
|
||||||
|
|
@ -666,7 +666,7 @@ def htmlProfile(rssIconAtTop: bool,
|
||||||
return profileStr
|
return profileStr
|
||||||
|
|
||||||
|
|
||||||
def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
|
def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
translate: {},
|
translate: {},
|
||||||
baseDir: str, httpPrefix: str,
|
baseDir: str, httpPrefix: str,
|
||||||
authorized: bool,
|
authorized: bool,
|
||||||
|
|
@ -685,11 +685,13 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
currPage = 1
|
currPage = 1
|
||||||
boxName = 'outbox'
|
boxName = 'outbox'
|
||||||
while ctr < maxItems and currPage < 4:
|
while ctr < maxItems and currPage < 4:
|
||||||
|
outboxFeedPathStr = \
|
||||||
|
'/users/' + nickname + '/' + boxName + '?page=' + \
|
||||||
|
str(currPage)
|
||||||
outboxFeed = \
|
outboxFeed = \
|
||||||
personBoxJson({}, session, baseDir, domain,
|
personBoxJson({}, session, baseDir, domain,
|
||||||
port,
|
port,
|
||||||
'/users/' + nickname + '/' + boxName + '?page=' +
|
outboxFeedPathStr,
|
||||||
str(currPage),
|
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
10, boxName,
|
10, boxName,
|
||||||
authorized, 0, False, 0)
|
authorized, 0, False, 0)
|
||||||
|
|
@ -720,7 +722,7 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
return profileStr
|
return profileStr
|
||||||
|
|
||||||
|
|
||||||
def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
def _htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
||||||
authorized: bool,
|
authorized: bool,
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
session, wfRequest: {}, personCache: {},
|
session, wfRequest: {}, personCache: {},
|
||||||
|
|
@ -756,7 +758,7 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
||||||
dormantMonths)
|
dormantMonths)
|
||||||
|
|
||||||
profileStr += \
|
profileStr += \
|
||||||
individualFollowAsHtml(translate, baseDir, session,
|
_individualFollowAsHtml(translate, baseDir, session,
|
||||||
wfRequest, personCache,
|
wfRequest, personCache,
|
||||||
domain, followingActor,
|
domain, followingActor,
|
||||||
authorized, nickname,
|
authorized, nickname,
|
||||||
|
|
@ -778,7 +780,7 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
||||||
return profileStr
|
return profileStr
|
||||||
|
|
||||||
|
|
||||||
def htmlProfileRoles(translate: {}, nickname: str, domain: str,
|
def _htmlProfileRoles(translate: {}, nickname: str, domain: str,
|
||||||
rolesJson: {}) -> str:
|
rolesJson: {}) -> str:
|
||||||
"""Shows roles on the profile screen
|
"""Shows roles on the profile screen
|
||||||
"""
|
"""
|
||||||
|
|
@ -801,7 +803,7 @@ def htmlProfileRoles(translate: {}, nickname: str, domain: str,
|
||||||
return profileStr
|
return profileStr
|
||||||
|
|
||||||
|
|
||||||
def htmlProfileSkills(translate: {}, nickname: str, domain: str,
|
def _htmlProfileSkills(translate: {}, nickname: str, domain: str,
|
||||||
skillsJson: {}) -> str:
|
skillsJson: {}) -> str:
|
||||||
"""Shows skills on the profile screen
|
"""Shows skills on the profile screen
|
||||||
"""
|
"""
|
||||||
|
|
@ -817,7 +819,7 @@ def htmlProfileSkills(translate: {}, nickname: str, domain: str,
|
||||||
return profileStr
|
return profileStr
|
||||||
|
|
||||||
|
|
||||||
def htmlProfileShares(actor: str, translate: {},
|
def _htmlProfileShares(actor: str, translate: {},
|
||||||
nickname: str, domain: str, sharesJson: {}) -> str:
|
nickname: str, domain: str, sharesJson: {}) -> str:
|
||||||
"""Shows shares on the profile screen
|
"""Shows shares on the profile screen
|
||||||
"""
|
"""
|
||||||
|
|
@ -1450,7 +1452,7 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
|
||||||
return editProfileForm
|
return editProfileForm
|
||||||
|
|
||||||
|
|
||||||
def individualFollowAsHtml(translate: {},
|
def _individualFollowAsHtml(translate: {},
|
||||||
baseDir: str, session, wfRequest: {},
|
baseDir: str, session, wfRequest: {},
|
||||||
personCache: {}, domain: str,
|
personCache: {}, domain: str,
|
||||||
followUrl: str,
|
followUrl: str,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from utils import locatePost
|
||||||
from utils import isPublicPost
|
from utils import isPublicPost
|
||||||
from utils import firstParagraphFromString
|
from utils import firstParagraphFromString
|
||||||
from utils import searchBoxPosts
|
from utils import searchBoxPosts
|
||||||
from utils import getHashtagCategory
|
from categories import getHashtagCategory
|
||||||
from feeds import rss2TagHeader
|
from feeds import rss2TagHeader
|
||||||
from feeds import rss2TagFooter
|
from feeds import rss2TagFooter
|
||||||
from webapp_utils import getAltPath
|
from webapp_utils import getAltPath
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ from webapp_headerbuttons import headerButtonsTimeline
|
||||||
from posts import isModerator
|
from posts import isModerator
|
||||||
|
|
||||||
|
|
||||||
def logTimelineTiming(enableTimingLog: bool, timelineStartTime,
|
def _logTimelineTiming(enableTimingLog: bool, timelineStartTime,
|
||||||
boxName: str, debugId: str) -> None:
|
boxName: str, debugId: str) -> None:
|
||||||
"""Create a log of timings for performance tuning
|
"""Create a log of timings for performance tuning
|
||||||
"""
|
"""
|
||||||
|
|
@ -127,7 +127,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
bannerFile, bannerFilename = \
|
bannerFile, bannerFilename = \
|
||||||
getBannerFile(baseDir, nickname, domain, theme)
|
getBannerFile(baseDir, nickname, domain, theme)
|
||||||
|
|
||||||
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '1')
|
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '1')
|
||||||
|
|
||||||
# is the user a moderator?
|
# is the user a moderator?
|
||||||
if not moderator:
|
if not moderator:
|
||||||
|
|
@ -137,7 +137,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
if not editor:
|
if not editor:
|
||||||
editor = isEditor(baseDir, nickname)
|
editor = isEditor(baseDir, nickname)
|
||||||
|
|
||||||
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '2')
|
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '2')
|
||||||
|
|
||||||
# the appearance of buttons - highlighted or not
|
# the appearance of buttons - highlighted or not
|
||||||
inboxButton = 'button'
|
inboxButton = 'button'
|
||||||
|
|
@ -221,7 +221,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
'" src="/icons/person.png"/></a>\n'
|
'" src="/icons/person.png"/></a>\n'
|
||||||
break
|
break
|
||||||
|
|
||||||
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '3')
|
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '3')
|
||||||
|
|
||||||
# moderation / reports button
|
# moderation / reports button
|
||||||
moderationButtonStr = ''
|
moderationButtonStr = ''
|
||||||
|
|
@ -256,7 +256,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
|
|
||||||
tlStr = htmlHeaderWithExternalStyle(cssFilename)
|
tlStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
|
|
||||||
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '4')
|
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '4')
|
||||||
|
|
||||||
# if this is a news instance and we are viewing the news timeline
|
# if this is a news instance and we are viewing the news timeline
|
||||||
newsHeader = False
|
newsHeader = False
|
||||||
|
|
@ -487,17 +487,17 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
|
|
||||||
tlStr += '</div>\n</form>\n'
|
tlStr += '</div>\n</form>\n'
|
||||||
|
|
||||||
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '6')
|
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '6')
|
||||||
|
|
||||||
if boxName == 'tlshares':
|
if boxName == 'tlshares':
|
||||||
maxSharesPerAccount = itemsPerPage
|
maxSharesPerAccount = itemsPerPage
|
||||||
return (tlStr +
|
return (tlStr +
|
||||||
htmlSharesTimeline(translate, pageNumber, itemsPerPage,
|
_htmlSharesTimeline(translate, pageNumber, itemsPerPage,
|
||||||
baseDir, actor, nickname, domain, port,
|
baseDir, actor, nickname, domain, port,
|
||||||
maxSharesPerAccount, httpPrefix) +
|
maxSharesPerAccount, httpPrefix) +
|
||||||
htmlFooter())
|
htmlFooter())
|
||||||
|
|
||||||
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '7')
|
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '7')
|
||||||
|
|
||||||
# page up arrow
|
# page up arrow
|
||||||
if pageNumber > 1:
|
if pageNumber > 1:
|
||||||
|
|
@ -543,12 +543,12 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
preparePostFromHtmlCache(currTlStr,
|
preparePostFromHtmlCache(currTlStr,
|
||||||
boxName,
|
boxName,
|
||||||
pageNumber)
|
pageNumber)
|
||||||
logTimelineTiming(enableTimingLog,
|
_logTimelineTiming(enableTimingLog,
|
||||||
timelineStartTime,
|
timelineStartTime,
|
||||||
boxName, '10')
|
boxName, '10')
|
||||||
|
|
||||||
if not currTlStr:
|
if not currTlStr:
|
||||||
logTimelineTiming(enableTimingLog,
|
_logTimelineTiming(enableTimingLog,
|
||||||
timelineStartTime,
|
timelineStartTime,
|
||||||
boxName, '11')
|
boxName, '11')
|
||||||
|
|
||||||
|
|
@ -570,7 +570,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
showIndividualPostIcons,
|
showIndividualPostIcons,
|
||||||
manuallyApproveFollowers,
|
manuallyApproveFollowers,
|
||||||
False, True)
|
False, True)
|
||||||
logTimelineTiming(enableTimingLog,
|
_logTimelineTiming(enableTimingLog,
|
||||||
timelineStartTime, boxName, '12')
|
timelineStartTime, boxName, '12')
|
||||||
|
|
||||||
if currTlStr:
|
if currTlStr:
|
||||||
|
|
@ -612,7 +612,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
|
||||||
rightColumnStr + ' </td>\n'
|
rightColumnStr + ' </td>\n'
|
||||||
tlStr += ' </tr>\n'
|
tlStr += ' </tr>\n'
|
||||||
|
|
||||||
logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '9')
|
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '9')
|
||||||
|
|
||||||
tlStr += ' </tbody>\n'
|
tlStr += ' </tbody>\n'
|
||||||
tlStr += '</table>\n'
|
tlStr += '</table>\n'
|
||||||
|
|
@ -656,7 +656,7 @@ def htmlIndividualShare(actor: str, item: {}, translate: {},
|
||||||
return profileStr
|
return profileStr
|
||||||
|
|
||||||
|
|
||||||
def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
def _htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
||||||
baseDir: str, actor: str,
|
baseDir: str, actor: str,
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
maxSharesPerAccount: int, httpPrefix: str) -> str:
|
maxSharesPerAccount: int, httpPrefix: str) -> str:
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ def getContentWarningButton(postID: str, translate: {},
|
||||||
'</div></details>\n'
|
'</div></details>\n'
|
||||||
|
|
||||||
|
|
||||||
def getActorPropertyUrl(actorJson: {}, propertyName: str) -> str:
|
def _getActorPropertyUrl(actorJson: {}, propertyName: str) -> str:
|
||||||
"""Returns a url property from an actor
|
"""Returns a url property from an actor
|
||||||
"""
|
"""
|
||||||
if not actorJson.get('attachment'):
|
if not actorJson.get('attachment'):
|
||||||
|
|
@ -206,10 +206,10 @@ def getActorPropertyUrl(actorJson: {}, propertyName: str) -> str:
|
||||||
def getBlogAddress(actorJson: {}) -> str:
|
def getBlogAddress(actorJson: {}) -> str:
|
||||||
"""Returns blog address for the given actor
|
"""Returns blog address for the given actor
|
||||||
"""
|
"""
|
||||||
return getActorPropertyUrl(actorJson, 'Blog')
|
return _getActorPropertyUrl(actorJson, 'Blog')
|
||||||
|
|
||||||
|
|
||||||
def setActorPropertyUrl(actorJson: {}, propertyName: str, url: str) -> None:
|
def _setActorPropertyUrl(actorJson: {}, propertyName: str, url: str) -> None:
|
||||||
"""Sets a url for the given actor property
|
"""Sets a url for the given actor property
|
||||||
"""
|
"""
|
||||||
if not actorJson.get('attachment'):
|
if not actorJson.get('attachment'):
|
||||||
|
|
@ -269,7 +269,7 @@ def setActorPropertyUrl(actorJson: {}, propertyName: str, url: str) -> None:
|
||||||
def setBlogAddress(actorJson: {}, blogAddress: str) -> None:
|
def setBlogAddress(actorJson: {}, blogAddress: str) -> None:
|
||||||
"""Sets an blog address for the given actor
|
"""Sets an blog address for the given actor
|
||||||
"""
|
"""
|
||||||
setActorPropertyUrl(actorJson, 'Blog', removeHtml(blogAddress))
|
_setActorPropertyUrl(actorJson, 'Blog', removeHtml(blogAddress))
|
||||||
|
|
||||||
|
|
||||||
def updateAvatarImageCache(session, baseDir: str, httpPrefix: str,
|
def updateAvatarImageCache(session, baseDir: str, httpPrefix: str,
|
||||||
|
|
@ -475,7 +475,7 @@ def postContainsPublic(postJsonObject: {}) -> bool:
|
||||||
return containsPublic
|
return containsPublic
|
||||||
|
|
||||||
|
|
||||||
def getImageFile(baseDir: str, name: str, directory: str,
|
def _getImageFile(baseDir: str, name: str, directory: str,
|
||||||
nickname: str, domain: str, theme: str) -> (str, str):
|
nickname: str, domain: str, theme: str) -> (str, str):
|
||||||
"""
|
"""
|
||||||
returns the filenames for an image with the given name
|
returns the filenames for an image with the given name
|
||||||
|
|
@ -495,28 +495,28 @@ def getImageFile(baseDir: str, name: str, directory: str,
|
||||||
|
|
||||||
def getBannerFile(baseDir: str,
|
def getBannerFile(baseDir: str,
|
||||||
nickname: str, domain: str, theme: str) -> (str, str):
|
nickname: str, domain: str, theme: str) -> (str, str):
|
||||||
return getImageFile(baseDir, 'banner',
|
return _getImageFile(baseDir, 'banner',
|
||||||
baseDir + '/accounts/' + nickname + '@' + domain,
|
baseDir + '/accounts/' + nickname + '@' + domain,
|
||||||
nickname, domain, theme)
|
nickname, domain, theme)
|
||||||
|
|
||||||
|
|
||||||
def getSearchBannerFile(baseDir: str,
|
def getSearchBannerFile(baseDir: str,
|
||||||
nickname: str, domain: str, theme: str) -> (str, str):
|
nickname: str, domain: str, theme: str) -> (str, str):
|
||||||
return getImageFile(baseDir, 'search_banner',
|
return _getImageFile(baseDir, 'search_banner',
|
||||||
baseDir + '/accounts/' + nickname + '@' + domain,
|
baseDir + '/accounts/' + nickname + '@' + domain,
|
||||||
nickname, domain, theme)
|
nickname, domain, theme)
|
||||||
|
|
||||||
|
|
||||||
def getLeftImageFile(baseDir: str,
|
def getLeftImageFile(baseDir: str,
|
||||||
nickname: str, domain: str, theme: str) -> (str, str):
|
nickname: str, domain: str, theme: str) -> (str, str):
|
||||||
return getImageFile(baseDir, 'left_col_image',
|
return _getImageFile(baseDir, 'left_col_image',
|
||||||
baseDir + '/accounts/' + nickname + '@' + domain,
|
baseDir + '/accounts/' + nickname + '@' + domain,
|
||||||
nickname, domain, theme)
|
nickname, domain, theme)
|
||||||
|
|
||||||
|
|
||||||
def getRightImageFile(baseDir: str,
|
def getRightImageFile(baseDir: str,
|
||||||
nickname: str, domain: str, theme: str) -> (str, str):
|
nickname: str, domain: str, theme: str) -> (str, str):
|
||||||
return getImageFile(baseDir, 'right_col_image',
|
return _getImageFile(baseDir, 'right_col_image',
|
||||||
baseDir + '/accounts/' + nickname + '@' + domain,
|
baseDir + '/accounts/' + nickname + '@' + domain,
|
||||||
nickname, domain, theme)
|
nickname, domain, theme)
|
||||||
|
|
||||||
|
|
@ -542,16 +542,6 @@ def htmlFooter() -> str:
|
||||||
return htmlStr
|
return htmlStr
|
||||||
|
|
||||||
|
|
||||||
def getFontFromCss(css: str) -> (str, str):
|
|
||||||
"""Returns the font name and format
|
|
||||||
"""
|
|
||||||
if ' url(' not in css:
|
|
||||||
return None, None
|
|
||||||
fontName = css.split(" url(")[1].split(")")[0].replace("'", '')
|
|
||||||
fontFormat = css.split(" format('")[1].split("')")[0]
|
|
||||||
return fontName, fontFormat
|
|
||||||
|
|
||||||
|
|
||||||
def loadIndividualPostAsHtmlFromCache(baseDir: str,
|
def loadIndividualPostAsHtmlFromCache(baseDir: str,
|
||||||
nickname: str, domain: str,
|
nickname: str, domain: str,
|
||||||
postJsonObject: {}) -> str:
|
postJsonObject: {}) -> str:
|
||||||
|
|
|
||||||
12
webfinger.py
12
webfinger.py
|
|
@ -25,7 +25,7 @@ from utils import saveJson
|
||||||
from utils import getProtocolPrefixes
|
from utils import getProtocolPrefixes
|
||||||
|
|
||||||
|
|
||||||
def parseHandle(handle: str) -> (str, str):
|
def _parseHandle(handle: str) -> (str, str):
|
||||||
if '.' not in handle:
|
if '.' not in handle:
|
||||||
return None, None
|
return None, None
|
||||||
prefixes = getProtocolPrefixes()
|
prefixes = getProtocolPrefixes()
|
||||||
|
|
@ -54,7 +54,7 @@ def webfingerHandle(session, handle: str, httpPrefix: str,
|
||||||
print('WARN: No session specified for webfingerHandle')
|
print('WARN: No session specified for webfingerHandle')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
nickname, domain = parseHandle(handle)
|
nickname, domain = _parseHandle(handle)
|
||||||
if not nickname:
|
if not nickname:
|
||||||
return None
|
return None
|
||||||
wfDomain = domain
|
wfDomain = domain
|
||||||
|
|
@ -97,7 +97,7 @@ def webfingerHandle(session, handle: str, httpPrefix: str,
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def generateMagicKey(publicKeyPem) -> str:
|
def _generateMagicKey(publicKeyPem) -> str:
|
||||||
"""See magic_key method in
|
"""See magic_key method in
|
||||||
https://github.com/tootsuite/mastodon/blob/
|
https://github.com/tootsuite/mastodon/blob/
|
||||||
707ddf7808f90e3ab042d7642d368c2ce8e95e6f/app/models/account.rb
|
707ddf7808f90e3ab042d7642d368c2ce8e95e6f/app/models/account.rb
|
||||||
|
|
@ -170,7 +170,7 @@ def createWebfingerEndpoint(nickname: str, domain: str, port: int,
|
||||||
"type": "application/activity+json"
|
"type": "application/activity+json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"href": generateMagicKey(publicKeyPem),
|
"href": _generateMagicKey(publicKeyPem),
|
||||||
"rel": "magic-public-key"
|
"rel": "magic-public-key"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -271,7 +271,7 @@ def webfingerLookup(path: str, baseDir: str,
|
||||||
return wfJson
|
return wfJson
|
||||||
|
|
||||||
|
|
||||||
def webfingerUpdateFromProfile(wfJson: {}, actorJson: {}) -> bool:
|
def _webfingerUpdateFromProfile(wfJson: {}, actorJson: {}) -> bool:
|
||||||
"""Updates webfinger Email/blog/xmpp links from profile
|
"""Updates webfinger Email/blog/xmpp links from profile
|
||||||
Returns true if one or more tags has been changed
|
Returns true if one or more tags has been changed
|
||||||
"""
|
"""
|
||||||
|
|
@ -350,6 +350,6 @@ def webfingerUpdate(baseDir: str, nickname: str, domain: str,
|
||||||
if not actorJson:
|
if not actorJson:
|
||||||
return
|
return
|
||||||
|
|
||||||
if webfingerUpdateFromProfile(wfJson, actorJson):
|
if _webfingerUpdateFromProfile(wfJson, actorJson):
|
||||||
if saveJson(wfJson, filename):
|
if saveJson(wfJson, filename):
|
||||||
cachedWebfingers[handle] = wfJson
|
cachedWebfingers[handle] = wfJson
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue