Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main

main
Bob Mottram 2020-08-18 21:17:27 +01:00
commit 2b8d34d9e6
51 changed files with 813 additions and 273 deletions

View File

@ -202,6 +202,7 @@ def receiveAcceptReject(session, baseDir: str,
print('DEBUG: ' + messageJson['type'] + ' has no actor') print('DEBUG: ' + messageJson['type'] + ' has no actor')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:

View File

@ -81,6 +81,12 @@ def announcedByPerson(postJsonObject: {}, nickname: str, domain: str) -> bool:
# not to be confused with shared items # not to be confused with shared items
if not postJsonObject['object'].get('shares'): if not postJsonObject['object'].get('shares'):
return False return False
if not isinstance(postJsonObject['object']['shares'], dict):
return False
if not postJsonObject['object']['shares'].get('items'):
return False
if not isinstance(postJsonObject['object']['shares']['items'], list):
return False
actorMatch = domain + '/users/' + nickname actorMatch = domain + '/users/' + nickname
for item in postJsonObject['object']['shares']['items']: for item in postJsonObject['object']['shares']['items']:
if item['actor'].endswith(actorMatch): if item['actor'].endswith(actorMatch):
@ -141,6 +147,7 @@ def createAnnounce(session, baseDir: str, federationList: [],
announceDomain = None announceDomain = None
announcePort = None announcePort = None
if '/users/' in objectUrl or \ if '/users/' in objectUrl or \
'/accounts/' in objectUrl or \
'/channel/' in objectUrl or \ '/channel/' in objectUrl or \
'/profile/' in objectUrl: '/profile/' in objectUrl:
announceNickname = getNicknameFromActor(objectUrl) announceNickname = getNicknameFromActor(objectUrl)
@ -257,6 +264,7 @@ def undoAnnounce(session, baseDir: str, federationList: [],
announceDomain = None announceDomain = None
announcePort = None announcePort = None
if '/users/' in objectUrl or \ if '/users/' in objectUrl or \
'/accounts/' in objectUrl or \
'/channel/' in objectUrl or \ '/channel/' in objectUrl or \
'/profile/' in objectUrl: '/profile/' in objectUrl:
announceNickname = getNicknameFromActor(objectUrl) announceNickname = getNicknameFromActor(objectUrl)

View File

@ -57,6 +57,7 @@ def authorizeBasic(baseDir: str, path: str, authHeader: str,
'contain a space character') 'contain a space character')
return False return False
if '/users/' not in path and \ if '/users/' not in path and \
'/accounts/' not in path and \
'/channel/' not in path and \ '/channel/' not in path and \
'/profile/' not in path: '/profile/' not in path:
if debug: if debug:

View File

@ -220,6 +220,7 @@ def outboxBlock(baseDir: str, httpPrefix: str,
print('DEBUG: c2s block object is not a status') print('DEBUG: c2s block object is not a status')
return return
if '/users/' not in messageId and \ if '/users/' not in messageId and \
'/accounts/' not in messageId and \
'/channel/' not in messageId and \ '/channel/' not in messageId and \
'/profile/' not in messageId: '/profile/' not in messageId:
if debug: if debug:
@ -298,6 +299,7 @@ def outboxUndoBlock(baseDir: str, httpPrefix: str,
print('DEBUG: c2s undo block object is not a status') print('DEBUG: c2s undo block object is not a status')
return return
if '/users/' not in messageId and \ if '/users/' not in messageId and \
'/accounts/' not in messageId and \
'/channel/' not in messageId and \ '/channel/' not in messageId and \
'/profile/' not in messageId: '/profile/' not in messageId:
if debug: if debug:

View File

@ -262,6 +262,7 @@ def bookmark(recentPostsCache: {},
bookmarkedPostDomain, bookmarkedPostPort = getDomainFromActor(acBm) bookmarkedPostDomain, bookmarkedPostPort = getDomainFromActor(acBm)
else: else:
if '/users/' in objectUrl or \ if '/users/' in objectUrl or \
'/accounts/' in objectUrl or \
'/channel/' in objectUrl or \ '/channel/' in objectUrl or \
'/profile/' in objectUrl: '/profile/' in objectUrl:
ou = objectUrl ou = objectUrl
@ -362,6 +363,7 @@ def undoBookmark(recentPostsCache: {},
bookmarkedPostDomain, bookmarkedPostPort = getDomainFromActor(acBm) bookmarkedPostDomain, bookmarkedPostPort = getDomainFromActor(acBm)
else: else:
if '/users/' in objectUrl or \ if '/users/' in objectUrl or \
'/accounts/' in objectUrl or \
'/channel/' in objectUrl or \ '/channel/' in objectUrl or \
'/profile/' in objectUrl: '/profile/' in objectUrl:
ou = objectUrl ou = objectUrl

247
daemon.py
View File

@ -1245,6 +1245,99 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings, 4) self._benchmarkGETtimings(GETstartTime, GETtimings, 4)
# manifest for progressive web apps
if '/manifest.json' in self.path:
app1 = "https://f-droid.org/en/packages/eu.siacs.conversations"
app2 = "https://staging.f-droid.org/en/packages/im.vector.app"
manifest = {
"name": "Epicyon",
"short_name": "Epicyon",
"start_url": "/index.html",
"display": "standalone",
"background_color": "black",
"theme_color": "grey",
"orientation": "portrait-primary",
"categories": ["microblog", "fediverse", "activitypub"],
"screenshots": [
{
"src": "/mobile.jpg",
"sizes": "418x851",
"type": "image/jpeg"
},
{
"src": "/mobile_person.jpg",
"sizes": "429x860",
"type": "image/jpeg"
},
{
"src": "/mobile_search.jpg",
"sizes": "422x861",
"type": "image/jpeg"
}
],
"icons": [
{
"src": "/logo72.png",
"type": "image/png",
"sizes": "72x72"
},
{
"src": "/logo96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "/logo128.png",
"type": "image/png",
"sizes": "128x128"
},
{
"src": "/logo144.png",
"type": "image/png",
"sizes": "144x144"
},
{
"src": "/logo152.png",
"type": "image/png",
"sizes": "152x152"
},
{
"src": "/logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/logo256.png",
"type": "image/png",
"sizes": "256x256"
},
{
"src": "/logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"related_applications": [
{
"platform": "fdroid",
"url": app1
},
{
"platform": "fdroid",
"url": app2
}
]
}
msg = json.dumps(manifest,
ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',
len(msg),
None, callingDomain)
self._write(msg)
if self.server.debug:
print('Sent manifest: ' + callingDomain)
return
# favicon image # favicon image
if 'favicon.ico' in self.path: if 'favicon.ico' in self.path:
favType = 'image/x-icon' favType = 'image/x-icon'
@ -1262,6 +1355,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.baseDir + '/img/icons/' + favFilename self.server.baseDir + '/img/icons/' + favFilename
if self._etag_exists(faviconFilename): if self._etag_exists(faviconFilename):
# The file has not changed # The file has not changed
if self.server.debug:
print('favicon icon has not changed: ' + callingDomain)
self._304() self._304()
return return
if self.server.iconsCache.get(favFilename): if self.server.iconsCache.get(favFilename):
@ -1271,6 +1366,8 @@ class PubServer(BaseHTTPRequestHandler):
favBinary, cookie, favBinary, cookie,
callingDomain) callingDomain)
self._write(favBinary) self._write(favBinary)
if self.server.debug:
print('Sent favicon from cache: ' + callingDomain)
return return
else: else:
if os.path.isfile(faviconFilename): if os.path.isfile(faviconFilename):
@ -1282,7 +1379,11 @@ class PubServer(BaseHTTPRequestHandler):
callingDomain) callingDomain)
self._write(favBinary) self._write(favBinary)
self.server.iconsCache[favFilename] = favBinary self.server.iconsCache[favFilename] = favBinary
if self.server.debug:
print('Sent favicon from file: ' + callingDomain)
return return
if self.server.debug:
print('favicon not sent: ' + callingDomain)
self._404() self._404()
return return
@ -1355,6 +1456,9 @@ class PubServer(BaseHTTPRequestHandler):
fontBinary, cookie, fontBinary, cookie,
callingDomain) callingDomain)
self._write(fontBinary) self._write(fontBinary)
if self.server.debug:
print('font sent from cache: ' +
self.path + ' ' + callingDomain)
return return
else: else:
if os.path.isfile(fontFilename): if os.path.isfile(fontFilename):
@ -1366,7 +1470,12 @@ class PubServer(BaseHTTPRequestHandler):
callingDomain) callingDomain)
self._write(fontBinary) self._write(fontBinary)
self.server.fontsCache[fontStr] = fontBinary self.server.fontsCache[fontStr] = fontBinary
if self.server.debug:
print('font sent from file: ' +
self.path + ' ' + callingDomain)
return return
if self.server.debug:
print('font not found: ' + self.path + ' ' + callingDomain)
self._404() self._404()
return return
@ -1421,9 +1530,15 @@ class PubServer(BaseHTTPRequestHandler):
self._set_headers('text/xml', len(msg), self._set_headers('text/xml', len(msg),
cookie, callingDomain) cookie, callingDomain)
self._write(msg) self._write(msg)
if self.server.debug:
print('Sent rss2 feed: ' +
self.path + ' ' + callingDomain)
return return
self._404() if self.server.debug:
return print('Failed to get rss2 feed: ' +
self.path + ' ' + callingDomain)
self._404()
return
# RSS 3.0 # RSS 3.0
if self.path.startswith('/blog/') and \ if self.path.startswith('/blog/') and \
@ -1459,9 +1574,15 @@ class PubServer(BaseHTTPRequestHandler):
self._set_headers('text/plain; charset=utf-8', self._set_headers('text/plain; charset=utf-8',
len(msg), cookie, callingDomain) len(msg), cookie, callingDomain)
self._write(msg) self._write(msg)
if self.server.debug:
print('Sent rss3 feed: ' +
self.path + ' ' + callingDomain)
return return
self._404() if self.server.debug:
return print('Failed to get rss3 feed: ' +
self.path + ' ' + callingDomain)
self._404()
return
# show the main blog page # show the main blog page
if htmlGET and (self.path == '/blog' or if htmlGET and (self.path == '/blog' or
@ -1830,15 +1951,19 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings, 15) self._benchmarkGETtimings(GETstartTime, GETtimings, 15)
# image on login screen or qrcode # manifest images used to create a home screen icon
if self.path == '/login.png' or \ # when selecting "add to home screen" in browsers
self.path == '/login.gif' or \ # which support progressive web apps
self.path == '/login.webp' or \ if self.path == '/logo72.png' or \
self.path == '/login.jpeg' or \ self.path == '/logo96.png' or \
self.path == '/login.jpg' or \ self.path == '/logo128.png' or \
self.path == '/qrcode.png': self.path == '/logo144.png' or \
self.path == '/logo152.png' or \
self.path == '/logo192.png' or \
self.path == '/logo256.png' or \
self.path == '/logo512.png':
mediaFilename = \ mediaFilename = \
self.server.baseDir + '/accounts' + self.path self.server.baseDir + '/img' + self.path
if os.path.isfile(mediaFilename): if os.path.isfile(mediaFilename):
if self._etag_exists(mediaFilename): if self._etag_exists(mediaFilename):
# The file has not changed # The file has not changed
@ -1866,6 +1991,75 @@ class PubServer(BaseHTTPRequestHandler):
self._404() self._404()
return return
# manifest images used to show example screenshots
# for use by app stores
if self.path == '/screenshot1.jpg' or \
self.path == '/screenshot2.jpg':
screenFilename = \
self.server.baseDir + '/img' + self.path
if os.path.isfile(screenFilename):
if self._etag_exists(screenFilename):
# The file has not changed
self._304()
return
tries = 0
mediaBinary = None
while tries < 5:
try:
with open(screenFilename, 'rb') as avFile:
mediaBinary = avFile.read()
break
except Exception as e:
print(e)
time.sleep(1)
tries += 1
if mediaBinary:
self._set_headers_etag(screenFilename,
'image/png',
mediaBinary, cookie,
callingDomain)
self._write(mediaBinary)
return
self._404()
return
# image on login screen or qrcode
if self.path == '/login.png' or \
self.path == '/login.gif' or \
self.path == '/login.webp' or \
self.path == '/login.jpeg' or \
self.path == '/login.jpg' or \
self.path == '/qrcode.png':
iconFilename = \
self.server.baseDir + '/accounts' + self.path
if os.path.isfile(iconFilename):
if self._etag_exists(iconFilename):
# The file has not changed
self._304()
return
tries = 0
mediaBinary = None
while tries < 5:
try:
with open(iconFilename, 'rb') as avFile:
mediaBinary = avFile.read()
break
except Exception as e:
print(e)
time.sleep(1)
tries += 1
if mediaBinary:
self._set_headers_etag(iconFilename,
'image/png',
mediaBinary, cookie,
callingDomain)
self._write(mediaBinary)
return
self._404()
return
self._benchmarkGETtimings(GETstartTime, GETtimings, 16) self._benchmarkGETtimings(GETstartTime, GETtimings, 16)
# QR code for account handle # QR code for account handle
@ -1875,11 +2069,11 @@ class PubServer(BaseHTTPRequestHandler):
savePersonQrcode(self.server.baseDir, savePersonQrcode(self.server.baseDir,
nickname, self.server.domain, nickname, self.server.domain,
self.server.port) self.server.port)
mediaFilename = \ qrFilename = \
self.server.baseDir + '/accounts/' + \ self.server.baseDir + '/accounts/' + \
nickname + '@' + self.server.domain + '/qrcode.png' nickname + '@' + self.server.domain + '/qrcode.png'
if os.path.isfile(mediaFilename): if os.path.isfile(qrFilename):
if self._etag_exists(mediaFilename): if self._etag_exists(qrFilename):
# The file has not changed # The file has not changed
self._304() self._304()
return return
@ -1888,7 +2082,7 @@ class PubServer(BaseHTTPRequestHandler):
mediaBinary = None mediaBinary = None
while tries < 5: while tries < 5:
try: try:
with open(mediaFilename, 'rb') as avFile: with open(qrFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary = avFile.read()
break break
except Exception as e: except Exception as e:
@ -1896,7 +2090,7 @@ class PubServer(BaseHTTPRequestHandler):
time.sleep(1) time.sleep(1)
tries += 1 tries += 1
if mediaBinary: if mediaBinary:
self._set_headers_etag(mediaFilename, 'image/png', self._set_headers_etag(qrFilename, 'image/png',
mediaBinary, cookie, mediaBinary, cookie,
callingDomain) callingDomain)
self._write(mediaBinary) self._write(mediaBinary)
@ -1908,11 +2102,11 @@ class PubServer(BaseHTTPRequestHandler):
if '/users/' in self.path and \ if '/users/' in self.path and \
self.path.endswith('/search_banner.png'): self.path.endswith('/search_banner.png'):
nickname = getNicknameFromActor(self.path) nickname = getNicknameFromActor(self.path)
mediaFilename = \ bannerFilename = \
self.server.baseDir + '/accounts/' + \ self.server.baseDir + '/accounts/' + \
nickname + '@' + self.server.domain + '/search_banner.png' nickname + '@' + self.server.domain + '/search_banner.png'
if os.path.isfile(mediaFilename): if os.path.isfile(bannerFilename):
if self._etag_exists(mediaFilename): if self._etag_exists(bannerFilename):
# The file has not changed # The file has not changed
self._304() self._304()
return return
@ -1921,7 +2115,7 @@ class PubServer(BaseHTTPRequestHandler):
mediaBinary = None mediaBinary = None
while tries < 5: while tries < 5:
try: try:
with open(mediaFilename, 'rb') as avFile: with open(bannerFilename, 'rb') as avFile:
mediaBinary = avFile.read() mediaBinary = avFile.read()
break break
except Exception as e: except Exception as e:
@ -1929,7 +2123,7 @@ class PubServer(BaseHTTPRequestHandler):
time.sleep(1) time.sleep(1)
tries += 1 tries += 1
if mediaBinary: if mediaBinary:
self._set_headers_etag(mediaFilename, 'image/png', self._set_headers_etag(bannerFilename, 'image/png',
mediaBinary, cookie, mediaBinary, cookie,
callingDomain) callingDomain)
self._write(mediaBinary) self._write(mediaBinary)
@ -5914,7 +6108,7 @@ class PubServer(BaseHTTPRequestHandler):
return return
self._400() self._400()
elif path.startswith('/api/v1/crypto/keys/query'): elif path.startswith('/api/v1/crypto/keys/query'):
# given a handle (nickname@domain) return the devices # given a handle (nickname@domain) return a list of the devices
# registered to that handle # registered to that handle
if not self._cryptoAPIQuery(): if not self._cryptoAPIQuery():
self._400() self._400()
@ -7232,7 +7426,12 @@ class PubServer(BaseHTTPRequestHandler):
self._write(msg) self._write(msg)
self.server.POSTbusy = False self.server.POSTbusy = False
return return
elif '@' in searchStr: elif ('@' in searchStr or
('://' in searchStr and
('/users/' in searchStr or
'/profile/' in searchStr or
'/accounts/' in searchStr or
'/channel/' in searchStr))):
# profile search # profile search
nickname = getNicknameFromActor(actorStr) nickname = getNicknameFromActor(actorStr)
if not self.server.session: if not self.server.session:

View File

@ -67,6 +67,7 @@ def createDelete(session, baseDir: str, federationList: [],
deleteDomain = None deleteDomain = None
deletePort = None deletePort = None
if '/users/' in objectUrl or \ if '/users/' in objectUrl or \
'/accounts/' in objectUrl or \
'/channel/' in objectUrl or \ '/channel/' in objectUrl or \
'/profile/' in objectUrl: '/profile/' in objectUrl:
deleteNickname = getNicknameFromActor(objectUrl) deleteNickname = getNicknameFromActor(objectUrl)
@ -262,6 +263,7 @@ def outboxDelete(baseDir: str, httpPrefix: str,
print('DEBUG: c2s delete object is not a status') print('DEBUG: c2s delete object is not a status')
return return
if '/users/' not in messageId and \ if '/users/' not in messageId and \
'/accounts/' not in messageId and \
'/channel/' not in messageId and \ '/channel/' not in messageId and \
'/profile/' not in messageId: '/profile/' not in messageId:
if debug: if debug:

View File

@ -42,6 +42,7 @@
--gallery-font-size-mobile: 35px; --gallery-font-size-mobile: 35px;
--button-corner-radius: 15px; --button-corner-radius: 15px;
--timeline-border-radius: 30px; --timeline-border-radius: 30px;
--focus-color: white;
} }
@font-face { @font-face {
@ -80,6 +81,10 @@ a:link {
font-weight: bold; font-weight: bold;
} }
a:focus {
border: 2px solid var(--focus-color);
}
.cwText { .cwText {
display: none; display: none;
} }

View File

@ -13,6 +13,7 @@
--event-foreground:white; --event-foreground:white;
--title-text: #282c37; --title-text: #282c37;
--title-background: #ccc; --title-background: #ccc;
--focus-color: white;
} }
@font-face { @font-face {
@ -67,6 +68,10 @@ a:link {
margin: -1rem; margin: -1rem;
} }
a:focus {
border: 2px solid var(--focus-color);
}
.calendar__day__header, .calendar__day__header,
.calendar__day__cell { .calendar__day__cell {
border: 2px solid var(--lines-color); border: 2px solid var(--lines-color);

View File

@ -30,6 +30,7 @@
--follow-text-size1: 24px; --follow-text-size1: 24px;
--follow-text-size2: 40px; --follow-text-size2: 40px;
--follow-text-entry-width: 90%; --follow-text-entry-width: 90%;
--focus-color: white;
} }
@font-face { @font-face {
@ -67,6 +68,10 @@ a:link {
font-weight: bold; font-weight: bold;
} }
a:focus {
border: 2px solid var(--focus-color);
}
.searchBanner { .searchBanner {
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("search_banner.png"); background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("search_banner.png");
background-position: center; background-position: center;

View File

@ -19,6 +19,7 @@
--button-background: #999; --button-background: #999;
--button-selected: #666; --button-selected: #666;
--form-border-radius: 30px; --form-border-radius: 30px;
--focus-color: white;
} }
@font-face { @font-face {
@ -63,6 +64,10 @@ a:link {
font-weight: bold; font-weight: bold;
} }
a:focus {
border: 2px solid var(--focus-color);
}
form { form {
border: var(--border-width) solid var(--border-color); border: var(--border-width) solid var(--border-color);
border-radius: var(--form-border-radius); border-radius: var(--form-border-radius);

View File

@ -18,7 +18,9 @@
--text-entry-background: #111; --text-entry-background: #111;
--time-color: #aaa; --time-color: #aaa;
--button-text: #FFFFFF; --button-text: #FFFFFF;
--button-small-text: #FFFFFF;
--button-background: #999; --button-background: #999;
--button-small-background: #999;
--button-selected: #666; --button-selected: #666;
--hashtag-margin: 2%; --hashtag-margin: 2%;
--hashtag-vertical-spacing1: 50px; --hashtag-vertical-spacing1: 50px;
@ -30,6 +32,7 @@
--follow-text-size1: 24px; --follow-text-size1: 24px;
--follow-text-size2: 40px; --follow-text-size2: 40px;
--follow-text-entry-width: 90%; --follow-text-entry-width: 90%;
--focus-color: white;
} }
@font-face { @font-face {
@ -72,6 +75,10 @@ a:link {
font-weight: bold; font-weight: bold;
} }
a:focus {
border: 2px solid var(--focus-color);
}
.follow { .follow {
height: 100%; height: 100%;
position: relative; position: relative;
@ -112,13 +119,14 @@ a:link {
width: 15%; width: 15%;
} }
textarea {
font-size: var(--font-size4);
width: 90%;
background-color: var(--text-entry-background);
}
@media screen and (min-width: 400px) { @media screen and (min-width: 400px) {
textarea {
font-family: Arial, Helvetica, sans-serif;
font-size: var(--font-size4);
width: 90%;
background-color: var(--text-entry-background);
color: white;
}
.followText { .followText {
font-size: var(--follow-text-size1); font-size: var(--follow-text-size1);
} }
@ -140,7 +148,22 @@ textarea {
text-align: center; text-align: center;
padding: 10px; padding: 10px;
font-size: 24px; font-size: 24px;
width: 20%; width: 10ch;
max-width: 200px;
min-width: 100px;
cursor: pointer;
margin: 30px;
}
.buttonsmall {
border-radius: 4px;
background-color: var(--button-small-background);
font-family: Arial, Helvetica, sans-serif;
border: none;
color: var(--button-small-text);
text-align: center;
padding: 10px;
font-size: 24px;
width: 7ch;
max-width: 200px; max-width: 200px;
min-width: 100px; min-width: 100px;
cursor: pointer; cursor: pointer;
@ -159,6 +182,13 @@ textarea {
} }
@media screen and (max-width: 1000px) { @media screen and (max-width: 1000px) {
textarea {
font-family: Arial, Helvetica, sans-serif;
font-size: var(--font-size);
width: 90%;
background-color: var(--text-entry-background);
color: white;
}
.followText { .followText {
font-size: var(--follow-text-size2); font-size: var(--follow-text-size2);
} }
@ -180,7 +210,22 @@ textarea {
text-align: center; text-align: center;
padding: 10px; padding: 10px;
font-size: 40px; font-size: 40px;
width: 20%; width: 10ch;
max-width: 200px;
min-width: 100px;
cursor: pointer;
margin: 30px;
}
.buttonsmall {
border-radius: 4px;
background-color: var(--button-small-background);
font-family: Arial, Helvetica, sans-serif;
border: none;
color: var(--button-small-text);
text-align: center;
padding: 10px;
font-size: 40px;
width: 7ch;
max-width: 200px; max-width: 200px;
min-width: 100px; min-width: 100px;
cursor: pointer; cursor: pointer;

View File

@ -26,6 +26,9 @@
--font-size4: 22px; --font-size4: 22px;
--font-size5: 20px; --font-size5: 20px;
--font-size-pgp-key: 16px; --font-size-pgp-key: 16px;
--font-size-pgp-key2: 8px;
--font-size-tox: 16px;
--font-size-tox2: 8px;
--text-entry-foreground: #ccc; --text-entry-foreground: #ccc;
--text-entry-background: #111; --text-entry-background: #111;
--time-color: #aaa; --time-color: #aaa;
@ -51,6 +54,7 @@
--timeline-border-radius: 30px; --timeline-border-radius: 30px;
--icons-side: right; --icons-side: right;
--title-color: #999; --title-color: #999;
--focus-color: white;
} }
@font-face { @font-face {
@ -73,6 +77,10 @@ body, html {
font-size: var(--font-size); font-size: var(--font-size);
} }
.imageAnchor:focus img{
border: 2px solid var(--focus-color);
}
h1 { h1 {
color: var(--title-color); color: var(--title-color);
} }
@ -93,6 +101,10 @@ a:link {
font-weight: bold; font-weight: bold;
} }
a:focus {
border: 2px solid var(--focus-color);
}
.timeline-banner { .timeline-banner {
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png"); background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png");
height: 10%; height: 10%;
@ -116,11 +128,6 @@ a:link {
float: right; float: right;
} }
.ssbaddr {
font-size: var(--font-size-pgp-key);
font-family: Arial, Helvetica, sans-serif;
}
.about { .about {
font-size: var(--font-size5); font-size: var(--font-size5);
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
@ -285,16 +292,19 @@ a:link {
width: 90%; width: 90%;
} }
.message:focus{
border: 2px solid var(--focus-color);
}
.message:focus img{
border: 2px solid var(--focus-color);
}
.gitpatch { .gitpatch {
width: 90%; width: 90%;
font-family: 'monospace'; font-family: 'monospace';
} }
.container p.administeredby {
font-size: var(--font-size-header);
font-family: Arial, Helvetica, sans-serif;
}
.container::after { .container::after {
content: ""; content: "";
clear: both; clear: both;
@ -346,13 +356,6 @@ a:link {
background: var(--link-bg-color); background: var(--link-bg-color);
} }
.pgp {
font-size: var(--font-size-pgp-key);
color: var(--main-link-color);
background: var(--link-bg-color);
font-family: 'monospace';
}
.container img.announceOrReply { .container img.announceOrReply {
float: none; float: none;
width: 30px; width: 30px;
@ -857,6 +860,24 @@ aside .toggle-inside li {
} }
@media screen and (min-width: 400px) { @media screen and (min-width: 400px) {
.container p.administeredby {
font-size: var(--font-size-header);
font-family: Arial, Helvetica, sans-serif;
}
.toxaddr {
font-size: var(--font-size-tox);
font-family: Arial, Helvetica, sans-serif;
}
.ssbaddr {
font-size: var(--font-size-pgp-key);
font-family: Arial, Helvetica, sans-serif;
}
.pgp {
font-size: var(--font-size-pgp-key);
color: var(--main-link-color);
background: var(--link-bg-color);
font-family: 'monospace';
}
body, html { body, html {
font-size: var(--font-size4); font-size: var(--font-size4);
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
@ -1260,6 +1281,24 @@ aside .toggle-inside li {
} }
@media screen and (max-width: 1000px) { @media screen and (max-width: 1000px) {
.container p.administeredby {
font-size: var(--font-size-tox2);
font-family: Arial, Helvetica, sans-serif;
}
.toxaddr {
font-size: var(--font-size-tox2);
font-family: Arial, Helvetica, sans-serif;
}
.ssbaddr {
font-size: var(--font-size-pgp-key2);
font-family: Arial, Helvetica, sans-serif;
}
.pgp {
font-size: var(--font-size-pgp-key2);
color: var(--main-link-color);
background: var(--link-bg-color);
font-family: 'monospace';
}
body, html { body, html {
font-size: var(--font-size3); font-size: var(--font-size3);
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;

View File

@ -30,6 +30,7 @@
--follow-text-size1: 24px; --follow-text-size1: 24px;
--follow-text-size2: 40px; --follow-text-size2: 40px;
--follow-text-entry-width: 90%; --follow-text-entry-width: 90%;
--focus-color: white;
} }
@font-face { @font-face {
@ -67,6 +68,10 @@ a:link {
font-weight: bold; font-weight: bold;
} }
a:focus {
border: 2px solid var(--focus-color);
}
.searchBanner { .searchBanner {
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("search_banner.png"); background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("search_banner.png");
background-position: center; background-position: center;

View File

@ -19,6 +19,7 @@
--button-text: #FFFFFF; --button-text: #FFFFFF;
--button-background: #999; --button-background: #999;
--button-selected: #666; --button-selected: #666;
--focus-color: white;
} }
@font-face { @font-face {
@ -57,6 +58,10 @@ a:link {
font-weight: bold; font-weight: bold;
} }
a:focus {
border: 2px solid var(--focus-color);
}
.screentitle { .screentitle {
font-size: 30px; font-size: 30px;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;

View File

@ -1130,6 +1130,7 @@ if args.actor:
args.actor = args.actor.replace(prefix, '') args.actor = args.actor.replace(prefix, '')
args.actor = args.actor.replace('/@', '/users/') args.actor = args.actor.replace('/@', '/users/')
if '/users/' not in args.actor and \ if '/users/' not in args.actor and \
'/accounts/' not in args.actor and \
'/channel/' not in args.actor and \ '/channel/' not in args.actor and \
'/profile/' not in args.actor: '/profile/' not in args.actor:
print('Expected actor format: ' + print('Expected actor format: ' +
@ -1143,10 +1144,14 @@ if args.actor:
nickname = args.actor.split('/profile/')[1] nickname = args.actor.split('/profile/')[1]
nickname = nickname.replace('\n', '').replace('\r', '') nickname = nickname.replace('\n', '').replace('\r', '')
domain = args.actor.split('/profile/')[0] domain = args.actor.split('/profile/')[0]
else: elif '/channel/' in args.actor:
nickname = args.actor.split('/channel/')[1] nickname = args.actor.split('/channel/')[1]
nickname = nickname.replace('\n', '').replace('\r', '') nickname = nickname.replace('\n', '').replace('\r', '')
domain = args.actor.split('/channel/')[0] domain = args.actor.split('/channel/')[0]
elif '/accounts/' in args.actor:
nickname = args.actor.split('/accounts/')[1]
nickname = nickname.replace('\n', '').replace('\r', '')
domain = args.actor.split('/accounts/')[0]
else: else:
# format: @nick@domain # format: @nick@domain
if '@' not in args.actor: if '@' not in args.actor:
@ -1198,6 +1203,7 @@ if args.actor:
if wfRequest.get('errors'): if wfRequest.get('errors'):
print('wfRequest error: ' + str(wfRequest['errors'])) print('wfRequest error: ' + str(wfRequest['errors']))
if '/users/' in args.actor or \ if '/users/' in args.actor or \
'/accounts/' in args.actor or \
'/profile/' in args.actor or \ '/profile/' in args.actor or \
'/channel/' in args.actor: '/channel/' in args.actor:
personUrl = originalActor personUrl = originalActor
@ -1212,6 +1218,7 @@ if args.actor:
personUrl = getUserUrl(wfRequest) personUrl = getUserUrl(wfRequest)
if nickname == domain: if nickname == domain:
personUrl = personUrl.replace('/users/', '/actor/') personUrl = personUrl.replace('/users/', '/actor/')
personUrl = personUrl.replace('/accounts/', '/actor/')
personUrl = personUrl.replace('/channel/', '/actor/') personUrl = personUrl.replace('/channel/', '/actor/')
personUrl = personUrl.replace('/profile/', '/actor/') personUrl = personUrl.replace('/profile/', '/actor/')
if not personUrl: if not personUrl:
@ -1221,7 +1228,7 @@ if args.actor:
asHeader = { asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"' 'Accept': 'application/ld+json; profile="' + profileStr + '"'
} }
if '/channel/' in personUrl: if '/channel/' in personUrl or '/accounts/' in personUrl:
profileStr = 'https://www.w3.org/ns/activitystreams' profileStr = 'https://www.w3.org/ns/activitystreams'
asHeader = { asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"' 'Accept': 'application/ld+json; profile="' + profileStr + '"'

View File

@ -555,6 +555,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
print('DEBUG: follow request has no actor') print('DEBUG: follow request has no actor')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -582,6 +583,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
if not messageJson.get('to'): if not messageJson.get('to'):
messageJson['to'] = messageJson['object'] messageJson['to'] = messageJson['object']
if '/users/' not in messageJson['object'] and \ if '/users/' not in messageJson['object'] and \
'/accounts/' not in messageJson['object'] and \
'/channel/' not in messageJson['object'] and \ '/channel/' not in messageJson['object'] and \
'/profile/' not in messageJson['object']: '/profile/' not in messageJson['object']:
if debug: if debug:

View File

@ -7,14 +7,142 @@ __email__ = "bob@freedombone.net"
__status__ = "Production" __status__ = "Production"
import os import os
from uuid import UUID
from datetime import datetime from datetime import datetime
from utils import loadJson from utils import loadJson
from utils import saveJson
from utils import locatePost from utils import locatePost
from utils import daysInMonth from utils import daysInMonth
from utils import mergeDicts from utils import mergeDicts
def validUuid(testUuid: str, version=4):
"""Check if uuid_to_test is a valid UUID
"""
try:
uuid_obj = UUID(testUuid, version=version)
except ValueError:
return False
return str(uuid_obj) == testUuid
def removeEventFromTimeline(eventId: str, tlEventsFilename: str) -> None:
"""Removes the given event Id from the timeline
"""
if eventId + '\n' not in open(tlEventsFilename).read():
return
with open(tlEventsFilename, 'r') as fp:
eventsTimeline = fp.read().replace(eventId + '\n', '')
try:
with open(tlEventsFilename, 'w+') as fp2:
fp2.write(eventsTimeline)
except BaseException:
print('ERROR: unable to save events timeline')
pass
def saveEvent(baseDir: str, handle: str, postId: str,
eventJson: {}) -> bool:
"""Saves an event to the calendar and/or the events timeline
If an event has extra fields, as per Mobilizon,
Then it is saved as a separate entity and added to the
events timeline
"""
calendarPath = baseDir + '/accounts/' + handle + '/calendar'
if not os.path.isdir(calendarPath):
os.mkdir(calendarPath)
# get the year, month and day from the event
eventTime = datetime.strptime(eventJson['startTime'],
"%Y-%m-%dT%H:%M:%S%z")
eventYear = int(eventTime.strftime("%Y"))
if eventYear < 2020 or eventYear >= 2100:
return False
eventMonthNumber = int(eventTime.strftime("%m"))
if eventMonthNumber < 1 or eventMonthNumber > 12:
return False
eventDayOfMonth = int(eventTime.strftime("%d"))
if eventDayOfMonth < 1 or eventDayOfMonth > 31:
return False
if eventJson.get('name') and eventJson.get('actor') and \
eventJson.get('uuid') and eventJson.get('content'):
if not validUuid(eventJson['uuid']):
return False
# if this is a full description of an event then save it
# as a separate json file
eventsPath = baseDir + '/accounts/' + handle + '/events'
if not os.path.isdir(eventsPath):
os.mkdir(eventsPath)
eventsYearPath = \
baseDir + '/accounts/' + handle + '/events/' + str(eventYear)
if not os.path.isdir(eventsYearPath):
os.mkdir(eventsYearPath)
eventId = str(eventYear) + '-' + eventTime.strftime("%m") + '-' + \
eventTime.strftime("%d") + '_' + eventJson['uuid']
eventFilename = eventsYearPath + '/' + eventId + '.json'
saveJson(eventJson, eventFilename)
# save to the events timeline
tlEventsFilename = baseDir + '/accounts/' + handle + '/events.txt'
if os.path.isfile(tlEventsFilename):
removeEventFromTimeline(eventId, tlEventsFilename)
try:
with open(tlEventsFilename, 'r+') as tlEventsFile:
content = tlEventsFile.read()
tlEventsFile.seek(0, 0)
tlEventsFile.write(eventId + '\n' + content)
except Exception as e:
print('WARN: Failed to write entry to events file ' +
tlEventsFilename + ' ' + str(e))
return False
else:
tlEventsFile = open(tlEventsFilename, 'w+')
tlEventsFile.write(eventId + '\n')
tlEventsFile.close()
# create a directory for the calendar year
if not os.path.isdir(calendarPath + '/' + str(eventYear)):
os.mkdir(calendarPath + '/' + str(eventYear))
# calendar month file containing event post Ids
calendarFilename = calendarPath + '/' + str(eventYear) + \
'/' + str(eventMonthNumber) + '.txt'
# Does this event post already exist within the calendar month?
if os.path.isfile(calendarFilename):
if postId in open(calendarFilename).read():
# Event post already exists
return False
# append the post Id to the file for the calendar month
calendarFile = open(calendarFilename, 'a+')
if not calendarFile:
return False
calendarFile.write(postId + '\n')
calendarFile.close()
# create a file which will trigger a notification that
# a new event has been added
calendarNotificationFilename = \
baseDir + '/accounts/' + handle + '/.newCalendar'
calendarNotificationFile = \
open(calendarNotificationFilename, 'w+')
if not calendarNotificationFile:
return False
calendarNotificationFile.write('/calendar?year=' +
str(eventYear) +
'?month=' +
str(eventMonthNumber) +
'?day=' +
str(eventDayOfMonth))
calendarNotificationFile.close()
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?
""" """

BIN
img/logo128.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
img/logo144.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
img/logo152.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/logo192.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
img/logo256.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
img/logo512.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
img/logo72.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
img/logo96.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -64,6 +64,7 @@ from git import isGitPatch
from git import receiveGitPatch from git import receiveGitPatch
from followingCalendar import receivingCalendarEvents from followingCalendar import receivingCalendarEvents
from content import dangerousMarkup from content import dangerousMarkup
from happening import saveEvent
def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None: def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
@ -660,6 +661,7 @@ def receiveUndoFollow(session, baseDir: str, httpPrefix: str,
print('DEBUG: follow request has no actor within object') print('DEBUG: follow request has no actor within object')
return False return False
if '/users/' not in messageJson['object']['actor'] and \ if '/users/' not in messageJson['object']['actor'] and \
'/accounts/' not in messageJson['object']['actor'] and \
'/channel/' not in messageJson['object']['actor'] and \ '/channel/' not in messageJson['object']['actor'] and \
'/profile/' not in messageJson['object']['actor']: '/profile/' not in messageJson['object']['actor']:
if debug: if debug:
@ -734,6 +736,7 @@ def receiveUndo(session, baseDir: str, httpPrefix: str,
print('DEBUG: follow request has no actor') print('DEBUG: follow request has no actor')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -791,11 +794,13 @@ def personReceiveUpdate(baseDir: str,
if actor not in personJson['id']: if actor not in personJson['id']:
actor = updateDomainFull + '/channel/' + updateNickname actor = updateDomainFull + '/channel/' + updateNickname
if actor not in personJson['id']: if actor not in personJson['id']:
if debug: actor = updateDomainFull + '/accounts/' + updateNickname
print('actor: ' + actor) if actor not in personJson['id']:
print('id: ' + personJson['id']) if debug:
print('DEBUG: Actor does not match id') print('actor: ' + actor)
return False print('id: ' + personJson['id'])
print('DEBUG: Actor does not match id')
return False
if updateDomainFull == domainFull: if updateDomainFull == domainFull:
if debug: if debug:
print('DEBUG: You can only receive actor updates ' + print('DEBUG: You can only receive actor updates ' +
@ -906,6 +911,7 @@ def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
print('DEBUG: ' + messageJson['type'] + ' object has no type') print('DEBUG: ' + messageJson['type'] + ' object has no type')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -1007,6 +1013,7 @@ def receiveLike(recentPostsCache: {},
print('DEBUG: ' + messageJson['type'] + ' has no "to" list') print('DEBUG: ' + messageJson['type'] + ' has no "to" list')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -1075,6 +1082,7 @@ def receiveUndoLike(recentPostsCache: {},
' like object is not a string') ' like object is not a string')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -1287,6 +1295,7 @@ def receiveDelete(session, handle: str, isGroup: bool, baseDir: str,
print('DEBUG: ' + messageJson['type'] + ' has no "to" list') print('DEBUG: ' + messageJson['type'] + ' has no "to" list')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -1357,6 +1366,7 @@ def receiveAnnounce(recentPostsCache: {},
print('DEBUG: ' + messageJson['type'] + ' has no "to" list') print('DEBUG: ' + messageJson['type'] + ' has no "to" list')
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -1365,6 +1375,7 @@ def receiveAnnounce(recentPostsCache: {},
messageJson['type']) messageJson['type'])
return False return False
if '/users/' not in messageJson['object'] and \ if '/users/' not in messageJson['object'] and \
'/accounts/' not in messageJson['object'] and \
'/channel/' not in messageJson['object'] and \ '/channel/' not in messageJson['object'] and \
'/profile/' not in messageJson['object']: '/profile/' not in messageJson['object']:
if debug: if debug:
@ -1433,6 +1444,7 @@ def receiveAnnounce(recentPostsCache: {},
lookupActor = attrib lookupActor = attrib
if lookupActor: if lookupActor:
if '/users/' in lookupActor or \ if '/users/' in lookupActor or \
'/accounts/' in lookupActor or \
'/channel/' in lookupActor or \ '/channel/' in lookupActor or \
'/profile/' in lookupActor: '/profile/' in lookupActor:
if '/statuses/' in lookupActor: if '/statuses/' in lookupActor:
@ -1484,6 +1496,7 @@ def receiveUndoAnnounce(recentPostsCache: {},
if messageJson['object']['type'] != 'Announce': if messageJson['object']['type'] != 'Announce':
return False return False
if '/users/' not in messageJson['actor'] and \ if '/users/' not in messageJson['actor'] and \
'/accounts/' not in messageJson['actor'] and \
'/channel/' not in messageJson['actor'] and \ '/channel/' not in messageJson['actor'] and \
'/profile/' not in messageJson['actor']: '/profile/' not in messageJson['actor']:
if debug: if debug:
@ -1678,6 +1691,7 @@ def obtainAvatarForReplyPost(session, baseDir: str, httpPrefix: str,
return return
if not ('/users/' in lookupActor or if not ('/users/' in lookupActor or
'/accounts/' in lookupActor or
'/channel/' in lookupActor or '/channel/' in lookupActor or
'/profile/' in lookupActor): '/profile/' in lookupActor):
return return
@ -1957,10 +1971,6 @@ def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
if not isinstance(postJsonObject['object']['tag'], list): if not isinstance(postJsonObject['object']['tag'], list):
return return
calendarPath = baseDir + '/accounts/' + handle + '/calendar'
if not os.path.isdir(calendarPath):
os.mkdir(calendarPath)
actor = postJsonObject['actor'] actor = postJsonObject['actor']
actorNickname = getNicknameFromActor(actor) actorNickname = getNicknameFromActor(actor)
actorDomain, actorPort = getDomainFromActor(actor) actorDomain, actorPort = getDomainFromActor(actor)
@ -1970,6 +1980,11 @@ def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
handleNickname, handleDomain, handleNickname, handleDomain,
actorNickname, actorDomain): actorNickname, actorDomain):
return return
postId = \
postJsonObject['id'].replace('/activity', '').replace('/', '#')
# look for events within the tags list
for tagDict in postJsonObject['object']['tag']: for tagDict in postJsonObject['object']['tag']:
if not tagDict.get('type'): if not tagDict.get('type'):
continue continue
@ -1977,38 +1992,7 @@ def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
continue continue
if not tagDict.get('startTime'): if not tagDict.get('startTime'):
continue continue
# get the year and month from the event saveEvent(baseDir, handle, postId, tagDict)
eventTime = datetime.datetime.strptime(tagDict['startTime'],
"%Y-%m-%dT%H:%M:%S%z")
eventYear = int(eventTime.strftime("%Y"))
eventMonthNumber = int(eventTime.strftime("%m"))
eventDayOfMonth = int(eventTime.strftime("%d"))
if not os.path.isdir(calendarPath + '/' + str(eventYear)):
os.mkdir(calendarPath + '/' + str(eventYear))
calendarFilename = calendarPath + '/' + str(eventYear) + \
'/' + str(eventMonthNumber) + '.txt'
postId = \
postJsonObject['id'].replace('/activity', '').replace('/', '#')
if os.path.isfile(calendarFilename):
if postId in open(calendarFilename).read():
return
calendarFile = open(calendarFilename, 'a+')
if calendarFile:
calendarFile.write(postId + '\n')
calendarFile.close()
calendarNotificationFilename = \
baseDir + '/accounts/' + handle + '/.newCalendar'
calendarNotificationFile = \
open(calendarNotificationFilename, 'w+')
if calendarNotificationFile:
calendarNotificationFile.write('/calendar?year=' +
str(eventYear) +
'?month=' +
str(eventMonthNumber) +
'?day=' +
str(eventDayOfMonth))
calendarNotificationFile.close()
def inboxUpdateIndex(boxname: str, baseDir: str, handle: str, def inboxUpdateIndex(boxname: str, baseDir: str, handle: str,

View File

@ -90,6 +90,7 @@ def like(recentPostsCache: {},
likedPostDomain, likedPostPort = getDomainFromActor(actorLiked) likedPostDomain, likedPostPort = getDomainFromActor(actorLiked)
else: else:
if '/users/' in objectUrl or \ if '/users/' in objectUrl or \
'/accounts/' in objectUrl or \
'/channel/' in objectUrl or \ '/channel/' in objectUrl or \
'/profile/' in objectUrl: '/profile/' in objectUrl:
likedPostNickname = getNicknameFromActor(objectUrl) likedPostNickname = getNicknameFromActor(objectUrl)
@ -193,6 +194,7 @@ def undolike(recentPostsCache: {},
likedPostDomain, likedPostPort = getDomainFromActor(actorLiked) likedPostDomain, likedPostPort = getDomainFromActor(actorLiked)
else: else:
if '/users/' in objectUrl or \ if '/users/' in objectUrl or \
'/accounts/' in objectUrl or \
'/channel/' in objectUrl or \ '/channel/' in objectUrl or \
'/profile/' in objectUrl: '/profile/' in objectUrl:
likedPostNickname = getNicknameFromActor(objectUrl) likedPostNickname = getNicknameFromActor(objectUrl)

View File

@ -136,6 +136,7 @@ def getUserUrl(wfRequest: {}) -> str:
if link.get('type') and link.get('href'): if link.get('type') and link.get('href'):
if link['type'] == 'application/activity+json': if link['type'] == 'application/activity+json':
if not ('/users/' in link['href'] or if not ('/users/' in link['href'] or
'/accounts/' in link['href'] or
'/profile/' in link['href'] or '/profile/' in link['href'] or
'/channel/' in link['href']): '/channel/' in link['href']):
print('Webfinger activity+json contains ' + print('Webfinger activity+json contains ' +
@ -207,7 +208,7 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
return None, None, None, None, None, None, None, None return None, None, None, None, None, None, None, None
personJson = getPersonFromCache(baseDir, personUrl, personCache) personJson = getPersonFromCache(baseDir, personUrl, personCache)
if not personJson: if not personJson:
if '/channel/' in personUrl: if '/channel/' in personUrl or '/accounts/' in personUrl:
asHeader = { asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"' 'Accept': 'application/ld+json; profile="' + profileStr + '"'
} }
@ -3188,7 +3189,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
asHeader = { asHeader = {
'Accept': 'application/activity+json; profile="' + profileStr + '"' 'Accept': 'application/activity+json; profile="' + profileStr + '"'
} }
if '/channel/' in postJsonObject['actor']: if '/channel/' in postJsonObject['actor'] or \
'/accounts/' in postJsonObject['actor']:
asHeader = { asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"' 'Accept': 'application/ld+json; profile="' + profileStr + '"'
} }
@ -3238,6 +3240,7 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
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 \
'/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)

View File

@ -1585,6 +1585,30 @@ def testActorParsing():
nickname = getNicknameFromActor(actor) nickname = getNicknameFromActor(actor)
assert nickname == 'mynick' assert nickname == 'mynick'
actor = 'https://element/accounts/badger'
domain, port = getDomainFromActor(actor)
assert domain == 'element'
nickname = getNicknameFromActor(actor)
assert nickname == 'badger'
actor = 'egg@chicken.com'
domain, port = getDomainFromActor(actor)
assert domain == 'chicken.com'
nickname = getNicknameFromActor(actor)
assert nickname == 'egg'
actor = '@waffle@cardboard'
domain, port = getDomainFromActor(actor)
assert domain == 'cardboard'
nickname = getNicknameFromActor(actor)
assert nickname == 'waffle'
actor = 'https://astral/channel/sky'
domain, port = getDomainFromActor(actor)
assert domain == 'astral'
nickname = getNicknameFromActor(actor)
assert nickname == 'sky'
actor = 'https://randomain/users/rando' actor = 'https://randomain/users/rando'
domain, port = getDomainFromActor(actor) domain, port = getDomainFromActor(actor)
assert domain == 'randomain' assert domain == 'randomain'

View File

@ -277,6 +277,7 @@ def setThemeNight(baseDir: str):
fontStr = \ fontStr = \
"url('./fonts/solidaric.woff2') format('woff2')" "url('./fonts/solidaric.woff2') format('woff2')"
themeParams = { themeParams = {
"focus-color": "blue",
"font-size-button-mobile": "36px", "font-size-button-mobile": "36px",
"font-size": "32px", "font-size": "32px",
"font-size2": "26px", "font-size2": "26px",
@ -320,6 +321,7 @@ def setThemeStarlight(baseDir: str):
removeTheme(baseDir) removeTheme(baseDir)
setThemeInConfig(baseDir, name) setThemeInConfig(baseDir, name)
themeParams = { themeParams = {
"focus-color": "darkred",
"font-size-button-mobile": "36px", "font-size-button-mobile": "36px",
"font-size": "32px", "font-size": "32px",
"font-size2": "26px", "font-size2": "26px",
@ -341,6 +343,7 @@ def setThemeStarlight(baseDir: str):
"hashtag-vertical-spacing3": "100px", "hashtag-vertical-spacing3": "100px",
"hashtag-vertical-spacing4": "150px", "hashtag-vertical-spacing4": "150px",
"button-background": "#69282c", "button-background": "#69282c",
"button-small-background": "darkblue",
"button-selected": "#a34046", "button-selected": "#a34046",
"button-highlighted": "#12435f", "button-highlighted": "#12435f",
"button-fg-highlighted": "white", "button-fg-highlighted": "white",
@ -501,7 +504,9 @@ def setThemeLCD(baseDir: str):
"button-selected": "black", "button-selected": "black",
"button-highlighted": "green", "button-highlighted": "green",
"button-background": "#33390d", "button-background": "#33390d",
"button-small-background": "#33390d",
"button-text": "#9fb42b", "button-text": "#9fb42b",
"button-small-text": "#9fb42b",
"color: #FFFFFE;": "color: #9fb42b;", "color: #FFFFFE;": "color: #9fb42b;",
"calendar-bg-color": "#eee", "calendar-bg-color": "#eee",
"day-number": "#3f2145", "day-number": "#3f2145",
@ -568,7 +573,9 @@ def setThemePurple(baseDir: str):
"main-visited-color": "#f93bb0", "main-visited-color": "#f93bb0",
"button-selected": "#c042a0", "button-selected": "#c042a0",
"button-background": "#ff42a0", "button-background": "#ff42a0",
"button-small-background": "#ff42a0",
"button-text": "white", "button-text": "white",
"button-small-text": "white",
"color: #FFFFFE;": "color: #1f152d;", "color: #FFFFFE;": "color: #1f152d;",
"calendar-bg-color": "#eee", "calendar-bg-color": "#eee",
"lines-color": "#ff42a0", "lines-color": "#ff42a0",
@ -599,6 +606,7 @@ def setThemePurple(baseDir: str):
def setThemeHacker(baseDir: str): def setThemeHacker(baseDir: str):
name = 'hacker' name = 'hacker'
themeParams = { themeParams = {
"focus-color": "green",
"main-bg-color": "black", "main-bg-color": "black",
"link-bg-color": "black", "link-bg-color": "black",
"main-bg-color-dm": "#0b0a0a", "main-bg-color-dm": "#0b0a0a",
@ -612,7 +620,9 @@ def setThemeHacker(baseDir: str):
"main-visited-color": "#3c8234", "main-visited-color": "#3c8234",
"button-selected": "#063200", "button-selected": "#063200",
"button-background": "#062200", "button-background": "#062200",
"button-small-background": "#062200",
"button-text": "#00ff00", "button-text": "#00ff00",
"button-small-text": "#00ff00",
"button-corner-radius": "4px", "button-corner-radius": "4px",
"timeline-border-radius": "4px", "timeline-border-radius": "4px",
"*font-family": "'Bedstead'", "*font-family": "'Bedstead'",
@ -646,6 +656,7 @@ def setThemeHacker(baseDir: str):
def setThemeLight(baseDir: str): def setThemeLight(baseDir: str):
name = 'light' name = 'light'
themeParams = { themeParams = {
"focus-color": "grey",
"font-size-button-mobile": "36px", "font-size-button-mobile": "36px",
"font-size": "32px", "font-size": "32px",
"font-size2": "26px", "font-size2": "26px",
@ -700,6 +711,7 @@ def setThemeLight(baseDir: str):
def setThemeSolidaric(baseDir: str): def setThemeSolidaric(baseDir: str):
name = 'solidaric' name = 'solidaric'
themeParams = { themeParams = {
"focus-color": "grey",
"font-size-button-mobile": "36px", "font-size-button-mobile": "36px",
"font-size": "32px", "font-size": "32px",
"font-size2": "26px", "font-size2": "26px",

View File

@ -91,7 +91,7 @@
"Stop blocking": "وقف الحظر", "Stop blocking": "وقف الحظر",
"Enter an emoji name to search for": "أدخل اسم رمز تعبيري للبحث عنه", "Enter an emoji name to search for": "أدخل اسم رمز تعبيري للبحث عنه",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "أدخل عنوانًا أو عنصرًا مشتركًا أو! history أو #hashtag أو * مهارة أو: emoji: للبحث عنه", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "أدخل عنوانًا أو عنصرًا مشتركًا أو! history أو #hashtag أو * مهارة أو: emoji: للبحث عنه",
"Go Back": "عد", "Go Back": "",
"Moderation Information": "معلومات الاعتدال", "Moderation Information": "معلومات الاعتدال",
"Suspended accounts": "الحسابات المعلقه", "Suspended accounts": "الحسابات المعلقه",
"These are currently suspended": "هذه معلقة حاليا", "These are currently suspended": "هذه معلقة حاليا",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Deixeu de bloquejar", "Stop blocking": "Deixeu de bloquejar",
"Enter an emoji name to search for": "Introduïu un nom emoji per cercar", "Enter an emoji name to search for": "Introduïu un nom emoji per cercar",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Introduïu una adreça, un element compartit, un historial!, #Hashtag, * skill o: emoji: per cercar", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Introduïu una adreça, un element compartit, un historial!, #Hashtag, * skill o: emoji: per cercar",
"Go Back": "Torna", "Go Back": "",
"Moderation Information": "Informació de moderació", "Moderation Information": "Informació de moderació",
"Suspended accounts": "Comptes suspesos", "Suspended accounts": "Comptes suspesos",
"These are currently suspended": "Actualment estan suspeses", "These are currently suspended": "Actualment estan suspeses",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Stopiwch rwystro", "Stop blocking": "Stopiwch rwystro",
"Enter an emoji name to search for": "Rhowch enw emoji i chwilio amdano", "Enter an emoji name to search for": "Rhowch enw emoji i chwilio amdano",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Rhowch gyfeiriad, eitem a rennir ,! Hanes, #hashtag, * sgil neu: emoji: i chwilio amdano", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Rhowch gyfeiriad, eitem a rennir ,! Hanes, #hashtag, * sgil neu: emoji: i chwilio amdano",
"Go Back": "Go Back", "Go Back": "",
"Moderation Information": "Gwybodaeth Cymedroli", "Moderation Information": "Gwybodaeth Cymedroli",
"Suspended accounts": "Cyfrifon gohiriedig", "Suspended accounts": "Cyfrifon gohiriedig",
"These are currently suspended": "Mae'r rhain wedi'u hatal ar hyn o bryd", "These are currently suspended": "Mae'r rhain wedi'u hatal ar hyn o bryd",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Sperre aufheben", "Stop blocking": "Sperre aufheben",
"Enter an emoji name to search for": "Geben Sie einen Emojinamen ein, nach dem gesucht werden soll", "Enter an emoji name to search for": "Geben Sie einen Emojinamen ein, nach dem gesucht werden soll",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Geben Sie eine Adresse, ein freigegebenes Element ,! History, #hashtag, * Skill oder: emoji: ein, nach der gesucht werden soll", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Geben Sie eine Adresse, ein freigegebenes Element ,! History, #hashtag, * Skill oder: emoji: ein, nach der gesucht werden soll",
"Go Back": "Zurück", "Go Back": "",
"Moderation Information": "Moderationsinformationen", "Moderation Information": "Moderationsinformationen",
"Suspended accounts": "Temporäre gesperrte Benutzer", "Suspended accounts": "Temporäre gesperrte Benutzer",
"These are currently suspended": "Diese sind temporär gesperrt", "These are currently suspended": "Diese sind temporär gesperrt",

View File

@ -39,7 +39,7 @@
"Report": "Report", "Report": "Report",
"Send to moderators": "Send to moderators", "Send to moderators": "Send to moderators",
"Search for emoji": "Search for emoji", "Search for emoji": "Search for emoji",
"Cancel": "Cancel", "Cancel": "",
"Submit": "Submit", "Submit": "Submit",
"Image description": "Image description", "Image description": "Image description",
"Item image": "Item image", "Item image": "Item image",
@ -91,7 +91,7 @@
"Stop blocking": "Stop blocking", "Stop blocking": "Stop blocking",
"Enter an emoji name to search for": "Enter an emoji name to search for", "Enter an emoji name to search for": "Enter an emoji name to search for",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for",
"Go Back": "Go Back", "Go Back": "",
"Moderation Information": "Moderation Information", "Moderation Information": "Moderation Information",
"Suspended accounts": "Suspended accounts", "Suspended accounts": "Suspended accounts",
"These are currently suspended": "These are currently suspended", "These are currently suspended": "These are currently suspended",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Dejar de bloquear", "Stop blocking": "Dejar de bloquear",
"Enter an emoji name to search for": "Ingrese un nombre de emoji para buscar", "Enter an emoji name to search for": "Ingrese un nombre de emoji para buscar",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Ingrese una dirección, elemento compartido,! Historial, #hashtag, * skill o: emoji: para buscar", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Ingrese una dirección, elemento compartido,! Historial, #hashtag, * skill o: emoji: para buscar",
"Go Back": "Regresa", "Go Back": "",
"Moderation Information": "Información de moderación", "Moderation Information": "Información de moderación",
"Suspended accounts": "Cuentas suspendidas", "Suspended accounts": "Cuentas suspendidas",
"These are currently suspended": "Actualmente están suspendidos", "These are currently suspended": "Actualmente están suspendidos",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Arrêtez le blocage", "Stop blocking": "Arrêtez le blocage",
"Enter an emoji name to search for": "Entrez un nom emoji à rechercher", "Enter an emoji name to search for": "Entrez un nom emoji à rechercher",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Entrez une adresse, un élément partagé,! History, #hashtag, * skill ou: emoji: pour rechercher", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Entrez une adresse, un élément partagé,! History, #hashtag, * skill ou: emoji: pour rechercher",
"Go Back": "Retourner", "Go Back": "",
"Moderation Information": "Informations de modération", "Moderation Information": "Informations de modération",
"Suspended accounts": "Comptes suspendus", "Suspended accounts": "Comptes suspendus",
"These are currently suspended": "Ceux-ci sont actuellement suspendus", "These are currently suspended": "Ceux-ci sont actuellement suspendus",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Stop blocáil", "Stop blocking": "Stop blocáil",
"Enter an emoji name to search for": "Cuir isteach ainm emoji chun cuardach a dhéanamh", "Enter an emoji name to search for": "Cuir isteach ainm emoji chun cuardach a dhéanamh",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Iontráil seoladh, mír roinnte ,! Stair, #hashtag, * scil nó: emoji: chun cuardach a dhéanamh", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Iontráil seoladh, mír roinnte ,! Stair, #hashtag, * scil nó: emoji: chun cuardach a dhéanamh",
"Go Back": "Dul ar ais", "Go Back": "",
"Moderation Information": "Faisnéis Modhnóireachta", "Moderation Information": "Faisnéis Modhnóireachta",
"Suspended accounts": "Cuntais ar fionraí", "Suspended accounts": "Cuntais ar fionraí",
"These are currently suspended": "Tá siad seo ar fionraí faoi láthair", "These are currently suspended": "Tá siad seo ar fionraí faoi láthair",

View File

@ -91,7 +91,7 @@
"Stop blocking": "रोकना बंद करो", "Stop blocking": "रोकना बंद करो",
"Enter an emoji name to search for": "खोजने के लिए एक इमोजी नाम दर्ज करें", "Enter an emoji name to search for": "खोजने के लिए एक इमोजी नाम दर्ज करें",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "एक पता, साझा किया गया आइटम दर्ज करें; इतिहास, # अंश, * कौशल या: इमोजी: खोजने के लिए", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "एक पता, साझा किया गया आइटम दर्ज करें; इतिहास, # अंश, * कौशल या: इमोजी: खोजने के लिए",
"Go Back": "वापस जाओ", "Go Back": "",
"Moderation Information": "मॉडरेशन जानकारी", "Moderation Information": "मॉडरेशन जानकारी",
"Suspended accounts": "निलंबित खाते", "Suspended accounts": "निलंबित खाते",
"These are currently suspended": "ये फिलहाल निलंबित हैं", "These are currently suspended": "ये फिलहाल निलंबित हैं",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Smetti di bloccare", "Stop blocking": "Smetti di bloccare",
"Enter an emoji name to search for": "Inserisci un nome emoji da cercare", "Enter an emoji name to search for": "Inserisci un nome emoji da cercare",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Inserisci un indirizzo, un oggetto condiviso,! Storia, #hashtag, * abilità o: emoji: per cercare", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Inserisci un indirizzo, un oggetto condiviso,! Storia, #hashtag, * abilità o: emoji: per cercare",
"Go Back": "Torna indietro", "Go Back": "",
"Moderation Information": "Informazioni sulla moderazione", "Moderation Information": "Informazioni sulla moderazione",
"Suspended accounts": "Conti sospesi", "Suspended accounts": "Conti sospesi",
"These are currently suspended": "Questi sono attualmente sospesi", "These are currently suspended": "Questi sono attualmente sospesi",

View File

@ -91,7 +91,7 @@
"Stop blocking": "ブロックを停止", "Stop blocking": "ブロックを停止",
"Enter an emoji name to search for": "検索する絵文字名を入力してください", "Enter an emoji name to search for": "検索する絵文字名を入力してください",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "検索するアドレス、共有アイテム、history、ハッシュタグ、* skillまたはemojiを入力してください", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "検索するアドレス、共有アイテム、history、ハッシュタグ、* skillまたはemojiを入力してください",
"Go Back": "戻る", "Go Back": "",
"Moderation Information": "モデレーション情報", "Moderation Information": "モデレーション情報",
"Suspended accounts": "一時停止されたアカウント", "Suspended accounts": "一時停止されたアカウント",
"These are currently suspended": "これらは現在一時停止中です", "These are currently suspended": "これらは現在一時停止中です",

View File

@ -98,7 +98,7 @@
"About this Instance": "A prepaus daquesta instància", "About this Instance": "A prepaus daquesta instància",
"Any blocks or suspensions made by moderators will be shown here.": "Tot blocatge o suspension realizada pels moderators son mostrats aquí.", "Any blocks or suspensions made by moderators will be shown here.": "Tot blocatge o suspension realizada pels moderators son mostrats aquí.",
"These are globally blocked for all accounts on this instance": "Aquí son los blocatges generals per totes los comptes daquesta instància", "These are globally blocked for all accounts on this instance": "Aquí son los blocatges generals per totes los comptes daquesta instància",
"Go Back": "Tornar", "Go Back": "",
"Stop blocking": "Quitar de blocar", "Stop blocking": "Quitar de blocar",
"View": "Veire", "View": "Veire",
"Options for": "Opcions per", "Options for": "Opcions per",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Pare de bloquear", "Stop blocking": "Pare de bloquear",
"Enter an emoji name to search for": "Digite um nome emoji para procurar", "Enter an emoji name to search for": "Digite um nome emoji para procurar",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Digite um endereço, item compartilhado,! History, #hashtag, * skill ou: emoji: para procurar", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Digite um endereço, item compartilhado,! History, #hashtag, * skill ou: emoji: para procurar",
"Go Back": "Volte", "Go Back": "",
"Moderation Information": "Informações sobre moderação", "Moderation Information": "Informações sobre moderação",
"Suspended accounts": "Contas suspensas", "Suspended accounts": "Contas suspensas",
"These are currently suspended": "Estes estão atualmente suspensos", "These are currently suspended": "Estes estão atualmente suspensos",

View File

@ -91,7 +91,7 @@
"Stop blocking": "Прекратить блокировку", "Stop blocking": "Прекратить блокировку",
"Enter an emoji name to search for": "Введите имя смайлика для поиска", "Enter an emoji name to search for": "Введите имя смайлика для поиска",
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Введите адрес, общий элемент,! History, #hashtag, * skill или: emoji: для поиска", "Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Введите адрес, общий элемент,! History, #hashtag, * skill или: emoji: для поиска",
"Go Back": "Вернитесь назад", "Go Back": "",
"Moderation Information": "Модерация Информация", "Moderation Information": "Модерация Информация",
"Suspended accounts": "Приостановленные аккаунты", "Suspended accounts": "Приостановленные аккаунты",
"These are currently suspended": "В настоящее время они приостановлены", "These are currently suspended": "В настоящее время они приостановлены",

View File

@ -111,6 +111,7 @@
"The files attached below should be no larger than 10MB in total uploaded at once.": "一次上传的文件总数不得超过10MB。", "The files attached below should be no larger than 10MB in total uploaded at once.": "一次上传的文件总数不得超过10MB。",
"Avatar image": "头像图片", "Avatar image": "头像图片",
"Background image": "背景图", "Background image": "背景图",
"Go Back": "◀",
"Timeline banner image": "时间线横幅图片", "Timeline banner image": "时间线横幅图片",
"Approve follower requests": "批准关注者请求", "Approve follower requests": "批准关注者请求",
"This is a bot account": "这是一个机器人帐户", "This is a bot account": "这是一个机器人帐户",

View File

@ -215,6 +215,8 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
def getNicknameFromActor(actor: str) -> str: def getNicknameFromActor(actor: str) -> str:
"""Returns the nickname from an actor url """Returns the nickname from an actor url
""" """
if actor.startswith('@'):
actor = actor[1:]
if '/users/' not in actor: if '/users/' not in actor:
if '/profile/' in actor: if '/profile/' in actor:
nickStr = actor.split('/profile/')[1].replace('@', '') nickStr = actor.split('/profile/')[1].replace('@', '')
@ -222,18 +224,27 @@ def getNicknameFromActor(actor: str) -> str:
return nickStr return nickStr
else: else:
return nickStr.split('/')[0] return nickStr.split('/')[0]
if '/channel/' in actor: elif '/channel/' in actor:
nickStr = actor.split('/channel/')[1].replace('@', '') nickStr = actor.split('/channel/')[1].replace('@', '')
if '/' not in nickStr: if '/' not in nickStr:
return nickStr return nickStr
else: else:
return nickStr.split('/')[0] return nickStr.split('/')[0]
# https://domain/@nick elif '/accounts/' in actor:
if '/@' in actor: nickStr = actor.split('/accounts/')[1].replace('@', '')
if '/' not in nickStr:
return nickStr
else:
return nickStr.split('/')[0]
elif '/@' in actor:
# https://domain/@nick
nickStr = actor.split('/@')[1] nickStr = actor.split('/@')[1]
if '/' in nickStr: if '/' in nickStr:
nickStr = nickStr.split('/')[0] nickStr = nickStr.split('/')[0]
return nickStr return nickStr
elif '@' in actor:
nickStr = actor.split('@')[0]
return nickStr
return None return None
nickStr = actor.split('/users/')[1].replace('@', '') nickStr = actor.split('/users/')[1].replace('@', '')
if '/' not in nickStr: if '/' not in nickStr:
@ -245,28 +256,38 @@ def getNicknameFromActor(actor: str) -> str:
def getDomainFromActor(actor: str) -> (str, int): def getDomainFromActor(actor: str) -> (str, int):
"""Returns the domain name from an actor url """Returns the domain name from an actor url
""" """
if actor.startswith('@'):
actor = actor[1:]
port = None port = None
prefixes = getProtocolPrefixes() prefixes = getProtocolPrefixes()
if '/profile/' in actor: if '/profile/' in actor:
domain = actor.split('/profile/')[0] domain = actor.split('/profile/')[0]
for prefix in prefixes: for prefix in prefixes:
domain = domain.replace(prefix, '') domain = domain.replace(prefix, '')
elif '/accounts/' in actor:
domain = actor.split('/accounts/')[0]
for prefix in prefixes:
domain = domain.replace(prefix, '')
elif '/channel/' in actor:
domain = actor.split('/channel/')[0]
for prefix in prefixes:
domain = domain.replace(prefix, '')
elif '/users/' in actor:
domain = actor.split('/users/')[0]
for prefix in prefixes:
domain = domain.replace(prefix, '')
elif '/@' in actor:
domain = actor.split('/@')[0]
for prefix in prefixes:
domain = domain.replace(prefix, '')
elif '@' in actor:
domain = actor.split('@')[1].strip()
else: else:
if '/channel/' in actor: domain = actor
domain = actor.split('/channel/')[0] for prefix in prefixes:
for prefix in prefixes: domain = domain.replace(prefix, '')
domain = domain.replace(prefix, '') if '/' in actor:
else: domain = domain.split('/')[0]
if '/users/' not in actor:
domain = actor
for prefix in prefixes:
domain = domain.replace(prefix, '')
if '/' in actor:
domain = domain.split('/')[0]
else:
domain = actor.split('/users/')[0]
for prefix in prefixes:
domain = domain.replace(prefix, '')
if ':' in domain: if ':' in domain:
portStr = domain.split(':')[1] portStr = domain.split(':')[1]
if not portStr.isdigit(): if not portStr.isdigit():
@ -576,12 +597,13 @@ def validNickname(domain: str, nickname: str) -> bool:
if nickname == domain: if nickname == domain:
return False return False
reservedNames = ('inbox', 'dm', 'outbox', 'following', reservedNames = ('inbox', 'dm', 'outbox', 'following',
'public', 'followers', 'profile', 'public', 'followers',
'channel', 'capabilities', 'calendar', 'channel', 'capabilities', 'calendar',
'tlreplies', 'tlmedia', 'tlblogs', 'tlreplies', 'tlmedia', 'tlblogs',
'moderation', 'activity', 'undo', 'moderation', 'activity', 'undo',
'reply', 'replies', 'question', 'like', 'reply', 'replies', 'question', 'like',
'likes', 'users', 'statuses', 'likes', 'users', 'statuses',
'accounts', 'channels', 'profile',
'updates', 'repeat', 'announce', 'updates', 'repeat', 'announce',
'shares', 'fonts', 'icons') 'shares', 'fonts', 'icons')
if nickname in reservedNames: if nickname in reservedNames:

View File

@ -252,7 +252,7 @@ def updateAvatarImageCache(session, baseDir: str, httpPrefix: str,
print('Failed to download avatar image: ' + str(avatarUrl)) print('Failed to download avatar image: ' + str(avatarUrl))
print(e) print(e)
prof = 'https://www.w3.org/ns/activitystreams' prof = 'https://www.w3.org/ns/activitystreams'
if '/channel/' not in actor: if '/channel/' not in actor or '/accounts/' not in actor:
sessionHeaders = { sessionHeaders = {
'Accept': 'application/activity+json; profile="' + prof + '"' 'Accept': 'application/activity+json; profile="' + prof + '"'
} }
@ -747,7 +747,13 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
# add the page title # add the page title
hashtagSearchForm = htmlHeader(cssFilename, hashtagSearchCSS) hashtagSearchForm = htmlHeader(cssFilename, hashtagSearchCSS)
hashtagSearchForm += '<center><h1>#' + hashtag + '</h1></center>' if nickname:
hashtagSearchForm += '<center>\n' + \
'<h1><a href="/users/' + nickname + '/search">#' + \
hashtag + '</a></h1>\n' + '</center>\n'
else:
hashtagSearchForm += '<center>\n' + \
'<h1>#' + hashtag + '</h1>\n' + '</center>\n'
if startIndex > 0: if startIndex > 0:
# previous page link # previous page link
@ -758,7 +764,7 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
iconsDir + '/pageup.png" title="' + \ iconsDir + '/pageup.png" title="' + \
translate['Page up'] + \ translate['Page up'] + \
'" alt="' + translate['Page up'] + \ '" alt="' + translate['Page up'] + \
'"></a></center>' '"></a></center>\n'
index = startIndex index = startIndex
while index <= endIndex: while index <= endIndex:
postId = lines[index].strip('\n').strip('\r') postId = lines[index].strip('\n').strip('\r')
@ -1306,240 +1312,242 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
editProfileForm = htmlHeader(cssFilename, editProfileCSS) editProfileForm = htmlHeader(cssFilename, editProfileCSS)
editProfileForm += \ editProfileForm += \
'<form enctype="multipart/form-data" method="POST" ' + \ '<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="' + path + '/profiledata">' 'accept-charset="UTF-8" action="' + path + '/profiledata">\n'
editProfileForm += ' <div class="vertical-center">' editProfileForm += ' <div class="vertical-center">\n'
editProfileForm += \ editProfileForm += \
' <p class="new-post-text">' + translate['Profile for'] + \ ' <p class="new-post-text">' + translate['Profile for'] + \
' ' + nickname + '@' + domainFull + '</p>' ' ' + nickname + '@' + domainFull + '</p>'
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
editProfileForm += \
' <input type="submit" name="submitProfile" value="' + \
translate['Submit'] + '">'
editProfileForm += \ editProfileForm += \
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \ ' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
translate['Cancel'] + '</button></a>' translate['Go Back'] + '</button></a>\n'
editProfileForm += ' </div>' editProfileForm += \
' <input type="submit" name="submitProfile" value="' + \
translate['Submit'] + '">\n'
editProfileForm += ' </div>\n'
if scheduledPostsExist(baseDir, nickname, domain): if scheduledPostsExist(baseDir, nickname, domain):
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
editProfileForm += \ editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \ ' <input type="checkbox" class="profilecheckbox" ' + \
'name="removeScheduledPosts"> ' + \ 'name="removeScheduledPosts"> ' + \
translate['Remove scheduled posts'] + '<br>' translate['Remove scheduled posts'] + '<br>\n'
editProfileForm += ' </div>' editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
editProfileForm += ' <label class="labels">' + \ editProfileForm += ' <label class="labels">' + \
translate['Nickname'] + '</label>' translate['Nickname'] + '</label>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="displayNickname" value="' + \ ' <input type="text" name="displayNickname" value="' + \
displayNickname + '"><br>' displayNickname + '"><br>\n'
editProfileForm += \ editProfileForm += \
' <label class="labels">' + translate['Your bio'] + '</label>' ' <label class="labels">' + translate['Your bio'] + '</label>\n'
editProfileForm += \ editProfileForm += \
' <textarea id="message" name="bio" style="height:200px">' + \ ' <textarea id="message" name="bio" style="height:200px">' + \
bioStr + '</textarea>' bioStr + '</textarea>\n'
editProfileForm += '<label class="labels">' + \ editProfileForm += '<label class="labels">' + \
translate['Donations link'] + '</label><br>' translate['Donations link'] + '</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" placeholder="https://..." ' + \ ' <input type="text" placeholder="https://..." ' + \
'name="donateUrl" value="' + donateUrl + '">' 'name="donateUrl" value="' + donateUrl + '">\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + translate['XMPP'] + '</label><br>' '<label class="labels">' + translate['XMPP'] + '</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="xmppAddress" value="' + \ ' <input type="text" name="xmppAddress" value="' + \
xmppAddress + '">' xmppAddress + '">\n'
editProfileForm += '<label class="labels">' + \ editProfileForm += '<label class="labels">' + \
translate['Matrix'] + '</label><br>' translate['Matrix'] + '</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="matrixAddress" value="' + \ ' <input type="text" name="matrixAddress" value="' + \
matrixAddress+'">' matrixAddress+'">\n'
editProfileForm += '<label class="labels">SSB</label><br>' editProfileForm += '<label class="labels">SSB</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="ssbAddress" value="' + \ ' <input type="text" name="ssbAddress" value="' + \
ssbAddress + '">' ssbAddress + '">\n'
editProfileForm += '<label class="labels">Blog</label><br>' editProfileForm += '<label class="labels">Blog</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="blogAddress" value="' + \ ' <input type="text" name="blogAddress" value="' + \
blogAddress + '">' blogAddress + '">\n'
editProfileForm += '<label class="labels">Tox</label><br>' editProfileForm += '<label class="labels">Tox</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="toxAddress" value="' + \ ' <input type="text" name="toxAddress" value="' + \
toxAddress + '">' toxAddress + '">\n'
editProfileForm += '<label class="labels">' + \ editProfileForm += '<label class="labels">' + \
translate['Email'] + '</label><br>' translate['Email'] + '</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="email" value="' + emailAddress + '">' ' <input type="text" name="email" value="' + emailAddress + '">\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + \ '<label class="labels">' + \
translate['PGP Fingerprint'] + '</label><br>' translate['PGP Fingerprint'] + '</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="openpgp" value="' + \ ' <input type="text" name="openpgp" value="' + \
PGPfingerprint + '">' PGPfingerprint + '">\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + translate['PGP'] + '</label><br>' '<label class="labels">' + translate['PGP'] + '</label><br>\n'
editProfileForm += \ editProfileForm += \
' <textarea id="message" placeholder=' + \ ' <textarea id="message" placeholder=' + \
'"-----BEGIN PGP PUBLIC KEY BLOCK-----" name="pgp" ' + \ '"-----BEGIN PGP PUBLIC KEY BLOCK-----" name="pgp" ' + \
'style="height:100px">' + PGPpubKey + '</textarea>' 'style="height:100px">' + PGPpubKey + '</textarea>\n'
editProfileForm += '<a href="/users/' + nickname + \ editProfileForm += '<a href="/users/' + nickname + \
'/followingaccounts"><label class="labels">' + \ '/followingaccounts"><label class="labels">' + \
translate['Following'] + '</label></a><br>' translate['Following'] + '</label></a><br>\n'
editProfileForm += ' </div>' editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
idx = 'The files attached below should be no larger than ' + \ idx = 'The files attached below should be no larger than ' + \
'10MB in total uploaded at once.' '10MB in total uploaded at once.'
editProfileForm += \ editProfileForm += \
' <label class="labels">' + translate[idx] + '</label><br><br>' ' <label class="labels">' + translate[idx] + '</label><br><br>\n'
editProfileForm += \ editProfileForm += \
' <label class="labels">' + translate['Avatar image'] + '</label>' ' <label class="labels">' + translate['Avatar image'] + \
'</label>\n'
editProfileForm += \ editProfileForm += \
' <input type="file" id="avatar" name="avatar"' ' <input type="file" id="avatar" name="avatar"'
editProfileForm += ' accept="' + imageFormats + '">' editProfileForm += ' accept="' + imageFormats + '">\n'
editProfileForm += \ editProfileForm += \
' <br><label class="labels">' + \ ' <br><label class="labels">' + \
translate['Background image'] + '</label>' translate['Background image'] + '</label>\n'
editProfileForm += ' <input type="file" id="image" name="image"' editProfileForm += ' <input type="file" id="image" name="image"'
editProfileForm += ' accept="' + imageFormats + '">' editProfileForm += ' accept="' + imageFormats + '">\n'
editProfileForm += ' <br><label class="labels">' + \ editProfileForm += ' <br><label class="labels">' + \
translate['Timeline banner image'] + '</label>' translate['Timeline banner image'] + '</label>\n'
editProfileForm += ' <input type="file" id="banner" name="banner"' editProfileForm += ' <input type="file" id="banner" name="banner"'
editProfileForm += ' accept="' + imageFormats + '">' editProfileForm += ' accept="' + imageFormats + '">\n'
editProfileForm += ' <br><label class="labels">' + \ editProfileForm += ' <br><label class="labels">' + \
translate['Search banner image'] + '</label>' translate['Search banner image'] + '</label>\n'
editProfileForm += ' <input type="file" id="search_banner" ' editProfileForm += ' <input type="file" id="search_banner" '
editProfileForm += 'name="search_banner"' editProfileForm += 'name="search_banner"'
editProfileForm += ' accept="' + imageFormats + '">' editProfileForm += ' accept="' + imageFormats + '">\n'
editProfileForm += ' </div>' editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + translate['Change Password'] + \ '<label class="labels">' + translate['Change Password'] + \
'</label><br>' '</label><br>\n'
editProfileForm += ' <input type="text" name="password" value=""><br>' editProfileForm += ' <input type="text" name="password" ' + \
'value=""><br>\n'
editProfileForm += \ editProfileForm += \
'<label class="labels">' + translate['Confirm Password'] + \ '<label class="labels">' + translate['Confirm Password'] + \
'</label><br>' '</label><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="text" name="passwordconfirm" value="">' ' <input type="text" name="passwordconfirm" value="">\n'
editProfileForm += ' </div>' editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
editProfileForm += \ editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \ ' <input type="checkbox" class="profilecheckbox" ' + \
'name="approveFollowers" ' + manuallyApprovesFollowers + \ 'name="approveFollowers" ' + manuallyApprovesFollowers + \
'> ' + translate['Approve follower requests'] + '<br>' '> ' + translate['Approve follower requests'] + '<br>\n'
editProfileForm += \ editProfileForm += \
' <input type="checkbox" ' + \ ' <input type="checkbox" ' + \
'class="profilecheckbox" name="isBot" ' + \ 'class="profilecheckbox" name="isBot" ' + \
isBot + '> ' + translate['This is a bot account'] + '<br>' isBot + '> ' + translate['This is a bot account'] + '<br>\n'
editProfileForm += \ editProfileForm += \
' <input type="checkbox" ' + \ ' <input type="checkbox" ' + \
'class="profilecheckbox" name="isGroup" ' + isGroup + '> ' + \ 'class="profilecheckbox" name="isGroup" ' + isGroup + '> ' + \
translate['This is a group account'] + '<br>' translate['This is a group account'] + '<br>\n'
editProfileForm += \ editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \ ' <input type="checkbox" class="profilecheckbox" ' + \
'name="followDMs" ' + followDMs + '> ' + \ 'name="followDMs" ' + followDMs + '> ' + \
translate['Only people I follow can send me DMs'] + '<br>' translate['Only people I follow can send me DMs'] + '<br>\n'
editProfileForm += \ editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \ ' <input type="checkbox" class="profilecheckbox" ' + \
'name="removeTwitter" ' + removeTwitter + '> ' + \ 'name="removeTwitter" ' + removeTwitter + '> ' + \
translate['Remove Twitter posts'] + '<br>' translate['Remove Twitter posts'] + '<br>\n'
if path.startswith('/users/' + adminNickname + '/'): if path.startswith('/users/' + adminNickname + '/'):
editProfileForm += \ editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \ ' <input type="checkbox" class="profilecheckbox" ' + \
'name="mediaInstance" ' + mediaInstanceStr + '> ' + \ 'name="mediaInstance" ' + mediaInstanceStr + '> ' + \
translate['This is a media instance'] + '<br>' translate['This is a media instance'] + '<br>\n'
editProfileForm += \ editProfileForm += \
' <br><b><label class="labels">' + \ ' <br><b><label class="labels">' + \
translate['Filtered words'] + '</label></b>' translate['Filtered words'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">' + \ editProfileForm += ' <br><label class="labels">' + \
translate['One per line'] + '</label>' translate['One per line'] + '</label>\n'
editProfileForm += ' <textarea id="message" ' + \ editProfileForm += ' <textarea id="message" ' + \
'name="filteredWords" style="height:200px">' + \ 'name="filteredWords" style="height:200px">' + \
filterStr + '</textarea>' filterStr + '</textarea>\n'
editProfileForm += \ editProfileForm += \
' <br><b><label class="labels">' + \ ' <br><b><label class="labels">' + \
translate['Word Replacements'] + '</label></b>' translate['Word Replacements'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">A -> B</label>' editProfileForm += ' <br><label class="labels">A -> B</label>\n'
editProfileForm += \ editProfileForm += \
' <textarea id="message" name="switchWords" ' + \ ' <textarea id="message" name="switchWords" ' + \
'style="height:200px">' + switchStr + '</textarea>' 'style="height:200px">' + switchStr + '</textarea>\n'
editProfileForm += \ editProfileForm += \
' <br><b><label class="labels">' + \ ' <br><b><label class="labels">' + \
translate['Blocked accounts'] + '</label></b>' translate['Blocked accounts'] + '</label></b>\n'
idx = 'Blocked accounts, one per line, in the form ' + \ idx = 'Blocked accounts, one per line, in the form ' + \
'nickname@domain or *@blockeddomain' 'nickname@domain or *@blockeddomain'
editProfileForm += \ editProfileForm += \
' <br><label class="labels">' + translate[idx] + '</label>' ' <br><label class="labels">' + translate[idx] + '</label>\n'
editProfileForm += \ editProfileForm += \
' <textarea id="message" name="blocked" style="height:200px">' + \ ' <textarea id="message" name="blocked" style="height:200px">' + \
blockedStr + '</textarea>' blockedStr + '</textarea>\n'
editProfileForm += \ editProfileForm += \
' <br><b><label class="labels">' + \ ' <br><b><label class="labels">' + \
translate['Federation list'] + '</label></b>' translate['Federation list'] + '</label></b>\n'
idx = 'Federate only with a defined set of instances. ' + \ idx = 'Federate only with a defined set of instances. ' + \
'One domain name per line.' 'One domain name per line.'
editProfileForm += \ editProfileForm += \
' <br><label class="labels">' + \ ' <br><label class="labels">' + \
translate[idx] + '</label>' translate[idx] + '</label>\n'
editProfileForm += \ editProfileForm += \
' <textarea id="message" name="allowedInstances" ' + \ ' <textarea id="message" name="allowedInstances" ' + \
'style="height:200px">' + allowedInstancesStr + '</textarea>' 'style="height:200px">' + allowedInstancesStr + '</textarea>\n'
editProfileForm += \ editProfileForm += \
' <br><b><label class="labels">' + \ ' <br><b><label class="labels">' + \
translate['Git Projects'] + '</label></b>' translate['Git Projects'] + '</label></b>\n'
idx = 'List of project names that you wish to receive git patches for' idx = 'List of project names that you wish to receive git patches for'
editProfileForm += \ editProfileForm += \
' <br><label class="labels">' + \ ' <br><label class="labels">' + \
translate[idx] + '</label>' translate[idx] + '</label>\n'
editProfileForm += \ editProfileForm += \
' <textarea id="message" name="gitProjects" ' + \ ' <textarea id="message" name="gitProjects" ' + \
'style="height:100px">' + gitProjectsStr + '</textarea>' 'style="height:100px">' + gitProjectsStr + '</textarea>\n'
editProfileForm += \ editProfileForm += \
' <br><b><label class="labels">' + \ ' <br><b><label class="labels">' + \
translate['YouTube Replacement Domain'] + '</label></b>' translate['YouTube Replacement Domain'] + '</label></b>\n'
YTReplacementDomain = getConfigParam(baseDir, "youtubedomain") YTReplacementDomain = getConfigParam(baseDir, "youtubedomain")
if not YTReplacementDomain: if not YTReplacementDomain:
YTReplacementDomain = '' YTReplacementDomain = ''
editProfileForm += \ editProfileForm += \
' <input type="text" name="ytdomain" value="' + \ ' <input type="text" name="ytdomain" value="' + \
YTReplacementDomain + '">' YTReplacementDomain + '">\n'
editProfileForm += ' </div>' editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
editProfileForm += \ editProfileForm += \
' <b><label class="labels">' + \ ' <b><label class="labels">' + \
translate['Skills'] + '</label></b><br>' translate['Skills'] + '</label></b><br>\n'
idx = 'If you want to participate within organizations then you ' + \ idx = 'If you want to participate within organizations then you ' + \
'can indicate some skills that you have and approximate ' + \ 'can indicate some skills that you have and approximate ' + \
'proficiency levels. This helps organizers to construct ' + \ 'proficiency levels. This helps organizers to construct ' + \
'teams with an appropriate combination of skills.' 'teams with an appropriate combination of skills.'
editProfileForm += ' <label class="labels">' + \ editProfileForm += ' <label class="labels">' + \
translate[idx] + '</label>' translate[idx] + '</label>\n'
editProfileForm += skillsStr + themesDropdown + moderatorsStr editProfileForm += skillsStr + themesDropdown + moderatorsStr
editProfileForm += ' </div>' + instanceStr editProfileForm += ' </div>\n' + instanceStr
editProfileForm += ' <div class="container">' editProfileForm += ' <div class="container">\n'
editProfileForm += ' <b><label class="labels">' + \ editProfileForm += ' <b><label class="labels">' + \
translate['Danger Zone'] + '</label></b><br>' translate['Danger Zone'] + '</label></b><br>\n'
editProfileForm += \ editProfileForm += \
' <input type="checkbox" class=dangercheckbox" ' + \ ' <input type="checkbox" class=dangercheckbox" ' + \
'name="deactivateThisAccount"> ' + \ 'name="deactivateThisAccount"> ' + \
translate['Deactivate this account'] + '<br>' translate['Deactivate this account'] + '<br>\n'
editProfileForm += ' </div>' editProfileForm += ' </div>\n'
editProfileForm += ' </div>' editProfileForm += ' </div>\n'
editProfileForm += '</form>' editProfileForm += '</form>\n'
editProfileForm += htmlFooter() editProfileForm += htmlFooter()
return editProfileForm return editProfileForm
@ -2206,7 +2214,7 @@ def htmlNewPost(mediaInstance: bool, translate: {},
newPostForm += \ newPostForm += \
' <a href="' + pathBase + \ ' <a href="' + pathBase + \
'/inbox"><button class="cancelbtn">' + \ '/inbox"><button class="cancelbtn">' + \
translate['Cancel'] + '</button></a>\n' translate['Go Back'] + '</button></a>\n'
newPostForm += \ newPostForm += \
' <input type="submit" name="submitPost" value="' + \ ' <input type="submit" name="submitPost" value="' + \
translate['Submit'] + '">\n' translate['Submit'] + '">\n'
@ -2289,6 +2297,8 @@ def htmlHeader(cssFilename: str, css: str, lang='en') -> str:
htmlStr += ' <link rel="preload" as="font" type="' + \ htmlStr += ' <link rel="preload" as="font" type="' + \
fontFormat + '" href="' + fontName + '" crossorigin>\n' fontFormat + '" href="' + fontName + '" crossorigin>\n'
htmlStr += ' <style>\n' + css + '</style>\n' htmlStr += ' <style>\n' + css + '</style>\n'
htmlStr += ' <link rel="manifest" href="/manifest.json">\n'
htmlStr += ' <meta name="theme-color" content="grey">\n'
htmlStr += ' </head>\n' htmlStr += ' </head>\n'
htmlStr += ' <body>\n' htmlStr += ' <body>\n'
return htmlStr return htmlStr
@ -2671,7 +2681,7 @@ def htmlProfile(defaultTimeline: str,
ssbAddress + '</label></p>\n' ssbAddress + '</label></p>\n'
if toxAddress: if toxAddress:
donateSection += \ donateSection += \
'<p>Tox: <label class="ssbaddr">' + \ '<p>Tox: <label class="toxaddr">' + \
toxAddress + '</label></p>\n' toxAddress + '</label></p>\n'
if PGPfingerprint: if PGPfingerprint:
donateSection += \ donateSection += \
@ -2791,30 +2801,32 @@ def htmlProfile(defaultTimeline: str,
profileStr = \ profileStr = \
linkToTimelineStart + profileHeaderStr + \ linkToTimelineStart + profileHeaderStr + \
linkToTimelineEnd + donateSection linkToTimelineEnd + donateSection
profileStr += '<div class="container">\n' profileStr += '<div class="container" id="buttonheader">\n'
profileStr += ' <center>' profileStr += ' <center>'
profileStr += \ profileStr += \
' <a href="' + usersPath + '"><button class="' + postsButton + \ ' <a href="' + usersPath + '#buttonheader"><button class="' + \
'"><span>' + translate['Posts'] + ' </span></button></a>' postsButton + '"><span>' + translate['Posts'] + \
profileStr += \
' <a href="' + usersPath + '/following"><button class="' + \
followingButton + '"><span>' + translate['Following'] + \
' </span></button></a>' ' </span></button></a>'
profileStr += \ profileStr += \
' <a href="' + usersPath + '/followers"><button class="' + \ ' <a href="' + usersPath + '/following#buttonheader">' + \
followersButton + '"><span>' + translate['Followers'] + \ '<button class="' + followingButton + '"><span>' + \
translate['Following'] + ' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/followers#buttonheader">' + \
'<button class="' + followersButton + \
'"><span>' + translate['Followers'] + ' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/roles#buttonheader">' + \
'<button class="' + rolesButton + '"><span>' + translate['Roles'] + \
' </span></button></a>' ' </span></button></a>'
profileStr += \ profileStr += \
' <a href="' + usersPath + '/roles"><button class="' + \ ' <a href="' + usersPath + '/skills#buttonheader">' + \
rolesButton + '"><span>' + translate['Roles'] + ' </span></button></a>' '<button class="' + skillsButton + '"><span>' + \
translate['Skills'] + ' </span></button></a>'
profileStr += \ profileStr += \
' <a href="' + usersPath + '/skills"><button class="' + \ ' <a href="' + usersPath + '/shares#buttonheader">' + \
skillsButton + '"><span>' + translate['Skills'] + \ '<button class="' + sharesButton + '"><span>' + \
' </span></button></a>' translate['Shares'] + ' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/shares"><button class="' + \
sharesButton + '"><span>' + translate['Shares'] + \
' </span></button></a>'
profileStr += editProfileStr + logoutStr profileStr += editProfileStr + logoutStr
profileStr += ' </center>' profileStr += ' </center>'
profileStr += '</div>' profileStr += '</div>'
@ -3708,7 +3720,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
nickname, domain, nickname, domain,
displayName, False) displayName, False)
avatarLink = ' <a href="' + postActor + '">' avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
avatarLink += \ avatarLink += \
' <img loading="lazy" src="' + avatarUrl + '" title="' + \ ' <img loading="lazy" src="' + avatarUrl + '" title="' + \
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>' translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>'
@ -3716,14 +3728,15 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
if showAvatarOptions and \ if showAvatarOptions and \
fullDomain + '/users/' + nickname not in postActor: fullDomain + '/users/' + nickname not in postActor:
avatarLink = \ avatarLink = \
' <a href="/users/' + nickname + '?options=' + postActor + \ ' <a class="imageAnchor" href="/users/' + \
nickname + '?options=' + postActor + \
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + '">\n' ';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + '">\n'
avatarLink += \ avatarLink += \
' <img loading="lazy" title="' + \ ' <img loading="lazy" title="' + \
translate['Show options for this person'] + \ translate['Show options for this person'] + \
'" src="' + avatarUrl + '" ' + avatarPosition + '/></a>\n' '" src="' + avatarUrl + '" ' + avatarPosition + '/></a>\n'
avatarImageInPost = \ avatarImageInPost = \
' <div class="timeline-avatar">' + avatarLink + '</div>\n' ' <div class="timeline-avatar">' + avatarLink.strip() + '</div>\n'
# don't create new html within the bookmarks timeline # don't create new html within the bookmarks timeline
# it should already have been created for the inbox # it should already have been created for the inbox
@ -3788,7 +3801,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
nickname, domain, nickname, domain,
displayName, False) displayName, False)
titleStr += \ titleStr += \
'<a href="/users/' + nickname + '?options=' + postActor + \ '<a class="imageAnchor" href="/users/' + \
nickname + '?options=' + postActor + \
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \ ';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \
'">' + displayName + '</a>\n' '">' + displayName + '</a>\n'
else: else:
@ -3802,7 +3816,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
# pprint(postJsonObject) # pprint(postJsonObject)
print('ERROR: no actorDomain') print('ERROR: no actorDomain')
titleStr += \ titleStr += \
'<a href="/users/' + nickname + '?options=' + postActor + \ '<a class="imageAnchor" href="/users/' + \
nickname + '?options=' + postActor + \
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \ ';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \
'">@' + actorNickname + '@' + actorDomain + '</a>\n' '">@' + actorNickname + '@' + actorDomain + '</a>\n'
@ -3833,19 +3848,20 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
replyStr = '' replyStr = ''
if isPublicRepeat: if isPublicRepeat:
replyStr += \ replyStr += \
'<a href="/users/' + nickname + '?replyto=' + replyToLink + \ '<a class="imageAnchor" href="/users/' + \
nickname + '?replyto=' + replyToLink + \
'?actor=' + postJsonObject['actor'] + \ '?actor=' + postJsonObject['actor'] + \
'" title="' + translate['Reply to this post'] + '">\n' '" title="' + translate['Reply to this post'] + '">\n'
else: else:
if isDM(postJsonObject): if isDM(postJsonObject):
replyStr += \ replyStr += \
'<a href="/users/' + nickname + \ '<a class="imageAnchor" href="/users/' + nickname + \
'?replydm=' + replyToLink + \ '?replydm=' + replyToLink + \
'?actor=' + postJsonObject['actor'] + \ '?actor=' + postJsonObject['actor'] + \
'" title="' + translate['Reply to this post'] + '">\n' '" title="' + translate['Reply to this post'] + '">\n'
else: else:
replyStr += \ replyStr += \
'<a href="/users/' + nickname + \ '<a class="imageAnchor" href="/users/' + nickname + \
'?replyfollowers=' + replyToLink + \ '?replyfollowers=' + replyToLink + \
'?actor=' + postJsonObject['actor'] + \ '?actor=' + postJsonObject['actor'] + \
'" title="' + translate['Reply to this post'] + '">\n' '" title="' + translate['Reply to this post'] + '">\n'
@ -3861,7 +3877,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
if isBlogPost(postJsonObject): if isBlogPost(postJsonObject):
if '/statuses/' in postJsonObject['object']['id']: if '/statuses/' in postJsonObject['object']['id']:
editStr += \ editStr += \
'<a href="/users/' + nickname + \ '<a class="imageAnchor" href="/users/' + nickname + \
'/tlblogs?editblogpost=' + \ '/tlblogs?editblogpost=' + \
postJsonObject['object']['id'].split('/statuses/')[1] + \ postJsonObject['object']['id'].split('/statuses/')[1] + \
'?actor=' + actorNickname + \ '?actor=' + actorNickname + \
@ -3885,7 +3901,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
announceLink = 'unrepeatprivate' announceLink = 'unrepeatprivate'
announceTitle = translate['Undo the repeat'] announceTitle = translate['Undo the repeat']
announceStr = \ announceStr = \
'<a href="/users/' + nickname + '?' + announceLink + \ '<a class="imageAnchor" href="/users/' + \
nickname + '?' + announceLink + \
'=' + postJsonObject['object']['id'] + pageNumberParam + \ '=' + postJsonObject['object']['id'] + pageNumberParam + \
'?actor=' + postJsonObject['actor'] + \ '?actor=' + postJsonObject['actor'] + \
'?bm=' + timelinePostBookmark + \ '?bm=' + timelinePostBookmark + \
@ -3913,7 +3930,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
likeLink = 'unlike' likeLink = 'unlike'
likeTitle = translate['Undo the like'] likeTitle = translate['Undo the like']
likeStr = \ likeStr = \
'<a href="/users/' + nickname + '?' + \ '<a class="imageAnchor" href="/users/' + nickname + '?' + \
likeLink + '=' + postJsonObject['object']['id'] + \ likeLink + '=' + postJsonObject['object']['id'] + \
pageNumberParam + \ pageNumberParam + \
'?actor=' + postJsonObject['actor'] + \ '?actor=' + postJsonObject['actor'] + \
@ -3935,7 +3952,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
bookmarkLink = 'unbookmark' bookmarkLink = 'unbookmark'
bookmarkTitle = translate['Undo the bookmark'] bookmarkTitle = translate['Undo the bookmark']
bookmarkStr = \ bookmarkStr = \
'<a href="/users/' + nickname + '?' + \ '<a class="imageAnchor" href="/users/' + nickname + '?' + \
bookmarkLink + '=' + postJsonObject['object']['id'] + \ bookmarkLink + '=' + postJsonObject['object']['id'] + \
pageNumberParam + \ pageNumberParam + \
'?actor=' + postJsonObject['actor'] + \ '?actor=' + postJsonObject['actor'] + \
@ -3955,7 +3972,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
messageId.startswith(postActor))): messageId.startswith(postActor))):
if '/users/' + nickname + '/' in messageId: if '/users/' + nickname + '/' in messageId:
deleteStr = \ deleteStr = \
'<a href="/users/' + nickname + \ '<a class="imageAnchor" href="/users/' + nickname + \
'?delete=' + messageId + pageNumberParam + \ '?delete=' + messageId + pageNumberParam + \
'" title="' + translate['Delete this post'] + '">\n' '" title="' + translate['Delete this post'] + '">\n'
deleteStr += \ deleteStr += \
@ -3965,7 +3982,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
else: else:
if not isMuted: if not isMuted:
muteStr = \ muteStr = \
'<a href="/users/' + nickname + \ '<a class="imageAnchor" href="/users/' + nickname + \
'?mute=' + messageId + pageNumberParam + '?tl=' + boxName + \ '?mute=' + messageId + pageNumberParam + '?tl=' + boxName + \
'?bm=' + timelinePostBookmark + \ '?bm=' + timelinePostBookmark + \
'" title="' + translate['Mute this post'] + '">\n' '" title="' + translate['Mute this post'] + '">\n'
@ -3976,7 +3993,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
'" src="/' + iconsDir + '/mute.png"/></a>\n' '" src="/' + iconsDir + '/mute.png"/></a>\n'
else: else:
muteStr = \ muteStr = \
'<a href="/users/' + nickname + '?unmute=' + messageId + \ '<a class="imageAnchor" href="/users/' + \
nickname + '?unmute=' + messageId + \
pageNumberParam + '?tl=' + boxName + '?bm=' + \ pageNumberParam + '?tl=' + boxName + '?bm=' + \
timelinePostBookmark + '" title="' + \ timelinePostBookmark + '" title="' + \
translate['Undo mute'] + '">\n' translate['Undo mute'] + '">\n'
@ -4034,7 +4052,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
idx = 'Show options for this person' idx = 'Show options for this person'
replyAvatarImageInPost = \ replyAvatarImageInPost = \
'<div class="timeline-avatar-reply">\n' \ '<div class="timeline-avatar-reply">\n' \
'<a href="/users/' + nickname + \ '<a class="imageAnchor" ' + \
'href="/users/' + nickname + \
'?options=' + \ '?options=' + \
announceActor + ';' + str(pageNumber) + \ announceActor + ';' + str(pageNumber) + \
';' + announceAvatarUrl + \ ';' + announceAvatarUrl + \
@ -4128,7 +4147,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
'<div class=' + \ '<div class=' + \
'"timeline-avatar-reply">\n' '"timeline-avatar-reply">\n'
replyAvatarImageInPost += \ replyAvatarImageInPost += \
'<a href="/users/' + nickname + \ '<a class="imageAnchor" ' + \
'href="/users/' + nickname + \
'?options=' + replyActor + \ '?options=' + replyActor + \
';' + str(pageNumber) + ';' + \ ';' + str(pageNumber) + ';' + \
replyAvatarUrl + \ replyAvatarUrl + \
@ -4190,7 +4210,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
attachmentStr, galleryStr = \ attachmentStr, galleryStr = \
getPostAttachmentsAsHtml(postJsonObject, boxName, translate, getPostAttachmentsAsHtml(postJsonObject, boxName, translate,
isMuted, avatarLink, isMuted, avatarLink.strip(),
replyStr, announceStr, likeStr, replyStr, announceStr, likeStr,
bookmarkStr, deleteStr, muteStr) bookmarkStr, deleteStr, muteStr)
@ -4530,7 +4550,8 @@ def htmlTimeline(defaultTimeline: str,
# show follow approvals icon # show follow approvals icon
followApprovals = \ followApprovals = \
'<a href="' + usersPath + \ '<a href="' + usersPath + \
'/followers"><img loading="lazy" ' + \ '/followers#buttonheader">' + \
'<img loading="lazy" ' + \
'class="timelineicon" alt="' + \ 'class="timelineicon" alt="' + \
translate['Approve follow requests'] + \ translate['Approve follow requests'] + \
'" title="' + translate['Approve follow requests'] + \ '" title="' + translate['Approve follow requests'] + \
@ -4566,7 +4587,7 @@ def htmlTimeline(defaultTimeline: str,
if boxName != 'tlblogs': if boxName != 'tlblogs':
if not manuallyApproveFollowers: if not manuallyApproveFollowers:
newPostButtonStr = \ newPostButtonStr = \
'<a href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newpost"><img loading="lazy" src="/' + \ '/newpost"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
translate['Create a new post'] + '" alt="| ' + \ translate['Create a new post'] + '" alt="| ' + \
@ -4574,7 +4595,7 @@ def htmlTimeline(defaultTimeline: str,
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr = \
'<a href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newfollowers"><img loading="lazy" src="/' + \ '/newfollowers"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
translate['Create a new post'] + \ translate['Create a new post'] + \
@ -4582,7 +4603,7 @@ def htmlTimeline(defaultTimeline: str,
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr = \
'<a href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newblog"><img loading="lazy" src="/' + \ '/newblog"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
translate['Create a new post'] + '" alt="| ' + \ translate['Create a new post'] + '" alt="| ' + \
@ -4590,7 +4611,7 @@ def htmlTimeline(defaultTimeline: str,
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr = \
'<a href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newdm"><img loading="lazy" src="/' + \ '/newdm"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
translate['Create a new DM'] + \ translate['Create a new DM'] + \
@ -4684,7 +4705,7 @@ def htmlTimeline(defaultTimeline: str,
sharesButtonStr + bookmarksButtonStr + \ sharesButtonStr + bookmarksButtonStr + \
moderationButtonStr + newPostButtonStr moderationButtonStr + newPostButtonStr
tlStr += \ tlStr += \
' <a href="' + usersPath + \ ' <a class="imageAnchor" href="' + usersPath + \
'/search"><img loading="lazy" src="/' + \ '/search"><img loading="lazy" src="/' + \
iconsDir + '/search.png" title="' + \ iconsDir + '/search.png" title="' + \
translate['Search and follow'] + '" alt="| ' + \ translate['Search and follow'] + '" alt="| ' + \
@ -4695,13 +4716,13 @@ def htmlTimeline(defaultTimeline: str,
# indicate that the calendar icon is highlighted # indicate that the calendar icon is highlighted
calendarAltText = '*' + calendarAltText + '*' calendarAltText = '*' + calendarAltText + '*'
tlStr += \ tlStr += \
' <a href="' + usersPath + calendarPath + \ ' <a class="imageAnchor" href="' + usersPath + calendarPath + \
'"><img loading="lazy" src="/' + iconsDir + '/' + \ '"><img loading="lazy" src="/' + iconsDir + '/' + \
calendarImage + '" title="' + translate['Calendar'] + \ calendarImage + '" title="' + translate['Calendar'] + \
'" alt="| ' + calendarAltText + '" class="timelineicon"/></a>\n' '" alt="| ' + calendarAltText + '" class="timelineicon"/></a>\n'
tlStr += \ tlStr += \
' <a href="' + usersPath + '/minimal' + \ ' <a class="imageAnchor" href="' + usersPath + '/minimal' + \
'"><img loading="lazy" src="/' + iconsDir + \ '"><img loading="lazy" src="/' + iconsDir + \
'/showhide.png" title="' + translate['Show/Hide Buttons'] + \ '/showhide.png" title="' + translate['Show/Hide Buttons'] + \
'" alt="| ' + translate['Show/Hide Buttons'] + \ '" alt="| ' + translate['Show/Hide Buttons'] + \
@ -5622,7 +5643,8 @@ def htmlPersonOptions(translate: {}, baseDir: str,
translate['Petname'] + ': ' + \ translate['Petname'] + ': ' + \
'<input type="text" name="optionpetname" value="' + \ '<input type="text" name="optionpetname" value="' + \
petname + '">\n' \ petname + '">\n' \
'<button type="submit" class="button" name="submitPetname">' + \ '<button type="submit" class="buttonsmall" ' + \
'name="submitPetname">' + \
translate['Submit'] + '</button><br>\n' translate['Submit'] + '</button><br>\n'
if isFollowingActor(baseDir, nickname, domain, optionsActor): if isFollowingActor(baseDir, nickname, domain, optionsActor):
@ -5632,7 +5654,7 @@ def htmlPersonOptions(translate: {}, baseDir: str,
'<input type="checkbox" ' + \ '<input type="checkbox" ' + \
'class="profilecheckbox" name="onCalendar" checked> ' + \ 'class="profilecheckbox" name="onCalendar" checked> ' + \
translate['Receive calendar events from this account'] + \ translate['Receive calendar events from this account'] + \
'<button type="submit" class="button" ' + \ '<button type="submit" class="buttonsmall" ' + \
'name="submitOnCalendar">' + \ 'name="submitOnCalendar">' + \
translate['Submit'] + '</button><br>\n' translate['Submit'] + '</button><br>\n'
else: else:
@ -5640,11 +5662,14 @@ def htmlPersonOptions(translate: {}, baseDir: str,
'<input type="checkbox" ' + \ '<input type="checkbox" ' + \
'class="profilecheckbox" name="onCalendar"> ' + \ 'class="profilecheckbox" name="onCalendar"> ' + \
translate['Receive calendar events from this account'] + \ translate['Receive calendar events from this account'] + \
'<button type="submit" class="button" ' + \ '<button type="submit" class="buttonsmall" ' + \
'name="submitOnCalendar">' + \ 'name="submitOnCalendar">' + \
translate['Submit'] + '</button><br>\n' translate['Submit'] + '</button><br>\n'
optionsStr += optionsLinkStr optionsStr += optionsLinkStr
optionsStr += \
' <a href="/"><button type="button" class="button" ' + \
'name="submitBack">' + translate['Go Back'] + '</button></a>\n'
optionsStr += \ optionsStr += \
' <button type="submit" class="button" name="submitView">' + \ ' <button type="submit" class="button" name="submitView">' + \
translate['View'] + '</button>\n' translate['View'] + '</button>\n'
@ -5675,7 +5700,7 @@ def htmlPersonOptions(translate: {}, baseDir: str,
optionsStr += \ optionsStr += \
' <br><br>' + translate['Notes'] + ': \n' ' <br><br>' + translate['Notes'] + ': \n'
optionsStr += '<button type="submit" class="button" ' + \ optionsStr += '<button type="submit" class="buttonsmall" ' + \
'name="submitPersonNotes">' + \ 'name="submitPersonNotes">' + \
translate['Submit'] + '</button><br>\n' translate['Submit'] + '</button><br>\n'
optionsStr += \ optionsStr += \
@ -6244,10 +6269,10 @@ def htmlSearch(translate: {},
followStr += \ followStr += \
' <input type="hidden" name="actor" value="' + actor + '">\n' ' <input type="hidden" name="actor" value="' + actor + '">\n'
followStr += ' <input type="text" name="searchtext" autofocus><br>\n' followStr += ' <input type="text" name="searchtext" autofocus><br>\n'
followStr += ' <a href="/"><button type="button" class="button" ' + \
'name="submitBack">' + translate['Go Back'] + '</button></a>\n'
followStr += ' <button type="submit" class="button" ' + \ followStr += ' <button type="submit" class="button" ' + \
'name="submitSearch">' + translate['Submit'] + '</button>\n' 'name="submitSearch">' + translate['Submit'] + '</button>\n'
followStr += ' <button type="submit" class="button" ' + \
'name="submitBack">' + translate['Go Back'] + '</button>\n'
followStr += ' </form>\n' followStr += ' </form>\n'
followStr += ' <p class="hashtagswarm">' + \ followStr += ' <p class="hashtagswarm">' + \
htmlHashTagSwarm(baseDir, actor) + '</p>\n' htmlHashTagSwarm(baseDir, actor) + '</p>\n'
@ -6269,6 +6294,7 @@ def htmlProfileAfterSearch(recentPostsCache: {}, maxRecentPosts: int,
"""Show a profile page after a search for a fediverse address """Show a profile page after a search for a fediverse address
""" """
if '/users/' in profileHandle or \ if '/users/' in profileHandle or \
'/accounts/' in profileHandle or \
'/channel/' in profileHandle or \ '/channel/' in profileHandle or \
'/profile/' in profileHandle or \ '/profile/' in profileHandle or \
'/@' in profileHandle: '/@' in profileHandle:
@ -6440,15 +6466,15 @@ def htmlProfileAfterSearch(recentPostsCache: {}, maxRecentPosts: int,
profileStr += \ profileStr += \
' <input type="hidden" name="actor" value="' + \ ' <input type="hidden" name="actor" value="' + \
personUrl + '">\n' personUrl + '">\n'
profileStr += \
' <a href="' + backUrl + '"><button class="button">' + \
translate['Go Back'] + '</button></a>\n'
profileStr += \ profileStr += \
' <button type="submit" class="button" name="submitYes">' + \ ' <button type="submit" class="button" name="submitYes">' + \
translate['Follow'] + '</button>\n' translate['Follow'] + '</button>\n'
profileStr += \ profileStr += \
' <button type="submit" class="button" name="submitView">' + \ ' <button type="submit" class="button" name="submitView">' + \
translate['View'] + '</button>\n' translate['View'] + '</button>\n'
profileStr += \
' <a href="' + backUrl + '"><button class="button">' + \
translate['Go Back'] + '</button></a>\n'
profileStr += ' </center>\n' profileStr += ' </center>\n'
profileStr += ' </form>\n' profileStr += ' </form>\n'
profileStr += '</div>\n' profileStr += '</div>\n'