Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
|
|
@ -202,6 +202,7 @@ def receiveAcceptReject(session, baseDir: str,
|
|||
print('DEBUG: ' + messageJson['type'] + ' has no actor')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
|
|||
|
|
@ -81,6 +81,12 @@ def announcedByPerson(postJsonObject: {}, nickname: str, domain: str) -> bool:
|
|||
# not to be confused with shared items
|
||||
if not postJsonObject['object'].get('shares'):
|
||||
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
|
||||
for item in postJsonObject['object']['shares']['items']:
|
||||
if item['actor'].endswith(actorMatch):
|
||||
|
|
@ -141,6 +147,7 @@ def createAnnounce(session, baseDir: str, federationList: [],
|
|||
announceDomain = None
|
||||
announcePort = None
|
||||
if '/users/' in objectUrl or \
|
||||
'/accounts/' in objectUrl or \
|
||||
'/channel/' in objectUrl or \
|
||||
'/profile/' in objectUrl:
|
||||
announceNickname = getNicknameFromActor(objectUrl)
|
||||
|
|
@ -257,6 +264,7 @@ def undoAnnounce(session, baseDir: str, federationList: [],
|
|||
announceDomain = None
|
||||
announcePort = None
|
||||
if '/users/' in objectUrl or \
|
||||
'/accounts/' in objectUrl or \
|
||||
'/channel/' in objectUrl or \
|
||||
'/profile/' in objectUrl:
|
||||
announceNickname = getNicknameFromActor(objectUrl)
|
||||
|
|
|
|||
1
auth.py
|
|
@ -57,6 +57,7 @@ def authorizeBasic(baseDir: str, path: str, authHeader: str,
|
|||
'contain a space character')
|
||||
return False
|
||||
if '/users/' not in path and \
|
||||
'/accounts/' not in path and \
|
||||
'/channel/' not in path and \
|
||||
'/profile/' not in path:
|
||||
if debug:
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ def outboxBlock(baseDir: str, httpPrefix: str,
|
|||
print('DEBUG: c2s block object is not a status')
|
||||
return
|
||||
if '/users/' not in messageId and \
|
||||
'/accounts/' not in messageId and \
|
||||
'/channel/' not in messageId and \
|
||||
'/profile/' not in messageId:
|
||||
if debug:
|
||||
|
|
@ -298,6 +299,7 @@ def outboxUndoBlock(baseDir: str, httpPrefix: str,
|
|||
print('DEBUG: c2s undo block object is not a status')
|
||||
return
|
||||
if '/users/' not in messageId and \
|
||||
'/accounts/' not in messageId and \
|
||||
'/channel/' not in messageId and \
|
||||
'/profile/' not in messageId:
|
||||
if debug:
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ def bookmark(recentPostsCache: {},
|
|||
bookmarkedPostDomain, bookmarkedPostPort = getDomainFromActor(acBm)
|
||||
else:
|
||||
if '/users/' in objectUrl or \
|
||||
'/accounts/' in objectUrl or \
|
||||
'/channel/' in objectUrl or \
|
||||
'/profile/' in objectUrl:
|
||||
ou = objectUrl
|
||||
|
|
@ -362,6 +363,7 @@ def undoBookmark(recentPostsCache: {},
|
|||
bookmarkedPostDomain, bookmarkedPostPort = getDomainFromActor(acBm)
|
||||
else:
|
||||
if '/users/' in objectUrl or \
|
||||
'/accounts/' in objectUrl or \
|
||||
'/channel/' in objectUrl or \
|
||||
'/profile/' in objectUrl:
|
||||
ou = objectUrl
|
||||
|
|
|
|||
239
daemon.py
|
|
@ -1245,6 +1245,99 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
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
|
||||
if 'favicon.ico' in self.path:
|
||||
favType = 'image/x-icon'
|
||||
|
|
@ -1262,6 +1355,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.baseDir + '/img/icons/' + favFilename
|
||||
if self._etag_exists(faviconFilename):
|
||||
# The file has not changed
|
||||
if self.server.debug:
|
||||
print('favicon icon has not changed: ' + callingDomain)
|
||||
self._304()
|
||||
return
|
||||
if self.server.iconsCache.get(favFilename):
|
||||
|
|
@ -1271,6 +1366,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
favBinary, cookie,
|
||||
callingDomain)
|
||||
self._write(favBinary)
|
||||
if self.server.debug:
|
||||
print('Sent favicon from cache: ' + callingDomain)
|
||||
return
|
||||
else:
|
||||
if os.path.isfile(faviconFilename):
|
||||
|
|
@ -1282,7 +1379,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
callingDomain)
|
||||
self._write(favBinary)
|
||||
self.server.iconsCache[favFilename] = favBinary
|
||||
if self.server.debug:
|
||||
print('Sent favicon from file: ' + callingDomain)
|
||||
return
|
||||
if self.server.debug:
|
||||
print('favicon not sent: ' + callingDomain)
|
||||
self._404()
|
||||
return
|
||||
|
||||
|
|
@ -1355,6 +1456,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
fontBinary, cookie,
|
||||
callingDomain)
|
||||
self._write(fontBinary)
|
||||
if self.server.debug:
|
||||
print('font sent from cache: ' +
|
||||
self.path + ' ' + callingDomain)
|
||||
return
|
||||
else:
|
||||
if os.path.isfile(fontFilename):
|
||||
|
|
@ -1366,7 +1470,12 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
callingDomain)
|
||||
self._write(fontBinary)
|
||||
self.server.fontsCache[fontStr] = fontBinary
|
||||
if self.server.debug:
|
||||
print('font sent from file: ' +
|
||||
self.path + ' ' + callingDomain)
|
||||
return
|
||||
if self.server.debug:
|
||||
print('font not found: ' + self.path + ' ' + callingDomain)
|
||||
self._404()
|
||||
return
|
||||
|
||||
|
|
@ -1421,7 +1530,13 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers('text/xml', len(msg),
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
if self.server.debug:
|
||||
print('Sent rss2 feed: ' +
|
||||
self.path + ' ' + callingDomain)
|
||||
return
|
||||
if self.server.debug:
|
||||
print('Failed to get rss2 feed: ' +
|
||||
self.path + ' ' + callingDomain)
|
||||
self._404()
|
||||
return
|
||||
|
||||
|
|
@ -1459,7 +1574,13 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._set_headers('text/plain; charset=utf-8',
|
||||
len(msg), cookie, callingDomain)
|
||||
self._write(msg)
|
||||
if self.server.debug:
|
||||
print('Sent rss3 feed: ' +
|
||||
self.path + ' ' + callingDomain)
|
||||
return
|
||||
if self.server.debug:
|
||||
print('Failed to get rss3 feed: ' +
|
||||
self.path + ' ' + callingDomain)
|
||||
self._404()
|
||||
return
|
||||
|
||||
|
|
@ -1830,15 +1951,19 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings, 15)
|
||||
|
||||
# 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':
|
||||
# manifest images used to create a home screen icon
|
||||
# when selecting "add to home screen" in browsers
|
||||
# which support progressive web apps
|
||||
if self.path == '/logo72.png' or \
|
||||
self.path == '/logo96.png' or \
|
||||
self.path == '/logo128.png' or \
|
||||
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 = \
|
||||
self.server.baseDir + '/accounts' + self.path
|
||||
self.server.baseDir + '/img' + self.path
|
||||
if os.path.isfile(mediaFilename):
|
||||
if self._etag_exists(mediaFilename):
|
||||
# The file has not changed
|
||||
|
|
@ -1866,6 +1991,75 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._404()
|
||||
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)
|
||||
|
||||
# QR code for account handle
|
||||
|
|
@ -1875,11 +2069,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
savePersonQrcode(self.server.baseDir,
|
||||
nickname, self.server.domain,
|
||||
self.server.port)
|
||||
mediaFilename = \
|
||||
qrFilename = \
|
||||
self.server.baseDir + '/accounts/' + \
|
||||
nickname + '@' + self.server.domain + '/qrcode.png'
|
||||
if os.path.isfile(mediaFilename):
|
||||
if self._etag_exists(mediaFilename):
|
||||
if os.path.isfile(qrFilename):
|
||||
if self._etag_exists(qrFilename):
|
||||
# The file has not changed
|
||||
self._304()
|
||||
return
|
||||
|
|
@ -1888,7 +2082,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mediaBinary = None
|
||||
while tries < 5:
|
||||
try:
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
with open(qrFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
break
|
||||
except Exception as e:
|
||||
|
|
@ -1896,7 +2090,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(mediaFilename, 'image/png',
|
||||
self._set_headers_etag(qrFilename, 'image/png',
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
|
@ -1908,11 +2102,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if '/users/' in self.path and \
|
||||
self.path.endswith('/search_banner.png'):
|
||||
nickname = getNicknameFromActor(self.path)
|
||||
mediaFilename = \
|
||||
bannerFilename = \
|
||||
self.server.baseDir + '/accounts/' + \
|
||||
nickname + '@' + self.server.domain + '/search_banner.png'
|
||||
if os.path.isfile(mediaFilename):
|
||||
if self._etag_exists(mediaFilename):
|
||||
if os.path.isfile(bannerFilename):
|
||||
if self._etag_exists(bannerFilename):
|
||||
# The file has not changed
|
||||
self._304()
|
||||
return
|
||||
|
|
@ -1921,7 +2115,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
mediaBinary = None
|
||||
while tries < 5:
|
||||
try:
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
with open(bannerFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
break
|
||||
except Exception as e:
|
||||
|
|
@ -1929,7 +2123,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(mediaFilename, 'image/png',
|
||||
self._set_headers_etag(bannerFilename, 'image/png',
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
|
@ -5914,7 +6108,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return
|
||||
self._400()
|
||||
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
|
||||
if not self._cryptoAPIQuery():
|
||||
self._400()
|
||||
|
|
@ -7232,7 +7426,12 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._write(msg)
|
||||
self.server.POSTbusy = False
|
||||
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
|
||||
nickname = getNicknameFromActor(actorStr)
|
||||
if not self.server.session:
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ def createDelete(session, baseDir: str, federationList: [],
|
|||
deleteDomain = None
|
||||
deletePort = None
|
||||
if '/users/' in objectUrl or \
|
||||
'/accounts/' in objectUrl or \
|
||||
'/channel/' in objectUrl or \
|
||||
'/profile/' in objectUrl:
|
||||
deleteNickname = getNicknameFromActor(objectUrl)
|
||||
|
|
@ -262,6 +263,7 @@ def outboxDelete(baseDir: str, httpPrefix: str,
|
|||
print('DEBUG: c2s delete object is not a status')
|
||||
return
|
||||
if '/users/' not in messageId and \
|
||||
'/accounts/' not in messageId and \
|
||||
'/channel/' not in messageId and \
|
||||
'/profile/' not in messageId:
|
||||
if debug:
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
--gallery-font-size-mobile: 35px;
|
||||
--button-corner-radius: 15px;
|
||||
--timeline-border-radius: 30px;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -80,6 +81,10 @@ a:link {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.cwText {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
--event-foreground:white;
|
||||
--title-text: #282c37;
|
||||
--title-background: #ccc;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -67,6 +68,10 @@ a:link {
|
|||
margin: -1rem;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.calendar__day__header,
|
||||
.calendar__day__cell {
|
||||
border: 2px solid var(--lines-color);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
--follow-text-size1: 24px;
|
||||
--follow-text-size2: 40px;
|
||||
--follow-text-entry-width: 90%;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -67,6 +68,10 @@ a:link {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.searchBanner {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("search_banner.png");
|
||||
background-position: center;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
--button-background: #999;
|
||||
--button-selected: #666;
|
||||
--form-border-radius: 30px;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -63,6 +64,10 @@ a:link {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
form {
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--form-border-radius);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@
|
|||
--text-entry-background: #111;
|
||||
--time-color: #aaa;
|
||||
--button-text: #FFFFFF;
|
||||
--button-small-text: #FFFFFF;
|
||||
--button-background: #999;
|
||||
--button-small-background: #999;
|
||||
--button-selected: #666;
|
||||
--hashtag-margin: 2%;
|
||||
--hashtag-vertical-spacing1: 50px;
|
||||
|
|
@ -30,6 +32,7 @@
|
|||
--follow-text-size1: 24px;
|
||||
--follow-text-size2: 40px;
|
||||
--follow-text-entry-width: 90%;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -72,6 +75,10 @@ a:link {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.follow {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
|
@ -112,13 +119,14 @@ a:link {
|
|||
width: 15%;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 400px) {
|
||||
.followText {
|
||||
font-size: var(--follow-text-size1);
|
||||
}
|
||||
|
|
@ -140,7 +148,22 @@ textarea {
|
|||
text-align: center;
|
||||
padding: 10px;
|
||||
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;
|
||||
min-width: 100px;
|
||||
cursor: pointer;
|
||||
|
|
@ -159,6 +182,13 @@ textarea {
|
|||
}
|
||||
|
||||
@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 {
|
||||
font-size: var(--follow-text-size2);
|
||||
}
|
||||
|
|
@ -180,7 +210,22 @@ textarea {
|
|||
text-align: center;
|
||||
padding: 10px;
|
||||
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;
|
||||
min-width: 100px;
|
||||
cursor: pointer;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
--font-size4: 22px;
|
||||
--font-size5: 20px;
|
||||
--font-size-pgp-key: 16px;
|
||||
--font-size-pgp-key2: 8px;
|
||||
--font-size-tox: 16px;
|
||||
--font-size-tox2: 8px;
|
||||
--text-entry-foreground: #ccc;
|
||||
--text-entry-background: #111;
|
||||
--time-color: #aaa;
|
||||
|
|
@ -51,6 +54,7 @@
|
|||
--timeline-border-radius: 30px;
|
||||
--icons-side: right;
|
||||
--title-color: #999;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -73,6 +77,10 @@ body, html {
|
|||
font-size: var(--font-size);
|
||||
}
|
||||
|
||||
.imageAnchor:focus img{
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--title-color);
|
||||
}
|
||||
|
|
@ -93,6 +101,10 @@ a:link {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.timeline-banner {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png");
|
||||
height: 10%;
|
||||
|
|
@ -116,11 +128,6 @@ a:link {
|
|||
float: right;
|
||||
}
|
||||
|
||||
.ssbaddr {
|
||||
font-size: var(--font-size-pgp-key);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.about {
|
||||
font-size: var(--font-size5);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
|
@ -285,16 +292,19 @@ a:link {
|
|||
width: 90%;
|
||||
}
|
||||
|
||||
.message:focus{
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.message:focus img{
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.gitpatch {
|
||||
width: 90%;
|
||||
font-family: 'monospace';
|
||||
}
|
||||
|
||||
.container p.administeredby {
|
||||
font-size: var(--font-size-header);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.container::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
|
|
@ -346,13 +356,6 @@ a:link {
|
|||
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 {
|
||||
float: none;
|
||||
width: 30px;
|
||||
|
|
@ -857,6 +860,24 @@ aside .toggle-inside li {
|
|||
}
|
||||
|
||||
@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 {
|
||||
font-size: var(--font-size4);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
|
@ -1260,6 +1281,24 @@ aside .toggle-inside li {
|
|||
}
|
||||
|
||||
@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 {
|
||||
font-size: var(--font-size3);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
--follow-text-size1: 24px;
|
||||
--follow-text-size2: 40px;
|
||||
--follow-text-entry-width: 90%;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -67,6 +68,10 @@ a:link {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.searchBanner {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("search_banner.png");
|
||||
background-position: center;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
--button-text: #FFFFFF;
|
||||
--button-background: #999;
|
||||
--button-selected: #666;
|
||||
--focus-color: white;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -57,6 +58,10 @@ a:link {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.screentitle {
|
||||
font-size: 30px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
|
|
|||
11
epicyon.py
|
|
@ -1130,6 +1130,7 @@ if args.actor:
|
|||
args.actor = args.actor.replace(prefix, '')
|
||||
args.actor = args.actor.replace('/@', '/users/')
|
||||
if '/users/' not in args.actor and \
|
||||
'/accounts/' not in args.actor and \
|
||||
'/channel/' not in args.actor and \
|
||||
'/profile/' not in args.actor:
|
||||
print('Expected actor format: ' +
|
||||
|
|
@ -1143,10 +1144,14 @@ if args.actor:
|
|||
nickname = args.actor.split('/profile/')[1]
|
||||
nickname = nickname.replace('\n', '').replace('\r', '')
|
||||
domain = args.actor.split('/profile/')[0]
|
||||
else:
|
||||
elif '/channel/' in args.actor:
|
||||
nickname = args.actor.split('/channel/')[1]
|
||||
nickname = nickname.replace('\n', '').replace('\r', '')
|
||||
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:
|
||||
# format: @nick@domain
|
||||
if '@' not in args.actor:
|
||||
|
|
@ -1198,6 +1203,7 @@ if args.actor:
|
|||
if wfRequest.get('errors'):
|
||||
print('wfRequest error: ' + str(wfRequest['errors']))
|
||||
if '/users/' in args.actor or \
|
||||
'/accounts/' in args.actor or \
|
||||
'/profile/' in args.actor or \
|
||||
'/channel/' in args.actor:
|
||||
personUrl = originalActor
|
||||
|
|
@ -1212,6 +1218,7 @@ if args.actor:
|
|||
personUrl = getUserUrl(wfRequest)
|
||||
if nickname == domain:
|
||||
personUrl = personUrl.replace('/users/', '/actor/')
|
||||
personUrl = personUrl.replace('/accounts/', '/actor/')
|
||||
personUrl = personUrl.replace('/channel/', '/actor/')
|
||||
personUrl = personUrl.replace('/profile/', '/actor/')
|
||||
if not personUrl:
|
||||
|
|
@ -1221,7 +1228,7 @@ if args.actor:
|
|||
asHeader = {
|
||||
'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'
|
||||
asHeader = {
|
||||
'Accept': 'application/ld+json; profile="' + profileStr + '"'
|
||||
|
|
|
|||
|
|
@ -555,6 +555,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
|||
print('DEBUG: follow request has no actor')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -582,6 +583,7 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
|
|||
if not messageJson.get('to'):
|
||||
messageJson['to'] = messageJson['object']
|
||||
if '/users/' not in messageJson['object'] and \
|
||||
'/accounts/' not in messageJson['object'] and \
|
||||
'/channel/' not in messageJson['object'] and \
|
||||
'/profile/' not in messageJson['object']:
|
||||
if debug:
|
||||
|
|
|
|||
128
happening.py
|
|
@ -7,14 +7,142 @@ __email__ = "bob@freedombone.net"
|
|||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
|
||||
from utils import loadJson
|
||||
from utils import saveJson
|
||||
from utils import locatePost
|
||||
from utils import daysInMonth
|
||||
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:
|
||||
"""Is this tag an Event or Place ActivityStreams type?
|
||||
"""
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
BIN
img/mobile.jpg
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 38 KiB |
56
inbox.py
|
|
@ -64,6 +64,7 @@ from git import isGitPatch
|
|||
from git import receiveGitPatch
|
||||
from followingCalendar import receivingCalendarEvents
|
||||
from content import dangerousMarkup
|
||||
from happening import saveEvent
|
||||
|
||||
|
||||
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')
|
||||
return False
|
||||
if '/users/' not in messageJson['object']['actor'] and \
|
||||
'/accounts/' not in messageJson['object']['actor'] and \
|
||||
'/channel/' not in messageJson['object']['actor'] and \
|
||||
'/profile/' not in messageJson['object']['actor']:
|
||||
if debug:
|
||||
|
|
@ -734,6 +736,7 @@ def receiveUndo(session, baseDir: str, httpPrefix: str,
|
|||
print('DEBUG: follow request has no actor')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -790,6 +793,8 @@ def personReceiveUpdate(baseDir: str,
|
|||
actor = updateDomainFull + '/profile/' + updateNickname
|
||||
if actor not in personJson['id']:
|
||||
actor = updateDomainFull + '/channel/' + updateNickname
|
||||
if actor not in personJson['id']:
|
||||
actor = updateDomainFull + '/accounts/' + updateNickname
|
||||
if actor not in personJson['id']:
|
||||
if debug:
|
||||
print('actor: ' + actor)
|
||||
|
|
@ -906,6 +911,7 @@ def receiveUpdate(recentPostsCache: {}, session, baseDir: str,
|
|||
print('DEBUG: ' + messageJson['type'] + ' object has no type')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -1007,6 +1013,7 @@ def receiveLike(recentPostsCache: {},
|
|||
print('DEBUG: ' + messageJson['type'] + ' has no "to" list')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -1075,6 +1082,7 @@ def receiveUndoLike(recentPostsCache: {},
|
|||
' like object is not a string')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -1287,6 +1295,7 @@ def receiveDelete(session, handle: str, isGroup: bool, baseDir: str,
|
|||
print('DEBUG: ' + messageJson['type'] + ' has no "to" list')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -1357,6 +1366,7 @@ def receiveAnnounce(recentPostsCache: {},
|
|||
print('DEBUG: ' + messageJson['type'] + ' has no "to" list')
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -1365,6 +1375,7 @@ def receiveAnnounce(recentPostsCache: {},
|
|||
messageJson['type'])
|
||||
return False
|
||||
if '/users/' not in messageJson['object'] and \
|
||||
'/accounts/' not in messageJson['object'] and \
|
||||
'/channel/' not in messageJson['object'] and \
|
||||
'/profile/' not in messageJson['object']:
|
||||
if debug:
|
||||
|
|
@ -1433,6 +1444,7 @@ def receiveAnnounce(recentPostsCache: {},
|
|||
lookupActor = attrib
|
||||
if lookupActor:
|
||||
if '/users/' in lookupActor or \
|
||||
'/accounts/' in lookupActor or \
|
||||
'/channel/' in lookupActor or \
|
||||
'/profile/' in lookupActor:
|
||||
if '/statuses/' in lookupActor:
|
||||
|
|
@ -1484,6 +1496,7 @@ def receiveUndoAnnounce(recentPostsCache: {},
|
|||
if messageJson['object']['type'] != 'Announce':
|
||||
return False
|
||||
if '/users/' not in messageJson['actor'] and \
|
||||
'/accounts/' not in messageJson['actor'] and \
|
||||
'/channel/' not in messageJson['actor'] and \
|
||||
'/profile/' not in messageJson['actor']:
|
||||
if debug:
|
||||
|
|
@ -1678,6 +1691,7 @@ def obtainAvatarForReplyPost(session, baseDir: str, httpPrefix: str,
|
|||
return
|
||||
|
||||
if not ('/users/' in lookupActor or
|
||||
'/accounts/' in lookupActor or
|
||||
'/channel/' in lookupActor or
|
||||
'/profile/' in lookupActor):
|
||||
return
|
||||
|
|
@ -1957,10 +1971,6 @@ def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
|
|||
if not isinstance(postJsonObject['object']['tag'], list):
|
||||
return
|
||||
|
||||
calendarPath = baseDir + '/accounts/' + handle + '/calendar'
|
||||
if not os.path.isdir(calendarPath):
|
||||
os.mkdir(calendarPath)
|
||||
|
||||
actor = postJsonObject['actor']
|
||||
actorNickname = getNicknameFromActor(actor)
|
||||
actorDomain, actorPort = getDomainFromActor(actor)
|
||||
|
|
@ -1970,6 +1980,11 @@ def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
|
|||
handleNickname, handleDomain,
|
||||
actorNickname, actorDomain):
|
||||
return
|
||||
|
||||
postId = \
|
||||
postJsonObject['id'].replace('/activity', '').replace('/', '#')
|
||||
|
||||
# look for events within the tags list
|
||||
for tagDict in postJsonObject['object']['tag']:
|
||||
if not tagDict.get('type'):
|
||||
continue
|
||||
|
|
@ -1977,38 +1992,7 @@ def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
|
|||
continue
|
||||
if not tagDict.get('startTime'):
|
||||
continue
|
||||
# get the year and month from the event
|
||||
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()
|
||||
saveEvent(baseDir, handle, postId, tagDict)
|
||||
|
||||
|
||||
def inboxUpdateIndex(boxname: str, baseDir: str, handle: str,
|
||||
|
|
|
|||
2
like.py
|
|
@ -90,6 +90,7 @@ def like(recentPostsCache: {},
|
|||
likedPostDomain, likedPostPort = getDomainFromActor(actorLiked)
|
||||
else:
|
||||
if '/users/' in objectUrl or \
|
||||
'/accounts/' in objectUrl or \
|
||||
'/channel/' in objectUrl or \
|
||||
'/profile/' in objectUrl:
|
||||
likedPostNickname = getNicknameFromActor(objectUrl)
|
||||
|
|
@ -193,6 +194,7 @@ def undolike(recentPostsCache: {},
|
|||
likedPostDomain, likedPostPort = getDomainFromActor(actorLiked)
|
||||
else:
|
||||
if '/users/' in objectUrl or \
|
||||
'/accounts/' in objectUrl or \
|
||||
'/channel/' in objectUrl or \
|
||||
'/profile/' in objectUrl:
|
||||
likedPostNickname = getNicknameFromActor(objectUrl)
|
||||
|
|
|
|||
7
posts.py
|
|
@ -136,6 +136,7 @@ def getUserUrl(wfRequest: {}) -> str:
|
|||
if link.get('type') and link.get('href'):
|
||||
if link['type'] == 'application/activity+json':
|
||||
if not ('/users/' in link['href'] or
|
||||
'/accounts/' in link['href'] or
|
||||
'/profile/' in link['href'] or
|
||||
'/channel/' in link['href']):
|
||||
print('Webfinger activity+json contains ' +
|
||||
|
|
@ -207,7 +208,7 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
|
|||
return None, None, None, None, None, None, None, None
|
||||
personJson = getPersonFromCache(baseDir, personUrl, personCache)
|
||||
if not personJson:
|
||||
if '/channel/' in personUrl:
|
||||
if '/channel/' in personUrl or '/accounts/' in personUrl:
|
||||
asHeader = {
|
||||
'Accept': 'application/ld+json; profile="' + profileStr + '"'
|
||||
}
|
||||
|
|
@ -3188,7 +3189,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
|
|||
asHeader = {
|
||||
'Accept': 'application/activity+json; profile="' + profileStr + '"'
|
||||
}
|
||||
if '/channel/' in postJsonObject['actor']:
|
||||
if '/channel/' in postJsonObject['actor'] or \
|
||||
'/accounts/' in postJsonObject['actor']:
|
||||
asHeader = {
|
||||
'Accept': 'application/ld+json; profile="' + profileStr + '"'
|
||||
}
|
||||
|
|
@ -3238,6 +3240,7 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
|
|||
rejectAnnounce(announceFilename)
|
||||
return None
|
||||
if '/users/' not in announcedJson['id'] and \
|
||||
'/accounts/' not in announcedJson['id'] and \
|
||||
'/channel/' not in announcedJson['id'] and \
|
||||
'/profile/' not in announcedJson['id']:
|
||||
rejectAnnounce(announceFilename)
|
||||
|
|
|
|||
24
tests.py
|
|
@ -1585,6 +1585,30 @@ def testActorParsing():
|
|||
nickname = getNicknameFromActor(actor)
|
||||
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'
|
||||
domain, port = getDomainFromActor(actor)
|
||||
assert domain == 'randomain'
|
||||
|
|
|
|||
12
theme.py
|
|
@ -277,6 +277,7 @@ def setThemeNight(baseDir: str):
|
|||
fontStr = \
|
||||
"url('./fonts/solidaric.woff2') format('woff2')"
|
||||
themeParams = {
|
||||
"focus-color": "blue",
|
||||
"font-size-button-mobile": "36px",
|
||||
"font-size": "32px",
|
||||
"font-size2": "26px",
|
||||
|
|
@ -320,6 +321,7 @@ def setThemeStarlight(baseDir: str):
|
|||
removeTheme(baseDir)
|
||||
setThemeInConfig(baseDir, name)
|
||||
themeParams = {
|
||||
"focus-color": "darkred",
|
||||
"font-size-button-mobile": "36px",
|
||||
"font-size": "32px",
|
||||
"font-size2": "26px",
|
||||
|
|
@ -341,6 +343,7 @@ def setThemeStarlight(baseDir: str):
|
|||
"hashtag-vertical-spacing3": "100px",
|
||||
"hashtag-vertical-spacing4": "150px",
|
||||
"button-background": "#69282c",
|
||||
"button-small-background": "darkblue",
|
||||
"button-selected": "#a34046",
|
||||
"button-highlighted": "#12435f",
|
||||
"button-fg-highlighted": "white",
|
||||
|
|
@ -501,7 +504,9 @@ def setThemeLCD(baseDir: str):
|
|||
"button-selected": "black",
|
||||
"button-highlighted": "green",
|
||||
"button-background": "#33390d",
|
||||
"button-small-background": "#33390d",
|
||||
"button-text": "#9fb42b",
|
||||
"button-small-text": "#9fb42b",
|
||||
"color: #FFFFFE;": "color: #9fb42b;",
|
||||
"calendar-bg-color": "#eee",
|
||||
"day-number": "#3f2145",
|
||||
|
|
@ -568,7 +573,9 @@ def setThemePurple(baseDir: str):
|
|||
"main-visited-color": "#f93bb0",
|
||||
"button-selected": "#c042a0",
|
||||
"button-background": "#ff42a0",
|
||||
"button-small-background": "#ff42a0",
|
||||
"button-text": "white",
|
||||
"button-small-text": "white",
|
||||
"color: #FFFFFE;": "color: #1f152d;",
|
||||
"calendar-bg-color": "#eee",
|
||||
"lines-color": "#ff42a0",
|
||||
|
|
@ -599,6 +606,7 @@ def setThemePurple(baseDir: str):
|
|||
def setThemeHacker(baseDir: str):
|
||||
name = 'hacker'
|
||||
themeParams = {
|
||||
"focus-color": "green",
|
||||
"main-bg-color": "black",
|
||||
"link-bg-color": "black",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
|
|
@ -612,7 +620,9 @@ def setThemeHacker(baseDir: str):
|
|||
"main-visited-color": "#3c8234",
|
||||
"button-selected": "#063200",
|
||||
"button-background": "#062200",
|
||||
"button-small-background": "#062200",
|
||||
"button-text": "#00ff00",
|
||||
"button-small-text": "#00ff00",
|
||||
"button-corner-radius": "4px",
|
||||
"timeline-border-radius": "4px",
|
||||
"*font-family": "'Bedstead'",
|
||||
|
|
@ -646,6 +656,7 @@ def setThemeHacker(baseDir: str):
|
|||
def setThemeLight(baseDir: str):
|
||||
name = 'light'
|
||||
themeParams = {
|
||||
"focus-color": "grey",
|
||||
"font-size-button-mobile": "36px",
|
||||
"font-size": "32px",
|
||||
"font-size2": "26px",
|
||||
|
|
@ -700,6 +711,7 @@ def setThemeLight(baseDir: str):
|
|||
def setThemeSolidaric(baseDir: str):
|
||||
name = 'solidaric'
|
||||
themeParams = {
|
||||
"focus-color": "grey",
|
||||
"font-size-button-mobile": "36px",
|
||||
"font-size": "32px",
|
||||
"font-size2": "26px",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "وقف الحظر",
|
||||
"Enter an emoji name to search for": "أدخل اسم رمز تعبيري للبحث عنه",
|
||||
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "أدخل عنوانًا أو عنصرًا مشتركًا أو! history أو #hashtag أو * مهارة أو: emoji: للبحث عنه",
|
||||
"Go Back": "عد",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "معلومات الاعتدال",
|
||||
"Suspended accounts": "الحسابات المعلقه",
|
||||
"These are currently suspended": "هذه معلقة حاليا",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Deixeu de bloquejar",
|
||||
"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",
|
||||
"Go Back": "Torna",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Informació de moderació",
|
||||
"Suspended accounts": "Comptes suspesos",
|
||||
"These are currently suspended": "Actualment estan suspeses",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Stopiwch rwystro",
|
||||
"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",
|
||||
"Go Back": "Go Back",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Gwybodaeth Cymedroli",
|
||||
"Suspended accounts": "Cyfrifon gohiriedig",
|
||||
"These are currently suspended": "Mae'r rhain wedi'u hatal ar hyn o bryd",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Sperre aufheben",
|
||||
"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",
|
||||
"Go Back": "Zurück",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Moderationsinformationen",
|
||||
"Suspended accounts": "Temporäre gesperrte Benutzer",
|
||||
"These are currently suspended": "Diese sind temporär gesperrt",
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
"Report": "Report",
|
||||
"Send to moderators": "Send to moderators",
|
||||
"Search for emoji": "Search for emoji",
|
||||
"Cancel": "Cancel",
|
||||
"Cancel": "✘",
|
||||
"Submit": "Submit",
|
||||
"Image description": "Image description",
|
||||
"Item image": "Item image",
|
||||
|
|
@ -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",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Moderation Information",
|
||||
"Suspended accounts": "Suspended accounts",
|
||||
"These are currently suspended": "These are currently suspended",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Dejar de bloquear",
|
||||
"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",
|
||||
"Go Back": "Regresa",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Información de moderación",
|
||||
"Suspended accounts": "Cuentas suspendidas",
|
||||
"These are currently suspended": "Actualmente están suspendidos",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Arrêtez le blocage",
|
||||
"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",
|
||||
"Go Back": "Retourner",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Informations de modération",
|
||||
"Suspended accounts": "Comptes suspendus",
|
||||
"These are currently suspended": "Ceux-ci sont actuellement suspendus",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Stop blocáil",
|
||||
"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",
|
||||
"Go Back": "Dul ar ais",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Faisnéis Modhnóireachta",
|
||||
"Suspended accounts": "Cuntais ar fionraí",
|
||||
"These are currently suspended": "Tá siad seo ar fionraí faoi láthair",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "रोकना बंद करो",
|
||||
"Enter an emoji name to search for": "खोजने के लिए एक इमोजी नाम दर्ज करें",
|
||||
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "एक पता, साझा किया गया आइटम दर्ज करें; इतिहास, # अंश, * कौशल या: इमोजी: खोजने के लिए",
|
||||
"Go Back": "वापस जाओ",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "मॉडरेशन जानकारी",
|
||||
"Suspended accounts": "निलंबित खाते",
|
||||
"These are currently suspended": "ये फिलहाल निलंबित हैं",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Smetti di bloccare",
|
||||
"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",
|
||||
"Go Back": "Torna indietro",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Informazioni sulla moderazione",
|
||||
"Suspended accounts": "Conti sospesi",
|
||||
"These are currently suspended": "Questi sono attualmente sospesi",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "ブロックを停止",
|
||||
"Enter an emoji name to search for": "検索する絵文字名を入力してください",
|
||||
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "検索するアドレス、共有アイテム、!history、#ハッシュタグ、* skillまたは:emoji:を入力してください",
|
||||
"Go Back": "戻る",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "モデレーション情報",
|
||||
"Suspended accounts": "一時停止されたアカウント",
|
||||
"These are currently suspended": "これらは現在一時停止中です",
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@
|
|||
"About this Instance": "A prepaus d’aquesta instància",
|
||||
"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 d’aquesta instància",
|
||||
"Go Back": "Tornar",
|
||||
"Go Back": "◀",
|
||||
"Stop blocking": "Quitar de blocar",
|
||||
"View": "Veire",
|
||||
"Options for": "Opcions per",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Pare de bloquear",
|
||||
"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",
|
||||
"Go Back": "Volte",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Informações sobre moderação",
|
||||
"Suspended accounts": "Contas suspensas",
|
||||
"These are currently suspended": "Estes estão atualmente suspensos",
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
"Stop blocking": "Прекратить блокировку",
|
||||
"Enter an emoji name to search for": "Введите имя смайлика для поиска",
|
||||
"Enter an address, shared item, !history, #hashtag, *skill or :emoji: to search for": "Введите адрес, общий элемент,! History, #hashtag, * skill или: emoji: для поиска",
|
||||
"Go Back": "Вернитесь назад",
|
||||
"Go Back": "◀",
|
||||
"Moderation Information": "Модерация Информация",
|
||||
"Suspended accounts": "Приостановленные аккаунты",
|
||||
"These are currently suspended": "В настоящее время они приостановлены",
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
"The files attached below should be no larger than 10MB in total uploaded at once.": "一次上传的文件总数不得超过10MB。",
|
||||
"Avatar image": "头像图片",
|
||||
"Background image": "背景图",
|
||||
"Go Back": "◀",
|
||||
"Timeline banner image": "时间线横幅图片",
|
||||
"Approve follower requests": "批准关注者请求",
|
||||
"This is a bot account": "这是一个机器人帐户",
|
||||
|
|
|
|||
42
utils.py
|
|
@ -215,6 +215,8 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
|
|||
def getNicknameFromActor(actor: str) -> str:
|
||||
"""Returns the nickname from an actor url
|
||||
"""
|
||||
if actor.startswith('@'):
|
||||
actor = actor[1:]
|
||||
if '/users/' not in actor:
|
||||
if '/profile/' in actor:
|
||||
nickStr = actor.split('/profile/')[1].replace('@', '')
|
||||
|
|
@ -222,18 +224,27 @@ def getNicknameFromActor(actor: str) -> str:
|
|||
return nickStr
|
||||
else:
|
||||
return nickStr.split('/')[0]
|
||||
if '/channel/' in actor:
|
||||
elif '/channel/' in actor:
|
||||
nickStr = actor.split('/channel/')[1].replace('@', '')
|
||||
if '/' not in nickStr:
|
||||
return nickStr
|
||||
else:
|
||||
return nickStr.split('/')[0]
|
||||
elif '/accounts/' 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
|
||||
if '/@' in actor:
|
||||
nickStr = actor.split('/@')[1]
|
||||
if '/' in nickStr:
|
||||
nickStr = nickStr.split('/')[0]
|
||||
return nickStr
|
||||
elif '@' in actor:
|
||||
nickStr = actor.split('@')[0]
|
||||
return nickStr
|
||||
return None
|
||||
nickStr = actor.split('/users/')[1].replace('@', '')
|
||||
if '/' not in nickStr:
|
||||
|
|
@ -245,28 +256,38 @@ def getNicknameFromActor(actor: str) -> str:
|
|||
def getDomainFromActor(actor: str) -> (str, int):
|
||||
"""Returns the domain name from an actor url
|
||||
"""
|
||||
if actor.startswith('@'):
|
||||
actor = actor[1:]
|
||||
port = None
|
||||
prefixes = getProtocolPrefixes()
|
||||
if '/profile/' in actor:
|
||||
domain = actor.split('/profile/')[0]
|
||||
for prefix in prefixes:
|
||||
domain = domain.replace(prefix, '')
|
||||
else:
|
||||
if '/channel/' in actor:
|
||||
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:
|
||||
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:
|
||||
portStr = domain.split(':')[1]
|
||||
if not portStr.isdigit():
|
||||
|
|
@ -576,12 +597,13 @@ def validNickname(domain: str, nickname: str) -> bool:
|
|||
if nickname == domain:
|
||||
return False
|
||||
reservedNames = ('inbox', 'dm', 'outbox', 'following',
|
||||
'public', 'followers', 'profile',
|
||||
'public', 'followers',
|
||||
'channel', 'capabilities', 'calendar',
|
||||
'tlreplies', 'tlmedia', 'tlblogs',
|
||||
'moderation', 'activity', 'undo',
|
||||
'reply', 'replies', 'question', 'like',
|
||||
'likes', 'users', 'statuses',
|
||||
'accounts', 'channels', 'profile',
|
||||
'updates', 'repeat', 'announce',
|
||||
'shares', 'fonts', 'icons')
|
||||
if nickname in reservedNames:
|
||||
|
|
|
|||
316
webinterface.py
|
|
@ -252,7 +252,7 @@ def updateAvatarImageCache(session, baseDir: str, httpPrefix: str,
|
|||
print('Failed to download avatar image: ' + str(avatarUrl))
|
||||
print(e)
|
||||
prof = 'https://www.w3.org/ns/activitystreams'
|
||||
if '/channel/' not in actor:
|
||||
if '/channel/' not in actor or '/accounts/' not in actor:
|
||||
sessionHeaders = {
|
||||
'Accept': 'application/activity+json; profile="' + prof + '"'
|
||||
}
|
||||
|
|
@ -747,7 +747,13 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
|
|||
|
||||
# add the page title
|
||||
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:
|
||||
# previous page link
|
||||
|
|
@ -758,7 +764,7 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
|
|||
iconsDir + '/pageup.png" title="' + \
|
||||
translate['Page up'] + \
|
||||
'" alt="' + translate['Page up'] + \
|
||||
'"></a></center>'
|
||||
'"></a></center>\n'
|
||||
index = startIndex
|
||||
while index <= endIndex:
|
||||
postId = lines[index].strip('\n').strip('\r')
|
||||
|
|
@ -1306,240 +1312,242 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
|||
editProfileForm = htmlHeader(cssFilename, editProfileCSS)
|
||||
editProfileForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/profiledata">'
|
||||
editProfileForm += ' <div class="vertical-center">'
|
||||
'accept-charset="UTF-8" action="' + path + '/profiledata">\n'
|
||||
editProfileForm += ' <div class="vertical-center">\n'
|
||||
editProfileForm += \
|
||||
' <p class="new-post-text">' + translate['Profile for'] + \
|
||||
' ' + nickname + '@' + domainFull + '</p>'
|
||||
editProfileForm += ' <div class="container">'
|
||||
editProfileForm += \
|
||||
' <input type="submit" name="submitProfile" value="' + \
|
||||
translate['Submit'] + '">'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
|
||||
translate['Cancel'] + '</button></a>'
|
||||
editProfileForm += ' </div>'
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
editProfileForm += \
|
||||
' <input type="submit" name="submitProfile" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
|
||||
if scheduledPostsExist(baseDir, nickname, domain):
|
||||
editProfileForm += ' <div class="container">'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="removeScheduledPosts"> ' + \
|
||||
translate['Remove scheduled posts'] + '<br>'
|
||||
editProfileForm += ' </div>'
|
||||
translate['Remove scheduled posts'] + '<br>\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
|
||||
editProfileForm += ' <div class="container">'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += ' <label class="labels">' + \
|
||||
translate['Nickname'] + '</label>'
|
||||
translate['Nickname'] + '</label>\n'
|
||||
editProfileForm += \
|
||||
' <input type="text" name="displayNickname" value="' + \
|
||||
displayNickname + '"><br>'
|
||||
displayNickname + '"><br>\n'
|
||||
editProfileForm += \
|
||||
' <label class="labels">' + translate['Your bio'] + '</label>'
|
||||
' <label class="labels">' + translate['Your bio'] + '</label>\n'
|
||||
editProfileForm += \
|
||||
' <textarea id="message" name="bio" style="height:200px">' + \
|
||||
bioStr + '</textarea>'
|
||||
bioStr + '</textarea>\n'
|
||||
editProfileForm += '<label class="labels">' + \
|
||||
translate['Donations link'] + '</label><br>'
|
||||
translate['Donations link'] + '</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="text" placeholder="https://..." ' + \
|
||||
'name="donateUrl" value="' + donateUrl + '">'
|
||||
'name="donateUrl" value="' + donateUrl + '">\n'
|
||||
editProfileForm += \
|
||||
'<label class="labels">' + translate['XMPP'] + '</label><br>'
|
||||
'<label class="labels">' + translate['XMPP'] + '</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="text" name="xmppAddress" value="' + \
|
||||
xmppAddress + '">'
|
||||
xmppAddress + '">\n'
|
||||
editProfileForm += '<label class="labels">' + \
|
||||
translate['Matrix'] + '</label><br>'
|
||||
translate['Matrix'] + '</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <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 += \
|
||||
' <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 += \
|
||||
' <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 += \
|
||||
' <input type="text" name="toxAddress" value="' + \
|
||||
toxAddress + '">'
|
||||
toxAddress + '">\n'
|
||||
editProfileForm += '<label class="labels">' + \
|
||||
translate['Email'] + '</label><br>'
|
||||
translate['Email'] + '</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="text" name="email" value="' + emailAddress + '">'
|
||||
' <input type="text" name="email" value="' + emailAddress + '">\n'
|
||||
editProfileForm += \
|
||||
'<label class="labels">' + \
|
||||
translate['PGP Fingerprint'] + '</label><br>'
|
||||
translate['PGP Fingerprint'] + '</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="text" name="openpgp" value="' + \
|
||||
PGPfingerprint + '">'
|
||||
PGPfingerprint + '">\n'
|
||||
editProfileForm += \
|
||||
'<label class="labels">' + translate['PGP'] + '</label><br>'
|
||||
'<label class="labels">' + translate['PGP'] + '</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <textarea id="message" placeholder=' + \
|
||||
'"-----BEGIN PGP PUBLIC KEY BLOCK-----" name="pgp" ' + \
|
||||
'style="height:100px">' + PGPpubKey + '</textarea>'
|
||||
'style="height:100px">' + PGPpubKey + '</textarea>\n'
|
||||
editProfileForm += '<a href="/users/' + nickname + \
|
||||
'/followingaccounts"><label class="labels">' + \
|
||||
translate['Following'] + '</label></a><br>'
|
||||
editProfileForm += ' </div>'
|
||||
editProfileForm += ' <div class="container">'
|
||||
translate['Following'] + '</label></a><br>\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
idx = 'The files attached below should be no larger than ' + \
|
||||
'10MB in total uploaded at once.'
|
||||
editProfileForm += \
|
||||
' <label class="labels">' + translate[idx] + '</label><br><br>'
|
||||
' <label class="labels">' + translate[idx] + '</label><br><br>\n'
|
||||
editProfileForm += \
|
||||
' <label class="labels">' + translate['Avatar image'] + '</label>'
|
||||
' <label class="labels">' + translate['Avatar image'] + \
|
||||
'</label>\n'
|
||||
editProfileForm += \
|
||||
' <input type="file" id="avatar" name="avatar"'
|
||||
editProfileForm += ' accept="' + imageFormats + '">'
|
||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||
|
||||
editProfileForm += \
|
||||
' <br><label class="labels">' + \
|
||||
translate['Background image'] + '</label>'
|
||||
translate['Background image'] + '</label>\n'
|
||||
editProfileForm += ' <input type="file" id="image" name="image"'
|
||||
editProfileForm += ' accept="' + imageFormats + '">'
|
||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||
|
||||
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 += ' accept="' + imageFormats + '">'
|
||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||
|
||||
editProfileForm += ' <br><label class="labels">' + \
|
||||
translate['Search banner image'] + '</label>'
|
||||
translate['Search banner image'] + '</label>\n'
|
||||
editProfileForm += ' <input type="file" id="search_banner" '
|
||||
editProfileForm += 'name="search_banner"'
|
||||
editProfileForm += ' accept="' + imageFormats + '">'
|
||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||
|
||||
editProfileForm += ' </div>'
|
||||
editProfileForm += ' <div class="container">'
|
||||
editProfileForm += ' </div>\n'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
'<label class="labels">' + translate['Change Password'] + \
|
||||
'</label><br>'
|
||||
editProfileForm += ' <input type="text" name="password" value=""><br>'
|
||||
'</label><br>\n'
|
||||
editProfileForm += ' <input type="text" name="password" ' + \
|
||||
'value=""><br>\n'
|
||||
editProfileForm += \
|
||||
'<label class="labels">' + translate['Confirm Password'] + \
|
||||
'</label><br>'
|
||||
'</label><br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="text" name="passwordconfirm" value="">'
|
||||
editProfileForm += ' </div>'
|
||||
editProfileForm += ' <div class="container">'
|
||||
' <input type="text" name="passwordconfirm" value="">\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="approveFollowers" ' + manuallyApprovesFollowers + \
|
||||
'> ' + translate['Approve follower requests'] + '<br>'
|
||||
'> ' + translate['Approve follower requests'] + '<br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="isBot" ' + \
|
||||
isBot + '> ' + translate['This is a bot account'] + '<br>'
|
||||
isBot + '> ' + translate['This is a bot account'] + '<br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="isGroup" ' + isGroup + '> ' + \
|
||||
translate['This is a group account'] + '<br>'
|
||||
translate['This is a group account'] + '<br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'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 += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="removeTwitter" ' + removeTwitter + '> ' + \
|
||||
translate['Remove Twitter posts'] + '<br>'
|
||||
translate['Remove Twitter posts'] + '<br>\n'
|
||||
if path.startswith('/users/' + adminNickname + '/'):
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="mediaInstance" ' + mediaInstanceStr + '> ' + \
|
||||
translate['This is a media instance'] + '<br>'
|
||||
translate['This is a media instance'] + '<br>\n'
|
||||
|
||||
editProfileForm += \
|
||||
' <br><b><label class="labels">' + \
|
||||
translate['Filtered words'] + '</label></b>'
|
||||
translate['Filtered words'] + '</label></b>\n'
|
||||
editProfileForm += ' <br><label class="labels">' + \
|
||||
translate['One per line'] + '</label>'
|
||||
translate['One per line'] + '</label>\n'
|
||||
editProfileForm += ' <textarea id="message" ' + \
|
||||
'name="filteredWords" style="height:200px">' + \
|
||||
filterStr + '</textarea>'
|
||||
filterStr + '</textarea>\n'
|
||||
|
||||
editProfileForm += \
|
||||
' <br><b><label class="labels">' + \
|
||||
translate['Word Replacements'] + '</label></b>'
|
||||
editProfileForm += ' <br><label class="labels">A -> B</label>'
|
||||
translate['Word Replacements'] + '</label></b>\n'
|
||||
editProfileForm += ' <br><label class="labels">A -> B</label>\n'
|
||||
editProfileForm += \
|
||||
' <textarea id="message" name="switchWords" ' + \
|
||||
'style="height:200px">' + switchStr + '</textarea>'
|
||||
'style="height:200px">' + switchStr + '</textarea>\n'
|
||||
|
||||
editProfileForm += \
|
||||
' <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 ' + \
|
||||
'nickname@domain or *@blockeddomain'
|
||||
editProfileForm += \
|
||||
' <br><label class="labels">' + translate[idx] + '</label>'
|
||||
' <br><label class="labels">' + translate[idx] + '</label>\n'
|
||||
editProfileForm += \
|
||||
' <textarea id="message" name="blocked" style="height:200px">' + \
|
||||
blockedStr + '</textarea>'
|
||||
blockedStr + '</textarea>\n'
|
||||
|
||||
editProfileForm += \
|
||||
' <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. ' + \
|
||||
'One domain name per line.'
|
||||
editProfileForm += \
|
||||
' <br><label class="labels">' + \
|
||||
translate[idx] + '</label>'
|
||||
translate[idx] + '</label>\n'
|
||||
editProfileForm += \
|
||||
' <textarea id="message" name="allowedInstances" ' + \
|
||||
'style="height:200px">' + allowedInstancesStr + '</textarea>'
|
||||
'style="height:200px">' + allowedInstancesStr + '</textarea>\n'
|
||||
|
||||
editProfileForm += \
|
||||
' <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'
|
||||
editProfileForm += \
|
||||
' <br><label class="labels">' + \
|
||||
translate[idx] + '</label>'
|
||||
translate[idx] + '</label>\n'
|
||||
editProfileForm += \
|
||||
' <textarea id="message" name="gitProjects" ' + \
|
||||
'style="height:100px">' + gitProjectsStr + '</textarea>'
|
||||
'style="height:100px">' + gitProjectsStr + '</textarea>\n'
|
||||
|
||||
editProfileForm += \
|
||||
' <br><b><label class="labels">' + \
|
||||
translate['YouTube Replacement Domain'] + '</label></b>'
|
||||
translate['YouTube Replacement Domain'] + '</label></b>\n'
|
||||
YTReplacementDomain = getConfigParam(baseDir, "youtubedomain")
|
||||
if not YTReplacementDomain:
|
||||
YTReplacementDomain = ''
|
||||
editProfileForm += \
|
||||
' <input type="text" name="ytdomain" value="' + \
|
||||
YTReplacementDomain + '">'
|
||||
YTReplacementDomain + '">\n'
|
||||
|
||||
editProfileForm += ' </div>'
|
||||
editProfileForm += ' <div class="container">'
|
||||
editProfileForm += ' </div>\n'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
' <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 ' + \
|
||||
'can indicate some skills that you have and approximate ' + \
|
||||
'proficiency levels. This helps organizers to construct ' + \
|
||||
'teams with an appropriate combination of skills.'
|
||||
editProfileForm += ' <label class="labels">' + \
|
||||
translate[idx] + '</label>'
|
||||
translate[idx] + '</label>\n'
|
||||
editProfileForm += skillsStr + themesDropdown + moderatorsStr
|
||||
editProfileForm += ' </div>' + instanceStr
|
||||
editProfileForm += ' <div class="container">'
|
||||
editProfileForm += ' </div>\n' + instanceStr
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += ' <b><label class="labels">' + \
|
||||
translate['Danger Zone'] + '</label></b><br>'
|
||||
translate['Danger Zone'] + '</label></b><br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class=dangercheckbox" ' + \
|
||||
'name="deactivateThisAccount"> ' + \
|
||||
translate['Deactivate this account'] + '<br>'
|
||||
editProfileForm += ' </div>'
|
||||
editProfileForm += ' </div>'
|
||||
editProfileForm += '</form>'
|
||||
translate['Deactivate this account'] + '<br>\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
editProfileForm += '</form>\n'
|
||||
editProfileForm += htmlFooter()
|
||||
return editProfileForm
|
||||
|
||||
|
|
@ -2206,7 +2214,7 @@ def htmlNewPost(mediaInstance: bool, translate: {},
|
|||
newPostForm += \
|
||||
' <a href="' + pathBase + \
|
||||
'/inbox"><button class="cancelbtn">' + \
|
||||
translate['Cancel'] + '</button></a>\n'
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
newPostForm += \
|
||||
' <input type="submit" name="submitPost" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
|
|
@ -2289,6 +2297,8 @@ def htmlHeader(cssFilename: str, css: str, lang='en') -> str:
|
|||
htmlStr += ' <link rel="preload" as="font" type="' + \
|
||||
fontFormat + '" href="' + fontName + '" crossorigin>\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 += ' <body>\n'
|
||||
return htmlStr
|
||||
|
|
@ -2671,7 +2681,7 @@ def htmlProfile(defaultTimeline: str,
|
|||
ssbAddress + '</label></p>\n'
|
||||
if toxAddress:
|
||||
donateSection += \
|
||||
'<p>Tox: <label class="ssbaddr">' + \
|
||||
'<p>Tox: <label class="toxaddr">' + \
|
||||
toxAddress + '</label></p>\n'
|
||||
if PGPfingerprint:
|
||||
donateSection += \
|
||||
|
|
@ -2791,30 +2801,32 @@ def htmlProfile(defaultTimeline: str,
|
|||
profileStr = \
|
||||
linkToTimelineStart + profileHeaderStr + \
|
||||
linkToTimelineEnd + donateSection
|
||||
profileStr += '<div class="container">\n'
|
||||
profileStr += '<div class="container" id="buttonheader">\n'
|
||||
profileStr += ' <center>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '"><button class="' + postsButton + \
|
||||
'"><span>' + translate['Posts'] + ' </span></button></a>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '/following"><button class="' + \
|
||||
followingButton + '"><span>' + translate['Following'] + \
|
||||
' <a href="' + usersPath + '#buttonheader"><button class="' + \
|
||||
postsButton + '"><span>' + translate['Posts'] + \
|
||||
' </span></button></a>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '/followers"><button class="' + \
|
||||
followersButton + '"><span>' + translate['Followers'] + \
|
||||
' <a href="' + usersPath + '/following#buttonheader">' + \
|
||||
'<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>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '/roles"><button class="' + \
|
||||
rolesButton + '"><span>' + translate['Roles'] + ' </span></button></a>'
|
||||
' <a href="' + usersPath + '/skills#buttonheader">' + \
|
||||
'<button class="' + skillsButton + '"><span>' + \
|
||||
translate['Skills'] + ' </span></button></a>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '/skills"><button class="' + \
|
||||
skillsButton + '"><span>' + translate['Skills'] + \
|
||||
' </span></button></a>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '/shares"><button class="' + \
|
||||
sharesButton + '"><span>' + translate['Shares'] + \
|
||||
' </span></button></a>'
|
||||
' <a href="' + usersPath + '/shares#buttonheader">' + \
|
||||
'<button class="' + sharesButton + '"><span>' + \
|
||||
translate['Shares'] + ' </span></button></a>'
|
||||
profileStr += editProfileStr + logoutStr
|
||||
profileStr += ' </center>'
|
||||
profileStr += '</div>'
|
||||
|
|
@ -3708,7 +3720,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
nickname, domain,
|
||||
displayName, False)
|
||||
|
||||
avatarLink = ' <a href="' + postActor + '">'
|
||||
avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
|
||||
avatarLink += \
|
||||
' <img loading="lazy" src="' + avatarUrl + '" title="' + \
|
||||
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>'
|
||||
|
|
@ -3716,14 +3728,15 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
if showAvatarOptions and \
|
||||
fullDomain + '/users/' + nickname not in postActor:
|
||||
avatarLink = \
|
||||
' <a href="/users/' + nickname + '?options=' + postActor + \
|
||||
' <a class="imageAnchor" href="/users/' + \
|
||||
nickname + '?options=' + postActor + \
|
||||
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + '">\n'
|
||||
avatarLink += \
|
||||
' <img loading="lazy" title="' + \
|
||||
translate['Show options for this person'] + \
|
||||
'" src="' + avatarUrl + '" ' + avatarPosition + '/></a>\n'
|
||||
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
|
||||
# it should already have been created for the inbox
|
||||
|
|
@ -3788,7 +3801,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
nickname, domain,
|
||||
displayName, False)
|
||||
titleStr += \
|
||||
'<a href="/users/' + nickname + '?options=' + postActor + \
|
||||
'<a class="imageAnchor" href="/users/' + \
|
||||
nickname + '?options=' + postActor + \
|
||||
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \
|
||||
'">' + displayName + '</a>\n'
|
||||
else:
|
||||
|
|
@ -3802,7 +3816,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
# pprint(postJsonObject)
|
||||
print('ERROR: no actorDomain')
|
||||
titleStr += \
|
||||
'<a href="/users/' + nickname + '?options=' + postActor + \
|
||||
'<a class="imageAnchor" href="/users/' + \
|
||||
nickname + '?options=' + postActor + \
|
||||
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \
|
||||
'">@' + actorNickname + '@' + actorDomain + '</a>\n'
|
||||
|
||||
|
|
@ -3833,19 +3848,20 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
replyStr = ''
|
||||
if isPublicRepeat:
|
||||
replyStr += \
|
||||
'<a href="/users/' + nickname + '?replyto=' + replyToLink + \
|
||||
'<a class="imageAnchor" href="/users/' + \
|
||||
nickname + '?replyto=' + replyToLink + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
'" title="' + translate['Reply to this post'] + '">\n'
|
||||
else:
|
||||
if isDM(postJsonObject):
|
||||
replyStr += \
|
||||
'<a href="/users/' + nickname + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'?replydm=' + replyToLink + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
'" title="' + translate['Reply to this post'] + '">\n'
|
||||
else:
|
||||
replyStr += \
|
||||
'<a href="/users/' + nickname + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'?replyfollowers=' + replyToLink + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
'" title="' + translate['Reply to this post'] + '">\n'
|
||||
|
|
@ -3861,7 +3877,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
if isBlogPost(postJsonObject):
|
||||
if '/statuses/' in postJsonObject['object']['id']:
|
||||
editStr += \
|
||||
'<a href="/users/' + nickname + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'/tlblogs?editblogpost=' + \
|
||||
postJsonObject['object']['id'].split('/statuses/')[1] + \
|
||||
'?actor=' + actorNickname + \
|
||||
|
|
@ -3885,7 +3901,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
announceLink = 'unrepeatprivate'
|
||||
announceTitle = translate['Undo the repeat']
|
||||
announceStr = \
|
||||
'<a href="/users/' + nickname + '?' + announceLink + \
|
||||
'<a class="imageAnchor" href="/users/' + \
|
||||
nickname + '?' + announceLink + \
|
||||
'=' + postJsonObject['object']['id'] + pageNumberParam + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
'?bm=' + timelinePostBookmark + \
|
||||
|
|
@ -3913,7 +3930,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
likeLink = 'unlike'
|
||||
likeTitle = translate['Undo the like']
|
||||
likeStr = \
|
||||
'<a href="/users/' + nickname + '?' + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + '?' + \
|
||||
likeLink + '=' + postJsonObject['object']['id'] + \
|
||||
pageNumberParam + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
|
|
@ -3935,7 +3952,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
bookmarkLink = 'unbookmark'
|
||||
bookmarkTitle = translate['Undo the bookmark']
|
||||
bookmarkStr = \
|
||||
'<a href="/users/' + nickname + '?' + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + '?' + \
|
||||
bookmarkLink + '=' + postJsonObject['object']['id'] + \
|
||||
pageNumberParam + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
|
|
@ -3955,7 +3972,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
messageId.startswith(postActor))):
|
||||
if '/users/' + nickname + '/' in messageId:
|
||||
deleteStr = \
|
||||
'<a href="/users/' + nickname + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'?delete=' + messageId + pageNumberParam + \
|
||||
'" title="' + translate['Delete this post'] + '">\n'
|
||||
deleteStr += \
|
||||
|
|
@ -3965,7 +3982,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
else:
|
||||
if not isMuted:
|
||||
muteStr = \
|
||||
'<a href="/users/' + nickname + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'?mute=' + messageId + pageNumberParam + '?tl=' + boxName + \
|
||||
'?bm=' + timelinePostBookmark + \
|
||||
'" title="' + translate['Mute this post'] + '">\n'
|
||||
|
|
@ -3976,7 +3993,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
'" src="/' + iconsDir + '/mute.png"/></a>\n'
|
||||
else:
|
||||
muteStr = \
|
||||
'<a href="/users/' + nickname + '?unmute=' + messageId + \
|
||||
'<a class="imageAnchor" href="/users/' + \
|
||||
nickname + '?unmute=' + messageId + \
|
||||
pageNumberParam + '?tl=' + boxName + '?bm=' + \
|
||||
timelinePostBookmark + '" title="' + \
|
||||
translate['Undo mute'] + '">\n'
|
||||
|
|
@ -4034,7 +4052,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
idx = 'Show options for this person'
|
||||
replyAvatarImageInPost = \
|
||||
'<div class="timeline-avatar-reply">\n' \
|
||||
'<a href="/users/' + nickname + \
|
||||
'<a class="imageAnchor" ' + \
|
||||
'href="/users/' + nickname + \
|
||||
'?options=' + \
|
||||
announceActor + ';' + str(pageNumber) + \
|
||||
';' + announceAvatarUrl + \
|
||||
|
|
@ -4128,7 +4147,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
'<div class=' + \
|
||||
'"timeline-avatar-reply">\n'
|
||||
replyAvatarImageInPost += \
|
||||
'<a href="/users/' + nickname + \
|
||||
'<a class="imageAnchor" ' + \
|
||||
'href="/users/' + nickname + \
|
||||
'?options=' + replyActor + \
|
||||
';' + str(pageNumber) + ';' + \
|
||||
replyAvatarUrl + \
|
||||
|
|
@ -4190,7 +4210,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
|
|||
|
||||
attachmentStr, galleryStr = \
|
||||
getPostAttachmentsAsHtml(postJsonObject, boxName, translate,
|
||||
isMuted, avatarLink,
|
||||
isMuted, avatarLink.strip(),
|
||||
replyStr, announceStr, likeStr,
|
||||
bookmarkStr, deleteStr, muteStr)
|
||||
|
||||
|
|
@ -4530,7 +4550,8 @@ def htmlTimeline(defaultTimeline: str,
|
|||
# show follow approvals icon
|
||||
followApprovals = \
|
||||
'<a href="' + usersPath + \
|
||||
'/followers"><img loading="lazy" ' + \
|
||||
'/followers#buttonheader">' + \
|
||||
'<img loading="lazy" ' + \
|
||||
'class="timelineicon" alt="' + \
|
||||
translate['Approve follow requests'] + \
|
||||
'" title="' + translate['Approve follow requests'] + \
|
||||
|
|
@ -4566,7 +4587,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
if boxName != 'tlblogs':
|
||||
if not manuallyApproveFollowers:
|
||||
newPostButtonStr = \
|
||||
'<a href="' + usersPath + \
|
||||
'<a class="imageAnchor" href="' + usersPath + \
|
||||
'/newpost"><img loading="lazy" src="/' + \
|
||||
iconsDir + '/newpost.png" title="' + \
|
||||
translate['Create a new post'] + '" alt="| ' + \
|
||||
|
|
@ -4574,7 +4595,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
'" class="timelineicon"/></a>\n'
|
||||
else:
|
||||
newPostButtonStr = \
|
||||
'<a href="' + usersPath + \
|
||||
'<a class="imageAnchor" href="' + usersPath + \
|
||||
'/newfollowers"><img loading="lazy" src="/' + \
|
||||
iconsDir + '/newpost.png" title="' + \
|
||||
translate['Create a new post'] + \
|
||||
|
|
@ -4582,7 +4603,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
'" class="timelineicon"/></a>\n'
|
||||
else:
|
||||
newPostButtonStr = \
|
||||
'<a href="' + usersPath + \
|
||||
'<a class="imageAnchor" href="' + usersPath + \
|
||||
'/newblog"><img loading="lazy" src="/' + \
|
||||
iconsDir + '/newpost.png" title="' + \
|
||||
translate['Create a new post'] + '" alt="| ' + \
|
||||
|
|
@ -4590,7 +4611,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
'" class="timelineicon"/></a>\n'
|
||||
else:
|
||||
newPostButtonStr = \
|
||||
'<a href="' + usersPath + \
|
||||
'<a class="imageAnchor" href="' + usersPath + \
|
||||
'/newdm"><img loading="lazy" src="/' + \
|
||||
iconsDir + '/newpost.png" title="' + \
|
||||
translate['Create a new DM'] + \
|
||||
|
|
@ -4684,7 +4705,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
sharesButtonStr + bookmarksButtonStr + \
|
||||
moderationButtonStr + newPostButtonStr
|
||||
tlStr += \
|
||||
' <a href="' + usersPath + \
|
||||
' <a class="imageAnchor" href="' + usersPath + \
|
||||
'/search"><img loading="lazy" src="/' + \
|
||||
iconsDir + '/search.png" title="' + \
|
||||
translate['Search and follow'] + '" alt="| ' + \
|
||||
|
|
@ -4695,13 +4716,13 @@ def htmlTimeline(defaultTimeline: str,
|
|||
# indicate that the calendar icon is highlighted
|
||||
calendarAltText = '*' + calendarAltText + '*'
|
||||
tlStr += \
|
||||
' <a href="' + usersPath + calendarPath + \
|
||||
' <a class="imageAnchor" href="' + usersPath + calendarPath + \
|
||||
'"><img loading="lazy" src="/' + iconsDir + '/' + \
|
||||
calendarImage + '" title="' + translate['Calendar'] + \
|
||||
'" alt="| ' + calendarAltText + '" class="timelineicon"/></a>\n'
|
||||
|
||||
tlStr += \
|
||||
' <a href="' + usersPath + '/minimal' + \
|
||||
' <a class="imageAnchor" href="' + usersPath + '/minimal' + \
|
||||
'"><img loading="lazy" src="/' + iconsDir + \
|
||||
'/showhide.png" title="' + translate['Show/Hide Buttons'] + \
|
||||
'" alt="| ' + translate['Show/Hide Buttons'] + \
|
||||
|
|
@ -5622,7 +5643,8 @@ def htmlPersonOptions(translate: {}, baseDir: str,
|
|||
translate['Petname'] + ': ' + \
|
||||
'<input type="text" name="optionpetname" value="' + \
|
||||
petname + '">\n' \
|
||||
'<button type="submit" class="button" name="submitPetname">' + \
|
||||
'<button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitPetname">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
|
||||
if isFollowingActor(baseDir, nickname, domain, optionsActor):
|
||||
|
|
@ -5632,7 +5654,7 @@ def htmlPersonOptions(translate: {}, baseDir: str,
|
|||
'<input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="onCalendar" checked> ' + \
|
||||
translate['Receive calendar events from this account'] + \
|
||||
'<button type="submit" class="button" ' + \
|
||||
'<button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitOnCalendar">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
else:
|
||||
|
|
@ -5640,11 +5662,14 @@ def htmlPersonOptions(translate: {}, baseDir: str,
|
|||
'<input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="onCalendar"> ' + \
|
||||
translate['Receive calendar events from this account'] + \
|
||||
'<button type="submit" class="button" ' + \
|
||||
'<button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitOnCalendar">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
|
||||
optionsStr += optionsLinkStr
|
||||
optionsStr += \
|
||||
' <a href="/"><button type="button" class="button" ' + \
|
||||
'name="submitBack">' + translate['Go Back'] + '</button></a>\n'
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submitView">' + \
|
||||
translate['View'] + '</button>\n'
|
||||
|
|
@ -5675,7 +5700,7 @@ def htmlPersonOptions(translate: {}, baseDir: str,
|
|||
|
||||
optionsStr += \
|
||||
' <br><br>' + translate['Notes'] + ': \n'
|
||||
optionsStr += '<button type="submit" class="button" ' + \
|
||||
optionsStr += '<button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitPersonNotes">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
optionsStr += \
|
||||
|
|
@ -6244,10 +6269,10 @@ def htmlSearch(translate: {},
|
|||
followStr += \
|
||||
' <input type="hidden" name="actor" value="' + actor + '">\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" ' + \
|
||||
'name="submitSearch">' + translate['Submit'] + '</button>\n'
|
||||
followStr += ' <button type="submit" class="button" ' + \
|
||||
'name="submitBack">' + translate['Go Back'] + '</button>\n'
|
||||
followStr += ' </form>\n'
|
||||
followStr += ' <p class="hashtagswarm">' + \
|
||||
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
|
||||
"""
|
||||
if '/users/' in profileHandle or \
|
||||
'/accounts/' in profileHandle or \
|
||||
'/channel/' in profileHandle or \
|
||||
'/profile/' in profileHandle or \
|
||||
'/@' in profileHandle:
|
||||
|
|
@ -6440,15 +6466,15 @@ def htmlProfileAfterSearch(recentPostsCache: {}, maxRecentPosts: int,
|
|||
profileStr += \
|
||||
' <input type="hidden" name="actor" value="' + \
|
||||
personUrl + '">\n'
|
||||
profileStr += \
|
||||
' <a href="' + backUrl + '"><button class="button">' + \
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
profileStr += \
|
||||
' <button type="submit" class="button" name="submitYes">' + \
|
||||
translate['Follow'] + '</button>\n'
|
||||
profileStr += \
|
||||
' <button type="submit" class="button" name="submitView">' + \
|
||||
translate['View'] + '</button>\n'
|
||||
profileStr += \
|
||||
' <a href="' + backUrl + '"><button class="button">' + \
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
profileStr += ' </center>\n'
|
||||
profileStr += ' </form>\n'
|
||||
profileStr += '</div>\n'
|
||||
|
|
|
|||