Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon
499
daemon.py
|
@ -25,6 +25,9 @@ from webfinger import webfingerMeta
|
|||
from webfinger import webfingerNodeInfo
|
||||
from webfinger import webfingerLookup
|
||||
from webfinger import webfingerUpdate
|
||||
from mastoapiv1 import getMastoApiV1Account
|
||||
from mastoapiv1 import getMastApiV1Id
|
||||
from mastoapiv1 import getNicknameFromMastoApiV1Id
|
||||
from metadata import metaDataInstance
|
||||
from metadata import metaDataNodeInfo
|
||||
from pgp import getEmailAddress
|
||||
|
@ -526,7 +529,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('Host', callingDomain)
|
||||
self.send_header('WWW-Authenticate',
|
||||
'title="Login to Epicyon", Basic realm="epicyon"')
|
||||
self.send_header('X-Robots-Tag', 'noindex')
|
||||
self.send_header('X-Robots-Tag',
|
||||
'noindex, nofollow, noarchive, nosnippet')
|
||||
self.send_header('Referrer-Policy', 'origin')
|
||||
self.end_headers()
|
||||
|
||||
def _logout_headers(self, fileFormat: str, length: int,
|
||||
|
@ -538,7 +543,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('Host', callingDomain)
|
||||
self.send_header('WWW-Authenticate',
|
||||
'title="Login to Epicyon", Basic realm="epicyon"')
|
||||
self.send_header('X-Robots-Tag', 'noindex')
|
||||
self.send_header('X-Robots-Tag',
|
||||
'noindex, nofollow, noarchive, nosnippet')
|
||||
self.send_header('Referrer-Policy', 'origin')
|
||||
self.end_headers()
|
||||
|
||||
def _logout_redirect(self, redirect: str, cookie: str,
|
||||
|
@ -553,7 +560,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('Host', callingDomain)
|
||||
self.send_header('InstanceID', self.server.instanceId)
|
||||
self.send_header('Content-Length', '0')
|
||||
self.send_header('X-Robots-Tag', 'noindex')
|
||||
self.send_header('X-Robots-Tag',
|
||||
'noindex, nofollow, noarchive, nosnippet')
|
||||
self.send_header('Referrer-Policy', 'origin')
|
||||
self.end_headers()
|
||||
|
||||
def _set_headers_base(self, fileFormat: str, length: int, cookie: str,
|
||||
|
@ -571,8 +580,10 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('Cookie', cookieStr)
|
||||
self.send_header('Host', callingDomain)
|
||||
self.send_header('InstanceID', self.server.instanceId)
|
||||
self.send_header('X-Robots-Tag', 'noindex')
|
||||
self.send_header('X-Robots-Tag',
|
||||
'noindex, nofollow, noarchive, nosnippet')
|
||||
self.send_header('X-Clacks-Overhead', 'GNU Natalie Nguyen')
|
||||
self.send_header('Referrer-Policy', 'origin')
|
||||
self.send_header('Accept-Ranges', 'none')
|
||||
|
||||
def _set_headers(self, fileFormat: str, length: int, cookie: str,
|
||||
|
@ -657,7 +668,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('Host', callingDomain)
|
||||
self.send_header('InstanceID', self.server.instanceId)
|
||||
self.send_header('Content-Length', '0')
|
||||
self.send_header('X-Robots-Tag', 'noindex')
|
||||
self.send_header('X-Robots-Tag',
|
||||
'noindex, nofollow, noarchive, nosnippet')
|
||||
self.send_header('Referrer-Policy', 'origin')
|
||||
self.end_headers()
|
||||
|
||||
def _httpReturnCode(self, httpCode: int, httpDescription: str,
|
||||
|
@ -677,7 +690,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
msgLenStr = str(len(msg))
|
||||
self.send_header('Content-Length', msgLenStr)
|
||||
self.send_header('X-Robots-Tag', 'noindex')
|
||||
self.send_header('X-Robots-Tag',
|
||||
'noindex, nofollow, noarchive, nosnippet')
|
||||
self.send_header('Referrer-Policy', 'origin')
|
||||
self.end_headers()
|
||||
if not self._write(msg):
|
||||
print('Error when showing ' + str(httpCode))
|
||||
|
@ -769,26 +784,101 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return True
|
||||
return False
|
||||
|
||||
def _mastoApi(self, callingDomain: str) -> bool:
|
||||
def _mastoApiV1(self, path: str, callingDomain: str,
|
||||
authorized: bool,
|
||||
httpPrefix: str,
|
||||
baseDir: str, nickname: str, domain: str,
|
||||
domainFull: str) -> bool:
|
||||
"""This is a vestigil mastodon API for the purpose
|
||||
of returning an empty result to sites like
|
||||
https://mastopeek.app-dist.eu
|
||||
"""
|
||||
if not self.path.startswith('/api/v1/'):
|
||||
if not path.startswith('/api/v1/'):
|
||||
return False
|
||||
if self.server.debug:
|
||||
print('DEBUG: mastodon api ' + self.path)
|
||||
print('mastodon api v1: ' + path)
|
||||
print('mastodon api v1: authorized ' + str(authorized))
|
||||
print('mastodon api v1: nickname ' + str(nickname))
|
||||
|
||||
sendJson = None
|
||||
sendJsonStr = ''
|
||||
|
||||
# parts of the api needing authorization
|
||||
if authorized and nickname:
|
||||
if path == '/api/v1/accounts/verify_credentials':
|
||||
sendJson = getMastoApiV1Account(baseDir, nickname, domain)
|
||||
sendJsonStr = 'masto API account sent for ' + nickname
|
||||
|
||||
# Parts of the api which don't need authorization
|
||||
mastoId = getMastApiV1Id(path)
|
||||
if mastoId is not None:
|
||||
pathNickname = getNicknameFromMastoApiV1Id(mastoId)
|
||||
if pathNickname:
|
||||
originalPath = path
|
||||
if '/followers?' in path or \
|
||||
'/following?' in path or \
|
||||
'/search?' in path or \
|
||||
'/relationships?' in path or \
|
||||
'/statuses?' in path:
|
||||
path = path.split('?')[0]
|
||||
if path.endswith('/followers'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API followers sent for ' + nickname
|
||||
elif path.endswith('/following'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API following sent for ' + nickname
|
||||
elif path.endswith('/statuses'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API statuses sent for ' + nickname
|
||||
elif path.endswith('/search'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API search sent ' + originalPath
|
||||
elif path.endswith('/relationships'):
|
||||
sendJson = []
|
||||
sendJsonStr = \
|
||||
'masto API relationships sent ' + originalPath
|
||||
else:
|
||||
sendJson = \
|
||||
getMastoApiV1Account(baseDir, pathNickname, domain)
|
||||
sendJsonStr = 'masto API account sent for ' + nickname
|
||||
|
||||
if path.startswith('/api/v1/blocks'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API instance blocks sent'
|
||||
elif path.startswith('/api/v1/favorites'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API favorites sent'
|
||||
elif path.startswith('/api/v1/follow_requests'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API follow requests sent'
|
||||
elif path.startswith('/api/v1/mutes'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API mutes sent'
|
||||
elif path.startswith('/api/v1/notifications'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API notifications sent'
|
||||
elif path.startswith('/api/v1/reports'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API reports sent'
|
||||
elif path.startswith('/api/v1/statuses'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API statuses sent'
|
||||
elif path.startswith('/api/v1/timelines'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API timelines sent'
|
||||
|
||||
adminNickname = getConfigParam(self.server.baseDir, 'admin')
|
||||
if adminNickname and self.path == '/api/v1/instance':
|
||||
if adminNickname and path == '/api/v1/instance':
|
||||
instanceDescriptionShort = \
|
||||
getConfigParam(self.server.baseDir,
|
||||
'instanceDescriptionShort')
|
||||
instanceDescriptionShort = 'Yet another Epicyon Instance'
|
||||
if not instanceDescriptionShort:
|
||||
instanceDescriptionShort = \
|
||||
self.server.translate['Yet another Epicyon Instance']
|
||||
instanceDescription = getConfigParam(self.server.baseDir,
|
||||
'instanceDescription')
|
||||
instanceTitle = getConfigParam(self.server.baseDir,
|
||||
'instanceTitle')
|
||||
instanceJson = \
|
||||
sendJson = \
|
||||
metaDataInstance(instanceTitle,
|
||||
instanceDescriptionShort,
|
||||
instanceDescription,
|
||||
|
@ -800,29 +890,21 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.registration,
|
||||
self.server.systemLanguage,
|
||||
self.server.projectVersion)
|
||||
msg = json.dumps(instanceJson).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
if self._hasAccept(callingDomain):
|
||||
if 'application/ld+json' in self.headers['Accept']:
|
||||
self._set_headers('application/ld+json', msglen,
|
||||
None, callingDomain)
|
||||
else:
|
||||
self._set_headers('application/json', msglen,
|
||||
None, callingDomain)
|
||||
else:
|
||||
self._set_headers('application/ld+json', msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
print('instance metadata sent')
|
||||
return True
|
||||
if self.path.startswith('/api/v1/instance/peers'):
|
||||
sendJsonStr = 'masto API instance metadata sent'
|
||||
elif path.startswith('/api/v1/instance/peers'):
|
||||
# This is just a dummy result.
|
||||
# Showing the full list of peers would have privacy implications.
|
||||
# On a large instance you are somewhat lost in the crowd, but on
|
||||
# small instances a full list of peers would convey a lot of
|
||||
# information about the interests of a small number of accounts
|
||||
msg = json.dumps(['mastodon.social',
|
||||
self.server.domainFull]).encode('utf-8')
|
||||
sendJson = ['mastodon.social', self.server.domainFull]
|
||||
sendJsonStr = 'masto API peers metadata sent'
|
||||
elif path.startswith('/api/v1/instance/activity'):
|
||||
sendJson = []
|
||||
sendJsonStr = 'masto API activity metadata sent'
|
||||
|
||||
if sendJson is not None:
|
||||
msg = json.dumps(sendJson).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
if self._hasAccept(callingDomain):
|
||||
if 'application/ld+json' in self.headers['Accept']:
|
||||
|
@ -835,28 +917,22 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers('application/ld+json', msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
print('instance peers metadata sent')
|
||||
return True
|
||||
if self.path.startswith('/api/v1/instance/activity'):
|
||||
# This is just a dummy result.
|
||||
msg = json.dumps([]).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
if self._hasAccept(callingDomain):
|
||||
if 'application/ld+json' in self.headers['Accept']:
|
||||
self._set_headers('application/ld+json', msglen,
|
||||
None, callingDomain)
|
||||
else:
|
||||
self._set_headers('application/json', msglen,
|
||||
None, callingDomain)
|
||||
else:
|
||||
self._set_headers('application/ld+json', msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
print('instance activity metadata sent')
|
||||
if sendJsonStr:
|
||||
print(sendJsonStr)
|
||||
return True
|
||||
|
||||
# no api endpoints were matched
|
||||
self._404()
|
||||
return True
|
||||
|
||||
def _mastoApi(self, path: str, callingDomain: str,
|
||||
authorized: bool, httpPrefix: str,
|
||||
baseDir: str, nickname: str, domain: str,
|
||||
domainFull: str) -> bool:
|
||||
return self._mastoApiV1(path, callingDomain, authorized,
|
||||
httpPrefix, baseDir, nickname, domain,
|
||||
domainFull)
|
||||
|
||||
def _nodeinfo(self, callingDomain: str) -> bool:
|
||||
if not self.path.startswith('/nodeinfo/2.0'):
|
||||
return False
|
||||
|
@ -3872,7 +3948,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if not actorJson.get('discoverable'):
|
||||
# discoverable in profile directory
|
||||
# which isn't implemented in Epicyon
|
||||
actorJson['discoverable'] = False
|
||||
actorJson['discoverable'] = True
|
||||
actorChanged = True
|
||||
if not actorJson['@context'][2].get('orgSchema'):
|
||||
actorJson['@context'][2]['orgSchema'] = \
|
||||
|
@ -3890,10 +3966,6 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if not actorJson['@context'][2].get('availability'):
|
||||
actorJson['@context'][2]['availaibility'] = \
|
||||
'toot:availability'
|
||||
if not actorJson['@context'][2].get('nomadicLocations'):
|
||||
actorJson['@context'][2]['nomadicLocations'] = \
|
||||
'toot:nomadicLocations'
|
||||
actorChanged = True
|
||||
if actorJson.get('capabilityAcquisitionEndpoint'):
|
||||
del actorJson['capabilityAcquisitionEndpoint']
|
||||
actorChanged = True
|
||||
|
@ -4239,6 +4311,35 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
del actorJson['movedTo']
|
||||
actorChanged = True
|
||||
|
||||
# Other accounts (alsoKnownAs)
|
||||
alsoKnownAs = []
|
||||
if actorJson.get('alsoKnownAs'):
|
||||
alsoKnownAs = actorJson['alsoKnownAs']
|
||||
if fields.get('alsoKnownAs'):
|
||||
alsoKnownAsStr = ''
|
||||
alsoKnownAsCtr = 0
|
||||
for altActor in alsoKnownAs:
|
||||
if alsoKnownAsCtr > 0:
|
||||
alsoKnownAsStr += ', '
|
||||
alsoKnownAsStr += altActor
|
||||
alsoKnownAsCtr += 1
|
||||
if fields['alsoKnownAs'] != alsoKnownAsStr and \
|
||||
'://' in fields['alsoKnownAs'] and \
|
||||
'@' not in fields['alsoKnownAs'] and \
|
||||
'.' in fields['alsoKnownAs']:
|
||||
newAlsoKnownAs = fields['alsoKnownAs'].split(',')
|
||||
alsoKnownAs = []
|
||||
for altActor in newAlsoKnownAs:
|
||||
altActor = altActor.strip()
|
||||
if '://' in altActor and '.' in altActor:
|
||||
alsoKnownAs.append(altActor)
|
||||
actorJson['alsoKnownAs'] = alsoKnownAs
|
||||
actorChanged = True
|
||||
else:
|
||||
if alsoKnownAs:
|
||||
del actorJson['alsoKnownAs']
|
||||
actorChanged = True
|
||||
|
||||
# change instance title
|
||||
if fields.get('instanceTitle'):
|
||||
currInstanceTitle = \
|
||||
|
@ -4718,6 +4819,14 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'https://w3id.org/security/v1',
|
||||
getDefaultPersonContext()
|
||||
]
|
||||
if actorJson.get('nomadicLocations'):
|
||||
del actorJson['nomadicLocations']
|
||||
if not actorJson.get('featured'):
|
||||
actorJson['featured'] = \
|
||||
actorJson['id'] + '/collections/featured'
|
||||
if not actorJson.get('featuredTags'):
|
||||
actorJson['featuredTags'] = \
|
||||
actorJson['id'] + '/collections/tags'
|
||||
randomizeActorImages(actorJson)
|
||||
saveJson(actorJson, actorFilename)
|
||||
webfingerUpdate(baseDir,
|
||||
|
@ -4920,7 +5029,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(faviconFilename,
|
||||
favType,
|
||||
favBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(favBinary)
|
||||
if debug:
|
||||
print('Sent favicon from cache: ' + callingDomain)
|
||||
|
@ -4932,7 +5041,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(faviconFilename,
|
||||
favType,
|
||||
favBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(favBinary)
|
||||
self.server.iconsCache[favFilename] = favBinary
|
||||
if self.server.debug:
|
||||
|
@ -4971,7 +5080,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(fontFilename,
|
||||
fontType,
|
||||
fontBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(fontBinary)
|
||||
if debug:
|
||||
print('font sent from cache: ' +
|
||||
|
@ -4987,7 +5096,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(fontFilename,
|
||||
fontType,
|
||||
fontBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(fontBinary)
|
||||
self.server.fontsCache[fontStr] = fontBinary
|
||||
if debug:
|
||||
|
@ -5279,6 +5388,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
ssbAddress = None
|
||||
emailAddress = None
|
||||
lockedAccount = False
|
||||
alsoKnownAs = None
|
||||
movedTo = ''
|
||||
actorJson = getPersonFromCache(baseDir,
|
||||
optionsActor,
|
||||
|
@ -5299,6 +5409,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
emailAddress = getEmailAddress(actorJson)
|
||||
PGPpubKey = getPGPpubKey(actorJson)
|
||||
PGPfingerprint = getPGPfingerprint(actorJson)
|
||||
if actorJson.get('alsoKnownAs'):
|
||||
alsoKnownAs = actorJson['alsoKnownAs']
|
||||
msg = htmlPersonOptions(self.server.defaultTimeline,
|
||||
self.server.cssCache,
|
||||
self.server.translate,
|
||||
|
@ -5318,7 +5430,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.dormantMonths,
|
||||
backToPath,
|
||||
lockedAccount,
|
||||
movedTo).encode('utf-8')
|
||||
movedTo, alsoKnownAs).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('text/html', msglen,
|
||||
cookie, callingDomain)
|
||||
|
@ -5367,7 +5479,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mediaBinary = avFile.read()
|
||||
self._set_headers_etag(mediaFilename, mediaFileType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show emoji done',
|
||||
|
@ -5407,7 +5519,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(emojiFilename,
|
||||
'image/' + mediaImageType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'background shown done',
|
||||
|
@ -5443,7 +5555,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(mediaFilename,
|
||||
mimeTypeStr,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
return
|
||||
else:
|
||||
|
@ -5454,7 +5566,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(mediaFilename,
|
||||
mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self.server.iconsCache[mediaStr] = mediaBinary
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
|
@ -5480,7 +5592,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(mediaFilename,
|
||||
mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'icon shown done',
|
||||
|
@ -8943,6 +9055,62 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return True
|
||||
return False
|
||||
|
||||
def _getFeaturedCollection(self, callingDomain: str,
|
||||
path: str,
|
||||
httpPrefix: str,
|
||||
domainFull: str):
|
||||
"""Returns the featured posts collections in
|
||||
actor/collections/featured
|
||||
TODO add ability to set a featured post
|
||||
"""
|
||||
featuredCollection = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams',
|
||||
{'atomUri': 'ostatus:atomUri',
|
||||
'conversation': 'ostatus:conversation',
|
||||
'inReplyToAtomUri': 'ostatus:inReplyToAtomUri',
|
||||
'sensitive': 'as:sensitive',
|
||||
'toot': 'http://joinmastodon.org/ns#',
|
||||
'votersCount': 'toot:votersCount'}],
|
||||
'id': httpPrefix + '://' + domainFull + path,
|
||||
'orderedItems': [],
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollection'
|
||||
}
|
||||
msg = json.dumps(featuredCollection,
|
||||
ensure_ascii=False).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('application/json', msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
|
||||
def _getFeaturedTagsCollection(self, callingDomain: str,
|
||||
path: str,
|
||||
httpPrefix: str,
|
||||
domainFull: str):
|
||||
"""Returns the featured tags collections in
|
||||
actor/collections/featuredTags
|
||||
TODO add ability to set a featured tags
|
||||
"""
|
||||
featuredTagsCollection = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams',
|
||||
{'atomUri': 'ostatus:atomUri',
|
||||
'conversation': 'ostatus:conversation',
|
||||
'inReplyToAtomUri': 'ostatus:inReplyToAtomUri',
|
||||
'sensitive': 'as:sensitive',
|
||||
'toot': 'http://joinmastodon.org/ns#',
|
||||
'votersCount': 'toot:votersCount'}],
|
||||
'id': httpPrefix + '://' + domainFull + path,
|
||||
'orderedItems': [],
|
||||
'totalItems': 0,
|
||||
'type': 'OrderedCollection'
|
||||
}
|
||||
msg = json.dumps(featuredTagsCollection,
|
||||
ensure_ascii=False).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('application/json', msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
|
||||
def _showPersonProfile(self, authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
|
@ -8954,61 +9122,61 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
"""Shows the profile for a person
|
||||
"""
|
||||
# look up a person
|
||||
getPerson = personLookup(domain, path, baseDir)
|
||||
if getPerson:
|
||||
if self._requestHTTP():
|
||||
actorJson = personLookup(domain, path, baseDir)
|
||||
if not actorJson:
|
||||
return False
|
||||
if self._requestHTTP():
|
||||
if not self.server.session:
|
||||
print('Starting new session during person lookup')
|
||||
self.server.session = createSession(proxyType)
|
||||
if not self.server.session:
|
||||
print('Starting new session during person lookup')
|
||||
self.server.session = createSession(proxyType)
|
||||
if not self.server.session:
|
||||
print('ERROR: GET failed to create session ' +
|
||||
'during person lookup')
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
msg = \
|
||||
htmlProfile(self.server.rssIconAtTop,
|
||||
self.server.cssCache,
|
||||
self.server.iconsAsButtons,
|
||||
self.server.defaultTimeline,
|
||||
self.server.recentPostsCache,
|
||||
self.server.maxRecentPosts,
|
||||
self.server.translate,
|
||||
self.server.projectVersion,
|
||||
baseDir,
|
||||
httpPrefix,
|
||||
authorized,
|
||||
getPerson, 'posts',
|
||||
self.server.session,
|
||||
self.server.cachedWebfingers,
|
||||
self.server.personCache,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
self.server.themeName,
|
||||
self.server.dormantMonths,
|
||||
self.server.peertubeInstances,
|
||||
None, None).encode('utf-8')
|
||||
print('ERROR: GET failed to create session ' +
|
||||
'during person lookup')
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
msg = \
|
||||
htmlProfile(self.server.rssIconAtTop,
|
||||
self.server.cssCache,
|
||||
self.server.iconsAsButtons,
|
||||
self.server.defaultTimeline,
|
||||
self.server.recentPostsCache,
|
||||
self.server.maxRecentPosts,
|
||||
self.server.translate,
|
||||
self.server.projectVersion,
|
||||
baseDir,
|
||||
httpPrefix,
|
||||
authorized,
|
||||
actorJson, 'posts',
|
||||
self.server.session,
|
||||
self.server.cachedWebfingers,
|
||||
self.server.personCache,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
self.server.themeName,
|
||||
self.server.dormantMonths,
|
||||
self.server.peertubeInstances,
|
||||
None, None).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('text/html', msglen,
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show profile 4 done',
|
||||
'show profile posts')
|
||||
else:
|
||||
if self._fetchAuthenticated():
|
||||
msgStr = json.dumps(actorJson, ensure_ascii=False)
|
||||
msg = msgStr.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('text/html', msglen,
|
||||
self._set_headers('application/ld+json', msglen,
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show profile 4 done',
|
||||
'show profile posts')
|
||||
else:
|
||||
if self._fetchAuthenticated():
|
||||
msg = json.dumps(getPerson,
|
||||
ensure_ascii=False).encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('application/json', msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
return False
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
|
||||
def _showBlogPage(self, authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
|
@ -9189,7 +9357,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mimeType = mediaFileMimeType(qrFilename)
|
||||
self._set_headers_etag(qrFilename, mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'login screen logo done',
|
||||
|
@ -9228,7 +9396,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mimeType = mediaFileMimeType(bannerFilename)
|
||||
self._set_headers_etag(bannerFilename, mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'account qrcode done',
|
||||
|
@ -9270,7 +9438,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mimeType = mediaFileMimeType(bannerFilename)
|
||||
self._set_headers_etag(bannerFilename, mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'account qrcode done',
|
||||
|
@ -9315,7 +9483,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(bgFilename,
|
||||
'image/' + ext,
|
||||
bgBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(bgBinary)
|
||||
self._benchmarkGETtimings(GETstartTime,
|
||||
GETtimings,
|
||||
|
@ -9359,7 +9527,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(mediaFilename,
|
||||
'image/' + mediaFileType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show media done',
|
||||
|
@ -9419,7 +9587,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(avatarFilename,
|
||||
'image/' + mediaImageType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'icon shown done',
|
||||
|
@ -9722,14 +9890,6 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'start', '_nodeinfo[callingDomain]')
|
||||
|
||||
# minimal mastodon api
|
||||
if self._mastoApi(callingDomain):
|
||||
return
|
||||
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'_nodeinfo[callingDomain]',
|
||||
'_mastoApi[callingDomain]')
|
||||
|
||||
if self.path == '/logout':
|
||||
if not self.server.newsInstance:
|
||||
msg = \
|
||||
|
@ -9822,6 +9982,19 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show logout', 'isAuthorized')
|
||||
|
||||
# minimal mastodon api
|
||||
if self._mastoApi(self.path, callingDomain, authorized,
|
||||
self.server.httpPrefix,
|
||||
self.server.baseDir,
|
||||
self.authorizedNickname,
|
||||
self.server.domain,
|
||||
self.server.domainFull):
|
||||
return
|
||||
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'_nodeinfo[callingDomain]',
|
||||
'_mastoApi[callingDomain]')
|
||||
|
||||
if not self.server.session:
|
||||
print('Starting new session during GET')
|
||||
self.server.session = createSession(self.server.proxyType)
|
||||
|
@ -9880,7 +10053,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if self.path == '/sharedInbox' or \
|
||||
self.path == '/users/inbox' or \
|
||||
self.path == '/actor/inbox' or \
|
||||
self.path == '/users/'+self.server.domain:
|
||||
self.path == '/users/' + self.server.domain:
|
||||
# if shared inbox is not enabled
|
||||
if not self.server.enableSharedInbox:
|
||||
self._503()
|
||||
|
@ -9958,6 +10131,24 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.debug)
|
||||
return
|
||||
|
||||
usersInPath = False
|
||||
if '/users/' in self.path:
|
||||
usersInPath = True
|
||||
|
||||
if usersInPath and self.path.endswith('/collections/featured'):
|
||||
self._getFeaturedCollection(callingDomain,
|
||||
self.path,
|
||||
self.server.httpPrefix,
|
||||
self.server.domainFull)
|
||||
return
|
||||
|
||||
if usersInPath and self.path.endswith('/collections/featuredTags'):
|
||||
self._getFeaturedTagsCollection(callingDomain,
|
||||
self.path,
|
||||
self.server.httpPrefix,
|
||||
self.server.domainFull)
|
||||
return
|
||||
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'sharedInbox enabled', 'rss3 done')
|
||||
|
||||
|
@ -10021,7 +10212,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
# list of registered devices for e2ee
|
||||
# see https://github.com/tootsuite/mastodon/pull/13820
|
||||
if authorized and '/users/' in self.path:
|
||||
if authorized and usersInPath:
|
||||
if self.path.endswith('/collections/devices'):
|
||||
nickname = self.path.split('/users/')
|
||||
if '/' in nickname:
|
||||
|
@ -10047,7 +10238,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'blog view done',
|
||||
'registered devices done')
|
||||
|
||||
if htmlGET and '/users/' in self.path:
|
||||
if htmlGET and usersInPath:
|
||||
# show the person options screen with view/follow/block/report
|
||||
if '?options=' in self.path:
|
||||
self._showPersonOptions(callingDomain, self.path,
|
||||
|
@ -10165,7 +10356,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'terms of service done')
|
||||
|
||||
# show a list of who you are following
|
||||
if htmlGET and authorized and '/users/' in self.path and \
|
||||
if htmlGET and authorized and usersInPath and \
|
||||
self.path.endswith('/followingaccounts'):
|
||||
nickname = getNicknameFromActor(self.path)
|
||||
followingFilename = \
|
||||
|
@ -10282,7 +10473,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mimeType = mediaFileMimeType(mediaFilename)
|
||||
self._set_headers_etag(mediaFilename, mimeType,
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'profile.css done',
|
||||
|
@ -10322,7 +10513,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mimeType = mediaFileMimeType(screenFilename)
|
||||
self._set_headers_etag(screenFilename, mimeType,
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'manifest logo done',
|
||||
|
@ -10368,7 +10559,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers_etag(iconFilename,
|
||||
mimeTypeStr,
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self.server.domainFull)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show screenshot done',
|
||||
|
@ -10382,7 +10573,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'login screen logo done')
|
||||
|
||||
# QR code for account handle
|
||||
if '/users/' in self.path and \
|
||||
if usersInPath and \
|
||||
self.path.endswith('/qrcode.png'):
|
||||
if self._showQRcode(callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
|
@ -10396,7 +10587,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'account qrcode done')
|
||||
|
||||
# search screen banner image
|
||||
if '/users/' in self.path:
|
||||
if usersInPath:
|
||||
if self.path.endswith('/search_banner.png'):
|
||||
if self._searchScreenBanner(callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
|
@ -10485,7 +10676,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
# cached avatar images
|
||||
# Note that this comes before the busy flag to avoid conflicts
|
||||
if self.path.startswith('/avatars/'):
|
||||
self._showCachedAvatar(callingDomain, self.path,
|
||||
self._showCachedAvatar(self.server.domainFull, self.path,
|
||||
self.server.baseDir,
|
||||
GETstartTime, GETtimings)
|
||||
return
|
||||
|
@ -10696,7 +10887,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'hashtag search done')
|
||||
|
||||
# show or hide buttons in the web interface
|
||||
if htmlGET and '/users/' in self.path and \
|
||||
if htmlGET and usersInPath and \
|
||||
self.path.endswith('/minimal') and \
|
||||
authorized:
|
||||
nickname = self.path.split('/users/')[1]
|
||||
|
@ -10716,7 +10907,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
# search for a fediverse address, shared item or emoji
|
||||
# from the web interface by selecting search icon
|
||||
if htmlGET and '/users/' in self.path:
|
||||
if htmlGET and usersInPath:
|
||||
if self.path.endswith('/search') or \
|
||||
'/search?' in self.path:
|
||||
if '?' in self.path:
|
||||
|
@ -10760,7 +10951,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'search screen shown done')
|
||||
|
||||
# Show the calendar for a user
|
||||
if htmlGET and '/users/' in self.path:
|
||||
if htmlGET and usersInPath:
|
||||
if '/calendar' in self.path:
|
||||
# show the calendar screen
|
||||
msg = htmlCalendar(self.server.cssCache,
|
||||
|
@ -10782,7 +10973,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'calendar shown done')
|
||||
|
||||
# Show confirmation for deleting a calendar event
|
||||
if htmlGET and '/users/' in self.path:
|
||||
if htmlGET and usersInPath:
|
||||
if '/eventdelete' in self.path and \
|
||||
'?time=' in self.path and \
|
||||
'?id=' in self.path:
|
||||
|
@ -10802,7 +10993,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'calendar delete shown done')
|
||||
|
||||
# search for emoji by name
|
||||
if htmlGET and '/users/' in self.path:
|
||||
if htmlGET and usersInPath:
|
||||
if self.path.endswith('/searchemoji'):
|
||||
# show the search screen
|
||||
msg = htmlSearchEmojiTextEntry(self.server.cssCache,
|
||||
|
@ -11320,7 +11511,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'individual post done',
|
||||
'post replies done')
|
||||
|
||||
if self.path.endswith('/roles') and '/users/' in self.path:
|
||||
if self.path.endswith('/roles') and usersInPath:
|
||||
if self._showRoles(authorized,
|
||||
callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
|
@ -11340,7 +11531,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'show roles done')
|
||||
|
||||
# show skills on the profile page
|
||||
if self.path.endswith('/skills') and '/users/' in self.path:
|
||||
if self.path.endswith('/skills') and usersInPath:
|
||||
if self._showSkills(authorized,
|
||||
callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
|
@ -11361,7 +11552,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
# get an individual post from the path
|
||||
# /users/nickname/statuses/number
|
||||
if '/statuses/' in self.path and '/users/' in self.path:
|
||||
if '/statuses/' in self.path and usersInPath:
|
||||
if self._showIndividualPost(authorized,
|
||||
callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
|
@ -11548,7 +11739,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'show shares 2 done')
|
||||
|
||||
# block a domain from htmlAccountInfo
|
||||
if authorized and '/users/' in self.path and \
|
||||
if authorized and usersInPath and \
|
||||
'/accountinfo?blockdomain=' in self.path and \
|
||||
'?handle=' in self.path:
|
||||
nickname = self.path.split('/users/')[1]
|
||||
|
@ -11584,7 +11775,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return
|
||||
|
||||
# unblock a domain from htmlAccountInfo
|
||||
if authorized and '/users/' in self.path and \
|
||||
if authorized and usersInPath and \
|
||||
'/accountinfo?unblockdomain=' in self.path and \
|
||||
'?handle=' in self.path:
|
||||
nickname = self.path.split('/users/')[1]
|
||||
|
@ -12898,8 +13089,12 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 3)
|
||||
|
||||
usersInPath = False
|
||||
if '/users/' in self.path:
|
||||
usersInPath = True
|
||||
|
||||
# moderator action buttons
|
||||
if authorized and '/users/' in self.path and \
|
||||
if authorized and usersInPath and \
|
||||
self.path.endswith('/moderationaction'):
|
||||
self._moderatorActions(self.path, callingDomain, cookie,
|
||||
self.server.baseDir,
|
||||
|
@ -13139,7 +13334,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 15)
|
||||
|
||||
if self.path.endswith('/outbox') or self.path.endswith('/shares'):
|
||||
if '/users/' in self.path:
|
||||
if usersInPath:
|
||||
if authorized:
|
||||
self.outboxAuthenticated = True
|
||||
pathUsersSection = self.path.split('/users/')[1]
|
||||
|
@ -13186,7 +13381,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
# receive images to the outbox
|
||||
if self.headers['Content-type'].startswith('image/') and \
|
||||
'/users/' in self.path:
|
||||
usersInPath:
|
||||
self._receiveImage(length, callingDomain, cookie,
|
||||
authorized, self.path,
|
||||
self.server.baseDir,
|
||||
|
@ -13362,7 +13557,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
if self.server.debug:
|
||||
print('DEBUG: POST saving to inbox queue')
|
||||
if '/users/' in self.path:
|
||||
if usersInPath:
|
||||
pathUsersSection = self.path.split('/users/')[1]
|
||||
if '/' not in pathUsersSection:
|
||||
if self.server.debug:
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
--main-bg-color-report: #221c27;
|
||||
--main-header-color-roles: #282237;
|
||||
--main-fg-color: #dddddd;
|
||||
--cw-color: #dddddd;
|
||||
--cw-style: normal;
|
||||
--cw-weight: bold;
|
||||
--column-left-fg-color: #dddddd;
|
||||
--column-right-fg-color: yellow;
|
||||
--column-right-fg-color-voted-on: red;
|
||||
|
@ -178,6 +181,12 @@ body, html {
|
|||
line-height: var(--line-spacing);
|
||||
}
|
||||
|
||||
.cw {
|
||||
font-style: var(--cw-style);
|
||||
font-weight: var(--cw-weight);
|
||||
color: var(--cw-color);
|
||||
}
|
||||
|
||||
.leftColIcons {
|
||||
width: 100%;
|
||||
background-color: var(--column-left-color);
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
--border-color: #505050;
|
||||
--font-size-header: 18px;
|
||||
--font-color-header: #ccc;
|
||||
--cw-color: #dddddd;
|
||||
--cw-style: normal;
|
||||
--font-size: 40px;
|
||||
--font-size2: 24px;
|
||||
--font-size3: 38px;
|
||||
|
@ -100,6 +102,11 @@ a:focus {
|
|||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.cw {
|
||||
font-style: var(--cw-style);
|
||||
color: var(--cw-color);
|
||||
}
|
||||
|
||||
.domainHistogramLeft {
|
||||
float: right;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
__filename__ = "mastoapiv1.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from utils import loadJson
|
||||
|
||||
|
||||
def getMastApiV1Id(path: str) -> int:
|
||||
"""Extracts the mastodon Id number from the given path
|
||||
"""
|
||||
mastoId = None
|
||||
idPath = '/api/v1/accounts/:'
|
||||
if not path.startswith(idPath):
|
||||
return None
|
||||
mastoIdStr = path.replace(idPath, '')
|
||||
if '/' in mastoIdStr:
|
||||
mastoIdStr = mastoIdStr.split('/')[0]
|
||||
if mastoIdStr.isdigit():
|
||||
mastoId = int(mastoIdStr)
|
||||
return mastoId
|
||||
return None
|
||||
|
||||
|
||||
def getMastoApiV1IdFromNickname(nickname: str) -> int:
|
||||
"""Given an account nickname return the corresponding mastodon id
|
||||
"""
|
||||
return int.from_bytes(nickname.encode('utf-8'), 'little')
|
||||
|
||||
|
||||
def _intToBytes(num: int) -> str:
|
||||
if num == 0:
|
||||
return b""
|
||||
else:
|
||||
return _intToBytes(num // 256) + bytes([num % 256])
|
||||
|
||||
|
||||
def getNicknameFromMastoApiV1Id(mastoId: int) -> str:
|
||||
"""Given the mastodon Id return the nickname
|
||||
"""
|
||||
nickname = _intToBytes(mastoId).decode()
|
||||
return nickname[::-1]
|
||||
|
||||
|
||||
def getMastoApiV1Account(baseDir: str, nickname: str, domain: str) -> {}:
|
||||
"""See https://github.com/McKael/mastodon-documentation/
|
||||
blob/master/Using-the-API/API.md#account
|
||||
Authorization has already been performed
|
||||
"""
|
||||
accountFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + '.json'
|
||||
if not os.path.isfile(accountFilename):
|
||||
return {}
|
||||
accountJson = loadJson(accountFilename)
|
||||
if not accountJson:
|
||||
return {}
|
||||
mastoAccountJson = {
|
||||
"id": getMastoApiV1IdFromNickname(nickname),
|
||||
"username": nickname,
|
||||
"acct": nickname,
|
||||
"display_name": accountJson['name'],
|
||||
"locked": accountJson['manuallyApprovesFollowers'],
|
||||
"created_at": "2016-10-05T10:30:00Z",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 0,
|
||||
"note": accountJson['summary'],
|
||||
"url": accountJson['id'],
|
||||
"avatar": accountJson['icon']['url'],
|
||||
"avatar_static": accountJson['icon']['url'],
|
||||
"header": accountJson['image']['url'],
|
||||
"header_static": accountJson['image']['url']
|
||||
}
|
||||
return mastoAccountJson
|
87
person.py
|
@ -170,38 +170,33 @@ def getDefaultPersonContext() -> str:
|
|||
"""Gets the default actor context
|
||||
"""
|
||||
return {
|
||||
'Emoji': 'toot:Emoji',
|
||||
'Hashtag': 'as:Hashtag',
|
||||
'IdentityProof': 'toot:IdentityProof',
|
||||
'PropertyValue': 'schema:PropertyValue',
|
||||
'alsoKnownAs': {
|
||||
'@id': 'as:alsoKnownAs', '@type': '@id'
|
||||
},
|
||||
'focalPoint': {
|
||||
'@container': '@list', '@id': 'toot:focalPoint'
|
||||
},
|
||||
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
|
||||
'movedTo': {
|
||||
'@id': 'as:movedTo', '@type': '@id'
|
||||
},
|
||||
'schema': 'http://schema.org#',
|
||||
'value': 'schema:value',
|
||||
'Curve25519Key': 'toot:Curve25519Key',
|
||||
'Device': 'toot:Device',
|
||||
'Ed25519Key': 'toot:Ed25519Key',
|
||||
'Ed25519Signature': 'toot:Ed25519Signature',
|
||||
'EncryptedMessage': 'toot:EncryptedMessage',
|
||||
'identityKey': {'@id': 'toot:identityKey', '@type': '@id'},
|
||||
'fingerprintKey': {'@id': 'toot:fingerprintKey', '@type': '@id'},
|
||||
'messageFranking': 'toot:messageFranking',
|
||||
'publicKeyBase64': 'toot:publicKeyBase64',
|
||||
'IdentityProof': 'toot:IdentityProof',
|
||||
'PropertyValue': 'schema:PropertyValue',
|
||||
'alsoKnownAs': {'@id': 'as:alsoKnownAs', '@type': '@id'},
|
||||
'cipherText': 'toot:cipherText',
|
||||
'claim': {'@id': 'toot:claim', '@type': '@id'},
|
||||
'deviceId': 'toot:deviceId',
|
||||
'devices': {'@id': 'toot:devices', '@type': '@id'},
|
||||
'discoverable': 'toot:discoverable',
|
||||
'orgSchema': 'toot:orgSchema',
|
||||
'shares': 'toot:shares',
|
||||
'skills': 'toot:skills',
|
||||
'roles': 'toot:roles',
|
||||
'availability': 'toot:availability',
|
||||
'nomadicLocations': 'toot:nomadicLocations'
|
||||
'featured': {'@id': 'toot:featured', '@type': '@id'},
|
||||
'featuredTags': {'@id': 'toot:featuredTags', '@type': '@id'},
|
||||
'fingerprintKey': {'@id': 'toot:fingerprintKey', '@type': '@id'},
|
||||
'focalPoint': {'@container': '@list', '@id': 'toot:focalPoint'},
|
||||
'identityKey': {'@id': 'toot:identityKey', '@type': '@id'},
|
||||
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
|
||||
'messageFranking': 'toot:messageFranking',
|
||||
'messageType': 'toot:messageType',
|
||||
'movedTo': {'@id': 'as:movedTo', '@type': '@id'},
|
||||
'publicKeyBase64': 'toot:publicKeyBase64',
|
||||
'schema': 'http://schema.org#',
|
||||
'suspended': 'toot:suspended',
|
||||
'toot': 'http://joinmastodon.org/ns#',
|
||||
'value': 'schema:value'
|
||||
}
|
||||
|
||||
|
||||
|
@ -262,17 +257,18 @@ def _createPersonBase(baseDir: str, nickname: str, domain: str, port: int,
|
|||
'https://w3id.org/security/v1',
|
||||
getDefaultPersonContext()
|
||||
],
|
||||
'attachment': [],
|
||||
'alsoKnownAs': [],
|
||||
'discoverable': False,
|
||||
'attachment': [],
|
||||
'devices': personId + '/collections/devices',
|
||||
'endpoints': {
|
||||
'id': personId+'/endpoints',
|
||||
'sharedInbox': httpPrefix+'://'+domain+'/inbox',
|
||||
'id': personId + '/endpoints',
|
||||
'sharedInbox': httpPrefix+'://' + domain + '/inbox',
|
||||
},
|
||||
'followers': personId+'/followers',
|
||||
'following': personId+'/following',
|
||||
'shares': personId+'/shares',
|
||||
'featured': personId + '/collections/featured',
|
||||
'featuredTags': personId + '/collections/tags',
|
||||
'followers': personId + '/followers',
|
||||
'following': personId + '/following',
|
||||
'shares': personId + '/shares',
|
||||
'orgSchema': None,
|
||||
'skills': {},
|
||||
'roles': {},
|
||||
|
@ -290,26 +286,19 @@ def _createPersonBase(baseDir: str, nickname: str, domain: str, port: int,
|
|||
},
|
||||
'inbox': inboxStr,
|
||||
'manuallyApprovesFollowers': approveFollowers,
|
||||
'discoverable': False,
|
||||
'discoverable': True,
|
||||
'name': personName,
|
||||
'outbox': personId+'/outbox',
|
||||
'outbox': personId + '/outbox',
|
||||
'preferredUsername': personName,
|
||||
'summary': '',
|
||||
'publicKey': {
|
||||
'id': personId+'#main-key',
|
||||
'id': personId + '#main-key',
|
||||
'owner': personId,
|
||||
'publicKeyPem': publicKeyPem
|
||||
},
|
||||
'tag': [],
|
||||
'type': personType,
|
||||
'url': personUrl,
|
||||
'nomadicLocations': [{
|
||||
'id': personId,
|
||||
'type': 'nomadicLocation',
|
||||
'locationAddress': 'acct:' + nickname + '@' + domain,
|
||||
'locationPrimary': True,
|
||||
'locationDeleted': False
|
||||
}]
|
||||
'url': personUrl
|
||||
}
|
||||
|
||||
if nickname == 'inbox':
|
||||
|
@ -551,16 +540,6 @@ def personUpgradeActor(baseDir: str, personJson: {},
|
|||
return
|
||||
if not personJson:
|
||||
personJson = loadJson(filename)
|
||||
if not personJson.get('nomadicLocations'):
|
||||
personJson['nomadicLocations'] = [{
|
||||
'id': personJson['id'],
|
||||
'type': 'nomadicLocation',
|
||||
'locationAddress':'acct:'+handle,
|
||||
'locationPrimary':True,
|
||||
'locationDeleted':False
|
||||
}]
|
||||
print('Nomadic locations added to to actor ' + handle)
|
||||
updateActor = True
|
||||
|
||||
if updateActor:
|
||||
saveJson(personJson, filename)
|
||||
|
|
14
tests.py
|
@ -92,6 +92,8 @@ from newsdaemon import hashtagRuleTree
|
|||
from newsdaemon import hashtagRuleResolve
|
||||
from newswire import getNewswireTags
|
||||
from newswire import parseFeedDate
|
||||
from mastoapiv1 import getMastoApiV1IdFromNickname
|
||||
from mastoapiv1 import getNicknameFromMastoApiV1Id
|
||||
|
||||
testServerAliceRunning = False
|
||||
testServerBobRunning = False
|
||||
|
@ -3046,9 +3048,21 @@ def testLinksWithinPost() -> None:
|
|||
assert postJsonObject['object']['content'] == content
|
||||
|
||||
|
||||
def testMastoApi():
|
||||
print('testMastoApi')
|
||||
nickname = 'ThisIsATestNickname'
|
||||
mastoId = getMastoApiV1IdFromNickname(nickname)
|
||||
assert(mastoId)
|
||||
nickname2 = getNicknameFromMastoApiV1Id(mastoId)
|
||||
if nickname2 != nickname:
|
||||
print(nickname + ' != ' + nickname2)
|
||||
assert nickname2 == nickname
|
||||
|
||||
|
||||
def runAllTests():
|
||||
print('Running tests...')
|
||||
testFunctions()
|
||||
testMastoApi()
|
||||
testLinksWithinPost()
|
||||
testReplyToPublicPost()
|
||||
testGetMentionedPeople()
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
"main-bg-color-reply": "white",
|
||||
"main-bg-color-report": "#e3dbf0",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
"cw-color": "#2d2c37",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"login-fg-color": "white",
|
||||
"options-fg-color": "lightgrey",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"main-bg-color-reply": "#030202",
|
||||
"main-bg-color-report": "#050202",
|
||||
"main-header-color-roles": "#1f192d",
|
||||
"cw-color": "#00ff00",
|
||||
"main-fg-color": "#00ff00",
|
||||
"login-fg-color": "#00ff00",
|
||||
"options-fg-color": "#00ff00",
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"title-color": "white",
|
||||
"main-visited-color": "#e1c4bc",
|
||||
"options-main-visited-color": "#e1c4bc",
|
||||
"cw-color": "white",
|
||||
"main-fg-color": "white",
|
||||
"options-fg-color": "white",
|
||||
"column-left-fg-color": "white",
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"options-main-link-color-hover": "#d09338",
|
||||
"main-visited-color": "#ffb900",
|
||||
"options-main-visited-color": "#ffb900",
|
||||
"cw-color": "white",
|
||||
"main-fg-color": "white",
|
||||
"login-fg-color": "white",
|
||||
"options-fg-color": "white",
|
||||
|
|
|
@ -99,6 +99,7 @@
|
|||
"main-bg-color-reply": "white",
|
||||
"main-bg-color-report": "white",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
"cw-color": "black",
|
||||
"main-fg-color": "black",
|
||||
"login-fg-color": "black",
|
||||
"options-fg-color": "black",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"main-bg-color-report": "#9fb42b",
|
||||
"main-bg-color-dm": "#5fb42b",
|
||||
"main-header-color-roles": "#9fb42b",
|
||||
"cw-color": "#33390d",
|
||||
"main-fg-color": "#33390d",
|
||||
"login-fg-color": "#33390d",
|
||||
"options-fg-color": "#33390d",
|
||||
|
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 980 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 992 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -1,4 +1,8 @@
|
|||
{
|
||||
"button-selected": "#999",
|
||||
"button-background": "#bbbbbb",
|
||||
"button-background-hover": "#999",
|
||||
"column-left-header-background": "#bbbbbb",
|
||||
"newswire-publish-icon": "True",
|
||||
"full-width-timeline-buttons": "False",
|
||||
"icons-as-buttons": "False",
|
||||
|
@ -32,6 +36,7 @@
|
|||
"main-bg-color-reply": "white",
|
||||
"main-bg-color-report": "#e3dbf0",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
"cw-color": "#777",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"login-fg-color": "#2d2c37",
|
||||
"options-fg-color": "#2d2c37",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"main-link-color-hover": "#d09338",
|
||||
"options-main-link-color": "#6481f5",
|
||||
"options-main-link-color-hover": "#d09338",
|
||||
"cw-color": "#0481f5",
|
||||
"main-fg-color": "#0481f5",
|
||||
"login-fg-color": "#0481f5",
|
||||
"options-fg-color": "#0481f5",
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"main-bg-color-reply": "#1a142d",
|
||||
"main-bg-color-report": "#12152d",
|
||||
"main-header-color-roles": "#1f192d",
|
||||
"cw-color": "#f98bb0",
|
||||
"main-fg-color": "#f98bb0",
|
||||
"login-fg-color": "#f98bb0",
|
||||
"options-fg-color": "#f98bb0",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"main-link-color-hover": "#46eed5",
|
||||
"options-main-link-color": "#05b9ec",
|
||||
"options-main-link-color-hover": "#46eed5",
|
||||
"cw-color": "white",
|
||||
"main-fg-color": "white",
|
||||
"login-fg-color": "white",
|
||||
"options-fg-color": "white",
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"main-bg-color-reply": "white",
|
||||
"main-bg-color-report": "white",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
"cw-color": "#2d2c37",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"login-fg-color": "#2d2c37",
|
||||
"options-fg-color": "#2d2c37",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"title-color": "#ffc4bc",
|
||||
"main-visited-color": "#e1c4bc",
|
||||
"options-main-visited-color": "#e1c4bc",
|
||||
"cw-color": "#ffc4bc",
|
||||
"main-fg-color": "#ffc4bc",
|
||||
"login-fg-color": "#ffc4bc",
|
||||
"options-fg-color": "#ffc4bc",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"dropdown-bg-color-hover": "#463b35",
|
||||
"cw-color": "#d5c7b7",
|
||||
"main-fg-color": "#d5c7b7",
|
||||
"column-left-fg-color": "#d5c7b7",
|
||||
"button-text": "#d5c7b7",
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "يرسل المنشورات إلى الحسابات التالية",
|
||||
"Word frequencies": "ترددات الكلمات",
|
||||
"New account": "حساب جديد",
|
||||
"Moved to new account address": "انتقل إلى عنوان الحساب الجديد"
|
||||
"Moved to new account address": "انتقل إلى عنوان الحساب الجديد",
|
||||
"Yet another Epicyon Instance": "مثال آخر Epicyon",
|
||||
"Other accounts": "حسابات أخرى"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Envia publicacions als comptes següents",
|
||||
"Word frequencies": "Freqüències de paraules",
|
||||
"New account": "Compte nou",
|
||||
"Moved to new account address": "S'ha mogut a l'adreça del compte nova"
|
||||
"Moved to new account address": "S'ha mogut a l'adreça del compte nova",
|
||||
"Yet another Epicyon Instance": "Encara una altra instància Epicyon",
|
||||
"Other accounts": "Altres comptes"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Yn anfon postiadau i'r cyfrifon canlynol",
|
||||
"Word frequencies": "Amleddau geiriau",
|
||||
"New account": "Cyfrif newydd",
|
||||
"Moved to new account address": "Wedi'i symud i gyfeiriad cyfrif newydd"
|
||||
"Moved to new account address": "Wedi'i symud i gyfeiriad cyfrif newydd",
|
||||
"Yet another Epicyon Instance": "Digwyddiad Epicyon arall",
|
||||
"Other accounts": "Cyfrifon eraill"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Sendet Beiträge an die folgenden Konten",
|
||||
"Word frequencies": "Worthäufigkeiten",
|
||||
"New account": "Neues Konto",
|
||||
"Moved to new account address": "An neue Kontoadresse verschoben"
|
||||
"Moved to new account address": "An neue Kontoadresse verschoben",
|
||||
"Yet another Epicyon Instance": "Noch eine Epicyon-Instanz",
|
||||
"Other accounts": "Andere Konten"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Sends out posts to the following accounts",
|
||||
"Word frequencies": "Word frequencies",
|
||||
"New account": "New account",
|
||||
"Moved to new account address": "Moved to new account address"
|
||||
"Moved to new account address": "Moved to new account address",
|
||||
"Yet another Epicyon Instance": "Yet another Epicyon Instance",
|
||||
"Other accounts": "Other accounts"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Envía publicaciones a las siguientes cuentas",
|
||||
"Word frequencies": "Frecuencias de palabras",
|
||||
"New account": "Nueva cuenta",
|
||||
"Moved to new account address": "Movido a la nueva dirección de la cuenta"
|
||||
"Moved to new account address": "Movido a la nueva dirección de la cuenta",
|
||||
"Yet another Epicyon Instance": "Otra instancia más de Epicyon",
|
||||
"Other accounts": "Otras cuentas"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Envoie des messages aux comptes suivants",
|
||||
"Word frequencies": "Fréquences des mots",
|
||||
"New account": "Nouveau compte",
|
||||
"Moved to new account address": "Déplacé vers une nouvelle adresse de compte"
|
||||
"Moved to new account address": "Déplacé vers une nouvelle adresse de compte",
|
||||
"Yet another Epicyon Instance": "Encore une autre instance Epicyon",
|
||||
"Other accounts": "Autres comptes"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Seoltar poist chuig na cuntais seo a leanas",
|
||||
"Word frequencies": "Minicíochtaí focal",
|
||||
"New account": "Cuntas nua",
|
||||
"Moved to new account address": "Ar athraíodh a ionad go seoladh cuntas nua"
|
||||
"Moved to new account address": "Ar athraíodh a ionad go seoladh cuntas nua",
|
||||
"Yet another Epicyon Instance": "Institiúid Epicyon eile fós",
|
||||
"Other accounts": "Cuntais eile"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "निम्नलिखित खातों में पोस्ट भेजता है",
|
||||
"Word frequencies": "शब्द आवृत्तियों",
|
||||
"New account": "नया खाता",
|
||||
"Moved to new account address": "नए खाते के पते पर ले जाया गया"
|
||||
"Moved to new account address": "नए खाते के पते पर ले जाया गया",
|
||||
"Yet another Epicyon Instance": "फिर भी एक और एपिकॉन उदाहरण",
|
||||
"Other accounts": "अन्य खाते"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Invia messaggi ai seguenti account",
|
||||
"Word frequencies": "Frequenze di parole",
|
||||
"New account": "Nuovo account",
|
||||
"Moved to new account address": "Spostato al nuovo indirizzo dell'account"
|
||||
"Moved to new account address": "Spostato al nuovo indirizzo dell'account",
|
||||
"Yet another Epicyon Instance": "Ancora un'altra istanza di Epicyon",
|
||||
"Other accounts": "Altri account"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "以下のアカウントに投稿を送信します",
|
||||
"Word frequencies": "単語の頻度",
|
||||
"New account": "新しいアカウント",
|
||||
"Moved to new account address": "新しいアカウントアドレスに移動しました"
|
||||
"Moved to new account address": "新しいアカウントアドレスに移動しました",
|
||||
"Yet another Epicyon Instance": "さらに別のエピキオンインスタンス",
|
||||
"Other accounts": "その他のアカウント"
|
||||
}
|
||||
|
|
|
@ -354,5 +354,7 @@
|
|||
"Sends out posts to the following accounts": "Sends out posts to the following accounts",
|
||||
"Word frequencies": "Word frequencies",
|
||||
"New account": "New account",
|
||||
"Moved to new account address": "Moved to new account address"
|
||||
"Moved to new account address": "Moved to new account address",
|
||||
"Yet another Epicyon Instance": "Yet another Epicyon Instance",
|
||||
"Other accounts": "Other accounts"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Envia postagens para as seguintes contas",
|
||||
"Word frequencies": "Frequências de palavras",
|
||||
"New account": "Nova conta",
|
||||
"Moved to new account address": "Movido para o novo endereço da conta"
|
||||
"Moved to new account address": "Movido para o novo endereço da conta",
|
||||
"Yet another Epicyon Instance": "Mais uma instância do Epicyon",
|
||||
"Other accounts": "Outras contas"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "Отправляет сообщения на следующие аккаунты",
|
||||
"Word frequencies": "Частоты слов",
|
||||
"New account": "Новый аккаунт",
|
||||
"Moved to new account address": "Перемещен на новый адрес учетной записи"
|
||||
"Moved to new account address": "Перемещен на новый адрес учетной записи",
|
||||
"Yet another Epicyon Instance": "Еще один экземпляр Эпикиона",
|
||||
"Other accounts": "Другие аккаунты"
|
||||
}
|
||||
|
|
|
@ -358,5 +358,7 @@
|
|||
"Sends out posts to the following accounts": "将帖子发送到以下帐户",
|
||||
"Word frequencies": "词频",
|
||||
"New account": "新账户",
|
||||
"Moved to new account address": "移至新帐户地址"
|
||||
"Moved to new account address": "移至新帐户地址",
|
||||
"Yet another Epicyon Instance": "另一个Epicyon实例",
|
||||
"Other accounts": "其他账户"
|
||||
}
|
||||
|
|
|
@ -48,7 +48,8 @@ def htmlPersonOptions(defaultTimeline: str,
|
|||
dormantMonths: int,
|
||||
backToPath: str,
|
||||
lockedAccount: bool,
|
||||
movedTo: str) -> str:
|
||||
movedTo: str,
|
||||
alsoKnownAs: []) -> str:
|
||||
"""Show options for a person: view/follow/block/report
|
||||
"""
|
||||
optionsDomain, optionsPort = getDomainFromActor(optionsActor)
|
||||
|
@ -143,6 +144,24 @@ def htmlPersonOptions(defaultTimeline: str,
|
|||
' <p class="optionsText">' + \
|
||||
translate['New account'] + \
|
||||
': <a href="' + movedTo + '">@' + newHandle + '</a></p>\n'
|
||||
elif alsoKnownAs:
|
||||
optionsStr += \
|
||||
' <p class="optionsText">' + \
|
||||
translate['Other accounts'] + ': '
|
||||
|
||||
if isinstance(alsoKnownAs, list):
|
||||
ctr = 0
|
||||
for altActor in alsoKnownAs:
|
||||
if ctr > 0:
|
||||
optionsStr += ' '
|
||||
ctr += 1
|
||||
altDomain, altPort = getDomainFromActor(altActor)
|
||||
optionsStr += \
|
||||
'<a href="' + altActor + '">' + altDomain + '</a>'
|
||||
elif isinstance(alsoKnownAs, str):
|
||||
altDomain, altPort = getDomainFromActor(alsoKnownAs)
|
||||
optionsStr += '<a href="' + alsoKnownAs + '">' + altDomain + '</a>'
|
||||
optionsStr += '</p>\n'
|
||||
if emailAddress:
|
||||
optionsStr += \
|
||||
'<p class="imText">' + translate['Email'] + \
|
||||
|
|
|
@ -1521,7 +1521,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
addEmojiToDisplayName(baseDir, httpPrefix,
|
||||
nickname, domain,
|
||||
cwStr, False)
|
||||
contentStr += '<b>' + cwStr + '</b>\n '
|
||||
contentStr += \
|
||||
'<label class="cw">' + cwStr + '</label>\n '
|
||||
if isModerationPost:
|
||||
containerClass = 'container report'
|
||||
# get the content warning text
|
||||
|
|
|
@ -229,6 +229,10 @@ def htmlProfileAfterSearch(cssCache: {},
|
|||
if profileJson['image'].get('url'):
|
||||
imageUrl = profileJson['image']['url']
|
||||
|
||||
alsoKnownAs = None
|
||||
if profileJson.get('alsoKnownAs'):
|
||||
alsoKnownAs = profileJson['alsoKnownAs']
|
||||
|
||||
profileStr = \
|
||||
_getProfileHeaderAfterSearch(baseDir,
|
||||
nickname, defaultTimeline,
|
||||
|
@ -238,7 +242,7 @@ def htmlProfileAfterSearch(cssCache: {},
|
|||
displayName, followsYou,
|
||||
profileDescriptionShort,
|
||||
avatarUrl, imageUrl,
|
||||
movedTo)
|
||||
movedTo, alsoKnownAs)
|
||||
|
||||
domainFull = getFullDomain(domain, port)
|
||||
|
||||
|
@ -306,7 +310,8 @@ def _getProfileHeader(baseDir: str, nickname: str, domain: str,
|
|||
avatarDescription: str,
|
||||
profileDescriptionShort: str,
|
||||
loginButton: str, avatarUrl: str,
|
||||
theme: str, movedTo: str) -> str:
|
||||
theme: str, movedTo: str,
|
||||
alsoKnownAs: []) -> str:
|
||||
"""The header of the profile screen, containing background
|
||||
image and avatar
|
||||
"""
|
||||
|
@ -335,6 +340,23 @@ def _getProfileHeader(baseDir: str, nickname: str, domain: str,
|
|||
' <p>' + translate['New account'] + ': ' + \
|
||||
'<a href="' + movedTo + '">@' + \
|
||||
newNickname + '@' + newDomainFull + '</a><br>\n'
|
||||
elif alsoKnownAs:
|
||||
htmlStr += \
|
||||
' <p>' + translate['Other accounts'] + ': '
|
||||
|
||||
if isinstance(alsoKnownAs, list):
|
||||
ctr = 0
|
||||
for altActor in alsoKnownAs:
|
||||
if ctr > 0:
|
||||
htmlStr += ' '
|
||||
ctr += 1
|
||||
altDomain, altPort = getDomainFromActor(altActor)
|
||||
htmlStr += \
|
||||
'<a href="' + altActor + '">' + altDomain + '</a>'
|
||||
elif isinstance(alsoKnownAs, str):
|
||||
altDomain, altPort = getDomainFromActor(alsoKnownAs)
|
||||
htmlStr += '<a href="' + alsoKnownAs + '">' + altDomain + '</a>'
|
||||
htmlStr += '</p>\n'
|
||||
htmlStr += \
|
||||
' <a href="/users/' + nickname + \
|
||||
'/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \
|
||||
|
@ -357,7 +379,8 @@ def _getProfileHeaderAfterSearch(baseDir: str,
|
|||
followsYou: bool,
|
||||
profileDescriptionShort: str,
|
||||
avatarUrl: str, imageUrl: str,
|
||||
movedTo: str) -> str:
|
||||
movedTo: str,
|
||||
alsoKnownAs: []) -> str:
|
||||
"""The header of a searched for handle, containing background
|
||||
image and avatar
|
||||
"""
|
||||
|
@ -388,6 +411,23 @@ def _getProfileHeaderAfterSearch(baseDir: str,
|
|||
newHandle = newNickname + '@' + newDomainFull
|
||||
htmlStr += ' <p>' + translate['New account'] + \
|
||||
': < a href="' + movedTo + '">@' + newHandle + '</a></p>\n'
|
||||
elif alsoKnownAs:
|
||||
htmlStr += \
|
||||
' <p>' + translate['Other accounts'] + ': '
|
||||
|
||||
if isinstance(alsoKnownAs, list):
|
||||
ctr = 0
|
||||
for altActor in alsoKnownAs:
|
||||
if ctr > 0:
|
||||
htmlStr += ' '
|
||||
ctr += 1
|
||||
altDomain, altPort = getDomainFromActor(altActor)
|
||||
htmlStr += \
|
||||
'<a href="' + altActor + '">' + altDomain + '</a>'
|
||||
elif isinstance(alsoKnownAs, str):
|
||||
altDomain, altPort = getDomainFromActor(alsoKnownAs)
|
||||
htmlStr += '<a href="' + alsoKnownAs + '">' + altDomain + '</a>'
|
||||
htmlStr += '</p>\n'
|
||||
|
||||
htmlStr += ' <p>' + profileDescriptionShort + '</p>\n'
|
||||
htmlStr += ' </figcaption>\n'
|
||||
|
@ -616,6 +656,10 @@ def htmlProfile(rssIconAtTop: bool,
|
|||
if profileJson.get('movedTo'):
|
||||
movedTo = profileJson['movedTo']
|
||||
|
||||
alsoKnownAs = None
|
||||
if profileJson.get('alsoKnownAs'):
|
||||
alsoKnownAs = profileJson['alsoKnownAs']
|
||||
|
||||
avatarUrl = profileJson['icon']['url']
|
||||
profileHeaderStr = \
|
||||
_getProfileHeader(baseDir, nickname, domain,
|
||||
|
@ -624,7 +668,7 @@ def htmlProfile(rssIconAtTop: bool,
|
|||
avatarDescription,
|
||||
profileDescriptionShort,
|
||||
loginButton, avatarUrl, theme,
|
||||
movedTo)
|
||||
movedTo, alsoKnownAs)
|
||||
|
||||
profileStr = profileHeaderStr + donateSection
|
||||
profileStr += '<div class="container" id="buttonheader">\n'
|
||||
|
@ -1280,6 +1324,22 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
|
|||
' <textarea id="message" name="bio" style="height:200px">' + \
|
||||
bioStr + '</textarea>\n'
|
||||
|
||||
alsoKnownAsStr = ''
|
||||
if actorJson.get('alsoKnownAs'):
|
||||
alsoKnownAs = actorJson['alsoKnownAs']
|
||||
ctr = 0
|
||||
for altActor in alsoKnownAs:
|
||||
if ctr > 0:
|
||||
alsoKnownAsStr += ', '
|
||||
ctr += 1
|
||||
alsoKnownAsStr += altActor
|
||||
|
||||
editProfileForm += '<label class="labels">' + \
|
||||
translate['Other accounts'] + ':</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="text" placeholder="https://..." ' + \
|
||||
'name="alsoKnownAs" value="' + alsoKnownAsStr + '">\n'
|
||||
|
||||
editProfileForm += '<label class="labels">' + \
|
||||
translate['Moved to new account address'] + ':</label><br>\n'
|
||||
editProfileForm += \
|
||||
|
|
|
@ -167,8 +167,8 @@ def getContentWarningButton(postID: str, translate: {},
|
|||
content: str) -> str:
|
||||
"""Returns the markup for a content warning button
|
||||
"""
|
||||
return ' <details><summary><b>' + \
|
||||
translate['SHOW MORE'] + '</b></summary>' + \
|
||||
return ' <details><summary class="cw">' + \
|
||||
translate['SHOW MORE'] + '</summary>' + \
|
||||
'<div id="' + postID + '">' + content + \
|
||||
'</div></details>\n'
|
||||
|
||||
|
|