Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
|
|
@ -66,7 +66,7 @@ def createReject(baseDir: str, federationList: [],
|
|||
return createAcceptReject(baseDir, federationList,
|
||||
nickname, domain, port,
|
||||
toUrl, ccUrl,
|
||||
httpPrefix, objectJson, None, 'Reject')
|
||||
httpPrefix, objectJson, 'Reject')
|
||||
|
||||
|
||||
def acceptFollow(baseDir: str, domain: str, messageJson: {},
|
||||
|
|
|
|||
49
blog.py
|
|
@ -20,6 +20,8 @@ from utils import getDomainFromActor
|
|||
from utils import locatePost
|
||||
from utils import loadJson
|
||||
from posts import createBlogsTimeline
|
||||
from newswire import rss2Header
|
||||
from newswire import rss2Footer
|
||||
|
||||
|
||||
def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||
|
|
@ -365,12 +367,12 @@ def htmlBlogPost(authorized: bool,
|
|||
'title="RSS 2.0" src="/' + \
|
||||
iconsDir + '/rss.png" /></a>'
|
||||
|
||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||
'loading="lazy" alt="RSS 3.0" ' + \
|
||||
'title="RSS 3.0" src="/' + \
|
||||
iconsDir + '/rss3.png" /></a>'
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
# blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||
# 'loading="lazy" alt="RSS 3.0" ' + \
|
||||
# 'title="RSS 3.0" src="/' + \
|
||||
# iconsDir + '/rss3.png" /></a>'
|
||||
|
||||
blogStr += '</p>'
|
||||
|
||||
|
|
@ -405,7 +407,7 @@ def htmlBlogPage(authorized: bool, session,
|
|||
timelineJson = createBlogsTimeline(session, baseDir,
|
||||
nickname, domain, port,
|
||||
httpPrefix,
|
||||
noOfItems, False, False,
|
||||
noOfItems, False,
|
||||
pageNumber)
|
||||
|
||||
if not timelineJson:
|
||||
|
|
@ -461,11 +463,11 @@ def htmlBlogPage(authorized: bool, session,
|
|||
'title="RSS 2.0" src="/' + \
|
||||
iconsDir + '/rss.png" /></a>'
|
||||
|
||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
blogStr += '<img loading="lazy" alt="RSS 3.0" ' + \
|
||||
'title="RSS 3.0" src="/' + \
|
||||
iconsDir + '/rss3.png" /></a>'
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
# blogStr += '<img loading="lazy" alt="RSS 3.0" ' + \
|
||||
# 'title="RSS 3.0" src="/' + \
|
||||
# iconsDir + '/rss3.png" /></a>'
|
||||
|
||||
blogStr += '</p>'
|
||||
|
||||
|
|
@ -473,23 +475,6 @@ def htmlBlogPage(authorized: bool, session,
|
|||
return None
|
||||
|
||||
|
||||
def rss2Header(httpPrefix: str,
|
||||
nickname: str, domainFull: str, translate: {}) -> str:
|
||||
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
|
||||
rssStr += "<rss version=\"2.0\">"
|
||||
rssStr += '<channel>'
|
||||
rssStr += ' <title>' + translate['Blog'] + '</title>'
|
||||
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||
'/users/' + nickname + '/rss.xml' + '</link>'
|
||||
return rssStr
|
||||
|
||||
|
||||
def rss2Footer() -> str:
|
||||
rssStr = '</channel>'
|
||||
rssStr += '</rss>'
|
||||
return rssStr
|
||||
|
||||
|
||||
def htmlBlogPageRSS2(authorized: bool, session,
|
||||
baseDir: str, httpPrefix: str, translate: {},
|
||||
nickname: str, domain: str, port: int,
|
||||
|
|
@ -505,7 +490,7 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
|||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
|
||||
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, translate)
|
||||
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, 'Blog', translate)
|
||||
|
||||
blogsIndex = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/tlblogs.index'
|
||||
|
|
@ -515,7 +500,7 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
|||
timelineJson = createBlogsTimeline(session, baseDir,
|
||||
nickname, domain, port,
|
||||
httpPrefix,
|
||||
noOfItems, False, False,
|
||||
noOfItems, False,
|
||||
pageNumber)
|
||||
|
||||
if not timelineJson:
|
||||
|
|
@ -561,7 +546,7 @@ def htmlBlogPageRSS3(authorized: bool, session,
|
|||
timelineJson = createBlogsTimeline(session, baseDir,
|
||||
nickname, domain, port,
|
||||
httpPrefix,
|
||||
noOfItems, False, False,
|
||||
noOfItems, False,
|
||||
pageNumber)
|
||||
|
||||
if not timelineJson:
|
||||
|
|
|
|||
31
content.py
|
|
@ -14,6 +14,32 @@ from utils import fileLastModified
|
|||
from utils import getLinkPrefixes
|
||||
|
||||
|
||||
def removeQuotesWithinQuotes(content: str) -> str:
|
||||
"""Removes any blockquote inside blockquote
|
||||
"""
|
||||
if '<blockquote>' not in content:
|
||||
return content
|
||||
if '</blockquote>' not in content:
|
||||
return content
|
||||
ctr = 1
|
||||
found = True
|
||||
while found:
|
||||
prefix = content.split('<blockquote>', ctr)[0] + '<blockquote>'
|
||||
quotedStr = content.split('<blockquote>', ctr)[1]
|
||||
if '</blockquote>' not in quotedStr:
|
||||
found = False
|
||||
else:
|
||||
endStr = quotedStr.split('</blockquote>')[1]
|
||||
quotedStr = quotedStr.split('</blockquote>')[0]
|
||||
if '<blockquote>' not in endStr:
|
||||
found = False
|
||||
if '<blockquote>' in quotedStr:
|
||||
quotedStr = quotedStr.replace('<blockquote>', '')
|
||||
content = prefix + quotedStr + '</blockquote>' + endStr
|
||||
ctr += 1
|
||||
return content
|
||||
|
||||
|
||||
def htmlReplaceEmailQuote(content: str) -> str:
|
||||
"""Replaces an email style quote "> Some quote" with html blockquote
|
||||
"""
|
||||
|
|
@ -44,9 +70,12 @@ def htmlReplaceEmailQuote(content: str) -> str:
|
|||
newContent += '<p>' + lineStr + '</p>'
|
||||
else:
|
||||
lineStr = lineStr.replace('>> ', '><blockquote>')
|
||||
if lineStr.startswith('>'):
|
||||
lineStr = lineStr.replace('>', '<blockquote>', 1)
|
||||
else:
|
||||
lineStr = lineStr.replace('>', '<br>')
|
||||
newContent += '<p>' + lineStr + '</blockquote></p>'
|
||||
return newContent
|
||||
return removeQuotesWithinQuotes(newContent)
|
||||
|
||||
|
||||
def htmlReplaceQuoteMarks(content: str) -> str:
|
||||
|
|
|
|||
424
daemon.py
|
|
@ -61,6 +61,7 @@ from person import removeAccount
|
|||
from person import canRemovePost
|
||||
from person import personSnooze
|
||||
from person import personUnsnooze
|
||||
from posts import isModerator
|
||||
from posts import mutePost
|
||||
from posts import unmutePost
|
||||
from posts import createQuestionPost
|
||||
|
|
@ -144,6 +145,8 @@ from webinterface import htmlSearchEmojiTextEntry
|
|||
from webinterface import htmlUnfollowConfirm
|
||||
from webinterface import htmlProfileAfterSearch
|
||||
from webinterface import htmlEditProfile
|
||||
from webinterface import htmlEditLinks
|
||||
from webinterface import htmlEditNewswire
|
||||
from webinterface import htmlTermsOfService
|
||||
from webinterface import htmlSkillsSearch
|
||||
from webinterface import htmlHistorySearch
|
||||
|
|
@ -200,6 +203,7 @@ from followingCalendar import removePersonFromCalendar
|
|||
from devices import E2EEdevicesCollection
|
||||
from devices import E2EEvalidDevice
|
||||
from devices import E2EEaddDevice
|
||||
from newswire import getRSSfromDict
|
||||
import os
|
||||
|
||||
|
||||
|
|
@ -2689,6 +2693,216 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
|
||||
def _linksUpdate(self, callingDomain: str, cookie: str,
|
||||
authorized: bool, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
domain: str, domainFull: str,
|
||||
onionDomain: str, i2pDomain: str, debug: bool,
|
||||
defaultTimeline: str):
|
||||
"""Updates the left links column of the timeline
|
||||
"""
|
||||
usersPath = path.replace('/linksdata', '')
|
||||
usersPath = usersPath.replace('/editlinks', '')
|
||||
actorStr = httpPrefix + '://' + domainFull + usersPath
|
||||
if ' boundary=' in self.headers['Content-type']:
|
||||
boundary = self.headers['Content-type'].split('boundary=')[1]
|
||||
if ';' in boundary:
|
||||
boundary = boundary.split(';')[0]
|
||||
|
||||
# get the nickname
|
||||
nickname = getNicknameFromActor(actorStr)
|
||||
moderator = None
|
||||
if nickname:
|
||||
moderator = isModerator(baseDir, nickname)
|
||||
if not nickname or not moderator:
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
if not nickname:
|
||||
print('WARN: nickname not found in ' + actorStr)
|
||||
else:
|
||||
print('WARN: nickname is not a moderator' + actorStr)
|
||||
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
length = int(self.headers['Content-length'])
|
||||
|
||||
# check that the POST isn't too large
|
||||
if length > self.server.maxPostLength:
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
print('Maximum links data length exceeded ' + str(length))
|
||||
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
try:
|
||||
# read the bytes of the http form POST
|
||||
postBytes = self.rfile.read(length)
|
||||
except SocketError as e:
|
||||
if e.errno == errno.ECONNRESET:
|
||||
print('WARN: connection was reset while ' +
|
||||
'reading bytes from http form POST')
|
||||
else:
|
||||
print('WARN: error while reading bytes ' +
|
||||
'from http form POST')
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
except ValueError as e:
|
||||
print('ERROR: failed to read bytes for POST')
|
||||
print(e)
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
linksFilename = baseDir + '/accounts/links.txt'
|
||||
|
||||
# extract all of the text fields into a dict
|
||||
fields = \
|
||||
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||
if fields.get('editedLinks'):
|
||||
linksStr = fields['editedLinks']
|
||||
linksFile = open(linksFilename, "w+")
|
||||
if linksFile:
|
||||
linksFile.write(linksStr)
|
||||
linksFile.close()
|
||||
else:
|
||||
if os.path.isfile(linksFilename):
|
||||
os.remove(linksFilename)
|
||||
|
||||
# redirect back to the default timeline
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
self._redirect_headers(actorStr + '/' + defaultTimeline,
|
||||
cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
|
||||
def _newswireUpdate(self, callingDomain: str, cookie: str,
|
||||
authorized: bool, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
domain: str, domainFull: str,
|
||||
onionDomain: str, i2pDomain: str, debug: bool,
|
||||
defaultTimeline: str):
|
||||
"""Updates the right newswire column of the timeline
|
||||
"""
|
||||
usersPath = path.replace('/newswiredata', '')
|
||||
usersPath = usersPath.replace('/editnewswire', '')
|
||||
actorStr = httpPrefix + '://' + domainFull + usersPath
|
||||
if ' boundary=' in self.headers['Content-type']:
|
||||
boundary = self.headers['Content-type'].split('boundary=')[1]
|
||||
if ';' in boundary:
|
||||
boundary = boundary.split(';')[0]
|
||||
|
||||
# get the nickname
|
||||
nickname = getNicknameFromActor(actorStr)
|
||||
moderator = None
|
||||
if nickname:
|
||||
moderator = isModerator(baseDir, nickname)
|
||||
if not nickname or not moderator:
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
if not nickname:
|
||||
print('WARN: nickname not found in ' + actorStr)
|
||||
else:
|
||||
print('WARN: nickname is not a moderator' + actorStr)
|
||||
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
length = int(self.headers['Content-length'])
|
||||
|
||||
# check that the POST isn't too large
|
||||
if length > self.server.maxPostLength:
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
print('Maximum newswire data length exceeded ' + str(length))
|
||||
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
try:
|
||||
# read the bytes of the http form POST
|
||||
postBytes = self.rfile.read(length)
|
||||
except SocketError as e:
|
||||
if e.errno == errno.ECONNRESET:
|
||||
print('WARN: connection was reset while ' +
|
||||
'reading bytes from http form POST')
|
||||
else:
|
||||
print('WARN: error while reading bytes ' +
|
||||
'from http form POST')
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
except ValueError as e:
|
||||
print('ERROR: failed to read bytes for POST')
|
||||
print(e)
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
newswireFilename = baseDir + '/accounts/newswire.txt'
|
||||
|
||||
# extract all of the text fields into a dict
|
||||
fields = \
|
||||
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||
if fields.get('editedNewswire'):
|
||||
newswireStr = fields['editedNewswire']
|
||||
newswireFile = open(newswireFilename, "w+")
|
||||
if newswireFile:
|
||||
newswireFile.write(newswireStr)
|
||||
newswireFile.close()
|
||||
else:
|
||||
if os.path.isfile(newswireFilename):
|
||||
os.remove(newswireFilename)
|
||||
|
||||
# redirect back to the default timeline
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
self._redirect_headers(actorStr + '/' + defaultTimeline,
|
||||
cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
|
||||
def _profileUpdate(self, callingDomain: str, cookie: str,
|
||||
authorized: bool, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
|
|
@ -2765,7 +2979,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
actorChanged = True
|
||||
profileMediaTypes = ('avatar', 'image',
|
||||
'banner', 'search_banner',
|
||||
'instanceLogo')
|
||||
'instanceLogo',
|
||||
'left_col_image', 'right_col_image')
|
||||
profileMediaTypesUploaded = {}
|
||||
for mType in profileMediaTypes:
|
||||
if debug:
|
||||
|
|
@ -2834,8 +3049,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
|
||||
# extract all of the text fields into a dict
|
||||
fields = \
|
||||
extractTextFieldsInPOST(postBytes, boundary,
|
||||
debug)
|
||||
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||
if debug:
|
||||
if fields:
|
||||
print('DEBUG: profile update text ' +
|
||||
|
|
@ -3205,10 +3419,14 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.defaultTimeline = 'inbox'
|
||||
if fields['mediaInstance'] == 'on':
|
||||
self.server.mediaInstance = True
|
||||
self.server.blogsInstance = False
|
||||
self.server.defaultTimeline = 'tlmedia'
|
||||
setConfigParam(baseDir,
|
||||
"mediaInstance",
|
||||
self.server.mediaInstance)
|
||||
setConfigParam(baseDir,
|
||||
"blogsInstance",
|
||||
self.server.blogsInstance)
|
||||
else:
|
||||
if self.server.mediaInstance:
|
||||
self.server.mediaInstance = False
|
||||
|
|
@ -3223,10 +3441,14 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.defaultTimeline = 'inbox'
|
||||
if fields['blogsInstance'] == 'on':
|
||||
self.server.blogsInstance = True
|
||||
self.server.mediaInstance = False
|
||||
self.server.defaultTimeline = 'tlblogs'
|
||||
setConfigParam(baseDir,
|
||||
"blogsInstance",
|
||||
self.server.blogsInstance)
|
||||
setConfigParam(baseDir,
|
||||
"mediaInstance",
|
||||
self.server.mediaInstance)
|
||||
else:
|
||||
if self.server.blogsInstance:
|
||||
self.server.blogsInstance = False
|
||||
|
|
@ -3733,6 +3955,42 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
path + ' ' + callingDomain)
|
||||
self._404()
|
||||
|
||||
def _getNewswireFeed(self, authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
domain: str, port: int, proxyType: str,
|
||||
GETstartTime, GETtimings: {},
|
||||
debug: bool):
|
||||
"""Returns the newswire feed
|
||||
"""
|
||||
if not self.server.session:
|
||||
print('Starting new session during RSS request')
|
||||
self.server.session = \
|
||||
createSession(proxyType)
|
||||
if not self.server.session:
|
||||
print('ERROR: GET failed to create session ' +
|
||||
'during RSS request')
|
||||
self._404()
|
||||
return
|
||||
|
||||
msg = getRSSfromDict(self.server.baseDir, self.server.newswire,
|
||||
self.server.httpPrefix,
|
||||
self.server.domainFull,
|
||||
'Newswire', self.server.translate)
|
||||
if msg:
|
||||
msg = msg.encode('utf-8')
|
||||
self._set_headers('text/xml', len(msg),
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
if debug:
|
||||
print('Sent rss2 newswire feed: ' +
|
||||
path + ' ' + callingDomain)
|
||||
return
|
||||
if debug:
|
||||
print('Failed to get rss2 newswire feed: ' +
|
||||
path + ' ' + callingDomain)
|
||||
self._404()
|
||||
|
||||
def _getRSS3feed(self, authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
|
|
@ -7047,6 +7305,47 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._404()
|
||||
return True
|
||||
|
||||
def _columImage(self, side: str, callingDomain: str, path: str,
|
||||
baseDir: str, domain: str, port: int,
|
||||
GETstartTime, GETtimings: {}) -> bool:
|
||||
"""Shows an image at the top of the left/right column
|
||||
"""
|
||||
nickname = getNicknameFromActor(path)
|
||||
if not nickname:
|
||||
self._404()
|
||||
return True
|
||||
bannerFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/' + side + '_col_image.png'
|
||||
if os.path.isfile(bannerFilename):
|
||||
if self._etag_exists(bannerFilename):
|
||||
# The file has not changed
|
||||
self._304()
|
||||
return True
|
||||
|
||||
tries = 0
|
||||
mediaBinary = None
|
||||
while tries < 5:
|
||||
try:
|
||||
with open(bannerFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
break
|
||||
except Exception as e:
|
||||
print(e)
|
||||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(bannerFilename, 'image/png',
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'account qrcode done',
|
||||
side + ' col image')
|
||||
return True
|
||||
self._404()
|
||||
return True
|
||||
|
||||
def _showBackgroundImage(self, callingDomain: str, path: str,
|
||||
baseDir: str,
|
||||
GETstartTime, GETtimings: {}) -> bool:
|
||||
|
|
@ -7307,6 +7606,50 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return True
|
||||
return False
|
||||
|
||||
def _editLinks(self, callingDomain: str, path: str,
|
||||
translate: {}, baseDir: str,
|
||||
httpPrefix: str, domain: str, port: int,
|
||||
cookie: str) -> bool:
|
||||
"""Show the links from the left column
|
||||
"""
|
||||
if '/users/' in path and path.endswith('/editlinks'):
|
||||
msg = htmlEditLinks(translate,
|
||||
baseDir,
|
||||
path, domain,
|
||||
port,
|
||||
httpPrefix).encode('utf-8')
|
||||
if msg:
|
||||
self._set_headers('text/html', len(msg),
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
return False
|
||||
|
||||
def _editNewswire(self, callingDomain: str, path: str,
|
||||
translate: {}, baseDir: str,
|
||||
httpPrefix: str, domain: str, port: int,
|
||||
cookie: str) -> bool:
|
||||
"""Show the newswire from the right column
|
||||
"""
|
||||
if '/users/' in path and path.endswith('/editnewswire'):
|
||||
msg = htmlEditNewswire(translate,
|
||||
baseDir,
|
||||
path, domain,
|
||||
port,
|
||||
httpPrefix).encode('utf-8')
|
||||
if msg:
|
||||
self._set_headers('text/html', len(msg),
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
return False
|
||||
|
||||
def _editEvent(self, callingDomain: str, path: str,
|
||||
httpPrefix: str, domain: str, domainFull: str,
|
||||
baseDir: str, translate: {},
|
||||
|
|
@ -7507,6 +7850,18 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'fonts', 'sharedInbox enabled')
|
||||
|
||||
if self.path == '/newswire.xml':
|
||||
self._getNewswireFeed(authorized,
|
||||
callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.port,
|
||||
self.server.proxyType,
|
||||
GETstartTime, GETtimings,
|
||||
self.server.debug)
|
||||
return
|
||||
|
||||
# RSS 2.0
|
||||
if self.path.startswith('/blog/') and \
|
||||
self.path.endswith('/rss.xml'):
|
||||
|
|
@ -7965,8 +8320,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'account qrcode done')
|
||||
|
||||
# search screen banner image
|
||||
if '/users/' in self.path and \
|
||||
self.path.endswith('/search_banner.png'):
|
||||
if '/users/' in self.path:
|
||||
if self.path.endswith('/search_banner.png'):
|
||||
if self._searchScreenBanner(callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
self.server.domain,
|
||||
|
|
@ -7974,6 +8329,22 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
GETstartTime, GETtimings):
|
||||
return
|
||||
|
||||
if self.path.endswith('/left_col_image.png'):
|
||||
if self._columImage('left', callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
self.server.domain,
|
||||
self.server.port,
|
||||
GETstartTime, GETtimings):
|
||||
return
|
||||
|
||||
if self.path.endswith('/right_col_image.png'):
|
||||
if self._columImage('right', callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
self.server.domain,
|
||||
self.server.port,
|
||||
GETstartTime, GETtimings):
|
||||
return
|
||||
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'account qrcode done',
|
||||
'search screen banner done')
|
||||
|
|
@ -8601,6 +8972,26 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
cookie):
|
||||
return
|
||||
|
||||
# edit links from the left column of the timeline in web interface
|
||||
if self._editLinks(callingDomain, self.path,
|
||||
self.server.translate,
|
||||
self.server.baseDir,
|
||||
self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.port,
|
||||
cookie):
|
||||
return
|
||||
|
||||
# edit newswire from the right column of the timeline
|
||||
if self._editNewswire(callingDomain, self.path,
|
||||
self.server.translate,
|
||||
self.server.baseDir,
|
||||
self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.port,
|
||||
cookie):
|
||||
return
|
||||
|
||||
if self._showNewPost(callingDomain, self.path,
|
||||
self.server.mediaInstance,
|
||||
self.server.translate,
|
||||
|
|
@ -10049,6 +10440,26 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.i2pDomain, self.server.debug)
|
||||
return
|
||||
|
||||
if authorized and self.path.endswith('/linksdata'):
|
||||
self._linksUpdate(callingDomain, cookie, authorized, self.path,
|
||||
self.server.baseDir, self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.domainFull,
|
||||
self.server.onionDomain,
|
||||
self.server.i2pDomain, self.server.debug,
|
||||
self.server.defaultTimeline)
|
||||
return
|
||||
|
||||
if authorized and self.path.endswith('/newswiredata'):
|
||||
self._newswireUpdate(callingDomain, cookie, authorized, self.path,
|
||||
self.server.baseDir, self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.domainFull,
|
||||
self.server.onionDomain,
|
||||
self.server.i2pDomain, self.server.debug,
|
||||
self.server.defaultTimeline)
|
||||
return
|
||||
|
||||
self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 3)
|
||||
|
||||
# moderator action buttons
|
||||
|
|
@ -10660,6 +11071,9 @@ def runDaemon(blogsInstance: bool, mediaInstance: bool,
|
|||
httpd.unitTest = unitTest
|
||||
httpd.YTReplacementDomain = YTReplacementDomain
|
||||
|
||||
# newswire storing rss feeds
|
||||
httpd.newswire = {}
|
||||
|
||||
# This counter is used to update the list of blocked domains in memory.
|
||||
# It helps to avoid touching the disk and so improves flooding resistance
|
||||
httpd.blocklistUpdateCtr = 0
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
:root {
|
||||
--main-bg-color: #282c37;
|
||||
--column-left-color: #282c37;
|
||||
--link-bg-color: #282c37;
|
||||
--dropdown-fg-color: #dddddd;
|
||||
--dropdown-bg-color: #111;
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
--main-bg-color-report: #221c27;
|
||||
--main-header-color-roles: #282237;
|
||||
--main-fg-color: #dddddd;
|
||||
--column-left-fg-color: #dddddd;
|
||||
--main-link-color: #999;
|
||||
--main-link-color-hover: #bbb;
|
||||
--main-visited-color: #888;
|
||||
|
|
@ -21,6 +23,7 @@
|
|||
--font-size-header-mobile: 32px;
|
||||
--font-color-header: #ccc;
|
||||
--font-size-button-mobile: 34px;
|
||||
--font-size-links: 18px;
|
||||
--font-size: 30px;
|
||||
--font-size2: 24px;
|
||||
--font-size3: 38px;
|
||||
|
|
@ -61,6 +64,14 @@
|
|||
--quote-font-weight: normal;
|
||||
--quote-font-size: 120%;
|
||||
--line-spacing: 130%;
|
||||
--column-left-width: 10vw;
|
||||
--column-center-width: 80vw;
|
||||
--column-right-width: 10vw;
|
||||
--column-left-header-background: #555;
|
||||
--column-left-header-color: #fff;
|
||||
--column-left-header-size: 20px;
|
||||
--column-left-icon-size: 20%;
|
||||
--column-right-icon-size: 20%;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -84,9 +95,7 @@ body, html {
|
|||
|
||||
height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
max-width: 80%;
|
||||
min-width: 950px;
|
||||
margin: 0 auto;
|
||||
font-size: var(--font-size);
|
||||
line-height: var(--line-spacing);
|
||||
}
|
||||
|
|
@ -126,6 +135,15 @@ h1 {
|
|||
color: var(--title-color);
|
||||
}
|
||||
|
||||
h3.linksHeader {
|
||||
background-color: var(--column-left-header-background);
|
||||
color: var(--column-left-header-color);
|
||||
font-size: var(--column-left-header-size);
|
||||
text-transform: uppercase;
|
||||
padding: 4px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
a, u {
|
||||
color: var(--main-fg-color);
|
||||
}
|
||||
|
|
@ -158,15 +176,6 @@ 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%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("image.png");
|
||||
height: 50%;
|
||||
|
|
@ -923,6 +932,101 @@ aside .toggle-inside li {
|
|||
}
|
||||
|
||||
@media screen and (min-width: 400px) {
|
||||
body, html {
|
||||
background-color: var(--main-bg-color);
|
||||
color: var(--main-fg-color);
|
||||
|
||||
height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
min-width: 950px;
|
||||
font-size: var(--font-size);
|
||||
line-height: var(--line-spacing);
|
||||
}
|
||||
.timeline-banner {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png");
|
||||
height: 15%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100vw;
|
||||
position: relative;
|
||||
}
|
||||
.timeline {
|
||||
border: 0;
|
||||
width: 100vw;
|
||||
}
|
||||
.col-left a:link {
|
||||
background: var(--column-left-color);
|
||||
}
|
||||
.col-left a:visited {
|
||||
background: var(--column-left-color);
|
||||
}
|
||||
.column-left {
|
||||
background-color: var(--column-left-color);
|
||||
width: var(--column-left-width);
|
||||
}
|
||||
.col-left {
|
||||
color: var(--column-left-fg-color);
|
||||
padding: 10px 10px;
|
||||
font-size: var(--font-size-links);
|
||||
float: left;
|
||||
width: var(--column-left-width);
|
||||
}
|
||||
.col-left img.leftColEdit {
|
||||
background: var(--column-left-color);
|
||||
margin: 40px 0;
|
||||
width: var(--column-left-icon-size);
|
||||
}
|
||||
.col-left img.leftColEditImage {
|
||||
background: var(--column-left-color);
|
||||
width: var(--column-left-icon-size);
|
||||
float: right;
|
||||
}
|
||||
.col-left img.leftColImg {
|
||||
background: var(--column-left-color);
|
||||
width: 100%;
|
||||
margin: 0 0;
|
||||
padding: 0 0;
|
||||
}
|
||||
.column-center {
|
||||
width: var(--column-center-width);
|
||||
}
|
||||
.col-center {
|
||||
width: var(--column-center-width);
|
||||
background-color: var(--main-bg-color);
|
||||
}
|
||||
.col-right a:link {
|
||||
background: var(--column-left-color);
|
||||
}
|
||||
.col-right a:visited {
|
||||
background: var(--column-left-color);
|
||||
}
|
||||
.column-right {
|
||||
background-color: var(--column-left-color);
|
||||
width: var(--column-right-width);
|
||||
}
|
||||
.col-right {
|
||||
background-color: var(--column-left-color);
|
||||
color: var(--column-left-fg-color);
|
||||
padding-right: 30px;
|
||||
font-size: var(--font-size-links);
|
||||
width: var(--column-right-width);
|
||||
}
|
||||
.col-right img.rightColEdit {
|
||||
background: var(--column-left-color);
|
||||
margin: 40px 0;
|
||||
width: var(--column-right-icon-size);
|
||||
}
|
||||
.col-right img.rightColEditImage {
|
||||
background: var(--column-left-color);
|
||||
width: var(--column-right-icon-size);
|
||||
float: right;
|
||||
}
|
||||
.col-right img.rightColImg {
|
||||
background: var(--column-left-color);
|
||||
width: 100%;
|
||||
margin: 0 0;
|
||||
padding: 0 0;
|
||||
}
|
||||
.likesCount {
|
||||
font-size: var(--font-size-likes);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
|
@ -1372,6 +1476,53 @@ aside .toggle-inside li {
|
|||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
body, html {
|
||||
background-color: var(--main-bg-color);
|
||||
color: var(--main-fg-color);
|
||||
|
||||
height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
min-width: 950px;
|
||||
margin-left: 0;
|
||||
font-size: var(--font-size);
|
||||
line-height: var(--line-spacing);
|
||||
}
|
||||
.timeline-banner {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png");
|
||||
height: 6%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 145vw;
|
||||
position: relative;
|
||||
}
|
||||
.timeline {
|
||||
border: 0;
|
||||
width: 100vw;
|
||||
}
|
||||
.column-left {
|
||||
display: none;
|
||||
width: 0%;
|
||||
}
|
||||
.col-left {
|
||||
float: left;
|
||||
width: 0%;
|
||||
display: none;
|
||||
}
|
||||
.col-center {
|
||||
width: 100vw;
|
||||
}
|
||||
.col-right {
|
||||
float: right;
|
||||
width: 0%;
|
||||
display: none;
|
||||
}
|
||||
.column-right {
|
||||
display: none;
|
||||
width: 0%;
|
||||
}
|
||||
.column-center {
|
||||
width: 100%;
|
||||
}
|
||||
.likesCount {
|
||||
font-size: var(--font-size-likes-mobile);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
|
|
|||
13
epicyon.py
|
|
@ -28,6 +28,7 @@ from posts import getUserUrl
|
|||
from posts import checkDomains
|
||||
from session import createSession
|
||||
from session import getJson
|
||||
from newswire import getRSS
|
||||
from filters import addFilter
|
||||
from filters import removeFilter
|
||||
import os
|
||||
|
|
@ -176,6 +177,8 @@ parser.add_argument('--postsraw', dest='postsraw', type=str,
|
|||
help='Show raw json of posts for the given handle')
|
||||
parser.add_argument('--json', dest='json', type=str, default=None,
|
||||
help='Show the json for a given activitypub url')
|
||||
parser.add_argument('--rss', dest='rss', type=str, default=None,
|
||||
help='Show an rss feed for a given url')
|
||||
parser.add_argument('-f', '--federate', nargs='+', dest='federationList',
|
||||
help='Specify federation list separated by spaces')
|
||||
parser.add_argument("--repliesEnabled", "--commentsEnabled",
|
||||
|
|
@ -595,6 +598,12 @@ if args.json:
|
|||
pprint(testJson)
|
||||
sys.exit()
|
||||
|
||||
if args.rss:
|
||||
session = createSession(None)
|
||||
testRSS = getRSS(session, args.rss)
|
||||
pprint(testRSS)
|
||||
sys.exit()
|
||||
|
||||
# create cache for actors
|
||||
if not os.path.isdir(baseDir + '/cache'):
|
||||
os.mkdir(baseDir + '/cache')
|
||||
|
|
@ -615,11 +624,15 @@ if not args.mediainstance:
|
|||
mediaInstance = getConfigParam(baseDir, 'mediaInstance')
|
||||
if mediaInstance is not None:
|
||||
args.mediainstance = mediaInstance
|
||||
if args.mediainstance:
|
||||
args.blogsinstance = False
|
||||
|
||||
if not args.blogsinstance:
|
||||
blogsInstance = getConfigParam(baseDir, 'blogsInstance')
|
||||
if blogsInstance is not None:
|
||||
args.blogsinstance = blogsInstance
|
||||
if args.blogsinstance:
|
||||
args.mediainstance = False
|
||||
|
||||
# set the instance title in config.json
|
||||
title = getConfigParam(baseDir, 'instanceTitle')
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 29 KiB |
|
|
@ -0,0 +1,177 @@
|
|||
__filename__ = "newswire.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
import requests
|
||||
from socket import error as SocketError
|
||||
import errno
|
||||
from datetime import datetime
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
def rss2Header(httpPrefix: str,
|
||||
nickname: str, domainFull: str,
|
||||
title: str, translate: {}) -> str:
|
||||
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
|
||||
rssStr += "<rss version=\"2.0\">"
|
||||
rssStr += '<channel>'
|
||||
if title.startswith('News'):
|
||||
rssStr += ' <title>Newswire</title>'
|
||||
else:
|
||||
rssStr += ' <title>' + translate[title] + '</title>'
|
||||
if title.startswith('News'):
|
||||
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||
'/newswire.xml' + '</link>'
|
||||
else:
|
||||
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||
'/users/' + nickname + '/rss.xml' + '</link>'
|
||||
return rssStr
|
||||
|
||||
|
||||
def rss2Footer() -> str:
|
||||
rssStr = '</channel>'
|
||||
rssStr += '</rss>'
|
||||
return rssStr
|
||||
|
||||
|
||||
def xml2StrToDict(xmlStr: str) -> {}:
|
||||
"""Converts an xml 2.0 string to a dictionary
|
||||
"""
|
||||
if '<item>' not in xmlStr:
|
||||
return {}
|
||||
result = {}
|
||||
rssItems = xmlStr.split('<item>')
|
||||
for rssItem in rssItems:
|
||||
if '<title>' not in rssItem:
|
||||
continue
|
||||
if '</title>' not in rssItem:
|
||||
continue
|
||||
if '<link>' not in rssItem:
|
||||
continue
|
||||
if '</link>' not in rssItem:
|
||||
continue
|
||||
if '<pubDate>' not in rssItem:
|
||||
continue
|
||||
if '</pubDate>' not in rssItem:
|
||||
continue
|
||||
title = rssItem.split('<title>')[1]
|
||||
title = title.split('</title>')[0]
|
||||
link = rssItem.split('<link>')[1]
|
||||
link = link.split('</link>')[0]
|
||||
pubDate = rssItem.split('<pubDate>')[1]
|
||||
pubDate = pubDate.split('</pubDate>')[0]
|
||||
parsed = False
|
||||
try:
|
||||
publishedDate = \
|
||||
datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S %z")
|
||||
result[str(publishedDate)] = [title, link]
|
||||
parsed = True
|
||||
except BaseException:
|
||||
pass
|
||||
if not parsed:
|
||||
try:
|
||||
publishedDate = \
|
||||
datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S UT")
|
||||
result[str(publishedDate) + '+00:00'] = [title, link]
|
||||
parsed = True
|
||||
except BaseException:
|
||||
print('WARN: unrecognized RSS date format: ' + pubDate)
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
def xmlStrToDict(xmlStr: str) -> {}:
|
||||
"""Converts an xml string to a dictionary
|
||||
"""
|
||||
if 'rss version="2.0"' in xmlStr:
|
||||
return xml2StrToDict(xmlStr)
|
||||
return {}
|
||||
|
||||
|
||||
def getRSS(session, url: str) -> {}:
|
||||
"""Returns an RSS url as a dict
|
||||
"""
|
||||
if not isinstance(url, str):
|
||||
print('url: ' + str(url))
|
||||
print('ERROR: getRSS url should be a string')
|
||||
return None
|
||||
headers = {
|
||||
'Accept': 'text/xml; charset=UTF-8'
|
||||
}
|
||||
params = None
|
||||
sessionParams = {}
|
||||
sessionHeaders = {}
|
||||
if headers:
|
||||
sessionHeaders = headers
|
||||
if params:
|
||||
sessionParams = params
|
||||
sessionHeaders['User-Agent'] = \
|
||||
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'
|
||||
if not session:
|
||||
print('WARN: no session specified for getRSS')
|
||||
try:
|
||||
result = session.get(url, headers=sessionHeaders, params=sessionParams)
|
||||
return xmlStrToDict(result.text)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' +
|
||||
'headers: ' + str(sessionHeaders) + '\n' +
|
||||
'params: ' + str(sessionParams) + '\n')
|
||||
print(e)
|
||||
except ValueError as e:
|
||||
print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' +
|
||||
'headers: ' + str(sessionHeaders) + '\n' +
|
||||
'params: ' + str(sessionParams) + '\n')
|
||||
print(e)
|
||||
except SocketError as e:
|
||||
if e.errno == errno.ECONNRESET:
|
||||
print('WARN: connection was reset during getRSS')
|
||||
print(e)
|
||||
return None
|
||||
|
||||
|
||||
def getRSSfromDict(baseDir: str, newswire: {},
|
||||
httpPrefix: str, domainFull: str,
|
||||
title: str, translate: {}) -> str:
|
||||
"""Returns an rss feed from the current newswire dict.
|
||||
This allows other instances to subscribe to the same newswire
|
||||
"""
|
||||
rssStr = rss2Header(httpPrefix,
|
||||
None, domainFull,
|
||||
'Newswire', translate)
|
||||
for published, fields in newswire.items():
|
||||
rssStr += '<item>\n'
|
||||
rssStr += ' <title>' + fields[0] + '</title>\n'
|
||||
rssStr += ' <link>' + fields[1] + '</link>\n'
|
||||
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
|
||||
rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT")
|
||||
rssStr += ' <pubDate>' + rssDateStr + '</pubDate>\n'
|
||||
rssStr += '</item>\n'
|
||||
rssStr += rss2Footer()
|
||||
return rssStr
|
||||
|
||||
|
||||
def getDictFromNewswire(session, baseDir: str) -> {}:
|
||||
"""Gets rss feeds as a dictionary from newswire file
|
||||
"""
|
||||
subscriptionsFilename = baseDir + '/accounts/newswire.txt'
|
||||
if not os.path.isfile(subscriptionsFilename):
|
||||
return {}
|
||||
|
||||
rssFeed = []
|
||||
with open(subscriptionsFilename, 'r') as fp:
|
||||
rssFeed = fp.readlines()
|
||||
result = {}
|
||||
for url in rssFeed:
|
||||
url = url.strip()
|
||||
if '://' not in url:
|
||||
continue
|
||||
if url.startswith('#'):
|
||||
continue
|
||||
result = dict(result.items() + getRSS(session, url).items())
|
||||
sortedResult = OrderedDict(sorted(result.items(), reverse=False))
|
||||
return sortedResult
|
||||
|
|
@ -54,7 +54,7 @@ def createSession(proxyType: str):
|
|||
|
||||
|
||||
def getJson(session, url: str, headers: {}, params: {},
|
||||
version='1.0.0', httpPrefix='https',
|
||||
version='1.1.0', httpPrefix='https',
|
||||
domain='testdomain') -> {}:
|
||||
if not isinstance(url, str):
|
||||
print('url: ' + str(url))
|
||||
|
|
|
|||
23
tests.py
|
|
@ -2125,7 +2125,7 @@ def testReplaceEmailQuote():
|
|||
"<p>Some other text.</p>"
|
||||
resultStr = htmlReplaceEmailQuote(testStr)
|
||||
if resultStr != expectedStr:
|
||||
print('Result: ' + resultStr)
|
||||
print('Result: ' + str(resultStr))
|
||||
print('Expect: ' + expectedStr)
|
||||
assert resultStr == expectedStr
|
||||
|
||||
|
|
@ -2135,7 +2135,26 @@ def testReplaceEmailQuote():
|
|||
"second line</blockquote></p><p>Some question?</p>"
|
||||
resultStr = htmlReplaceEmailQuote(testStr)
|
||||
if resultStr != expectedStr:
|
||||
print('Result: ' + resultStr)
|
||||
print('Result: ' + str(resultStr))
|
||||
print('Expect: ' + expectedStr)
|
||||
assert resultStr == expectedStr
|
||||
|
||||
testStr = "<p><span class=\"h-card\">" + \
|
||||
"<a href=\"https://somedomain/@somenick\" " + \
|
||||
"class=\"u-url mention\">@<span>somenick</span>" + \
|
||||
"</a></span> </p><p>> Text1.<br />> <br />" + \
|
||||
"> Text2<br />> <br />> Text3<br />" + \
|
||||
"><br />> Text4<br />> <br />> " + \
|
||||
"Text5<br />> <br />> Text6</p><p>Text7</p>"
|
||||
expectedStr = "<p><span class=\"h-card\">" + \
|
||||
"<a href=\"https://somedomain/@somenick\" " + \
|
||||
"class=\"u-url mention\">@<span>somenick</span></a>" + \
|
||||
"</span> </p><p><blockquote> Text1.<br /><br />" + \
|
||||
"Text2<br /><br />Text3<br />><br />Text4<br />" + \
|
||||
"<br />Text5<br /><br />Text6</blockquote></p><p>Text7</p>"
|
||||
resultStr = htmlReplaceEmailQuote(testStr)
|
||||
if resultStr != expectedStr:
|
||||
print('Result: ' + str(resultStr))
|
||||
print('Expect: ' + expectedStr)
|
||||
assert resultStr == expectedStr
|
||||
|
||||
|
|
|
|||
60
theme.py
|
|
@ -15,7 +15,7 @@ from shutil import copyfile
|
|||
def getThemeFiles() -> []:
|
||||
return ('epicyon.css', 'login.css', 'follow.css',
|
||||
'suspended.css', 'calendar.css', 'blog.css',
|
||||
'options.css', 'search.css')
|
||||
'options.css', 'search.css', 'links.css')
|
||||
|
||||
|
||||
def getThemesList() -> []:
|
||||
|
|
@ -264,12 +264,17 @@ def setThemeIndymedia(baseDir: str):
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "black",
|
||||
"column-left-header-color": "#fff",
|
||||
"column-left-header-background": "#555",
|
||||
"column-left-header-size": "20px",
|
||||
"column-left-color": "#003366",
|
||||
"text-entry-background": "#0f0d10",
|
||||
"link-bg-color": "black",
|
||||
"main-link-color": "#ff9900",
|
||||
"main-link-color-hover": "#d09338",
|
||||
"main-visited-color": "#ffb900",
|
||||
"main-fg-color": "white",
|
||||
"column-left-fg-color": "white",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
"border-color": "#003366",
|
||||
"border-width": "0",
|
||||
|
|
@ -292,6 +297,10 @@ def setThemeIndymedia(baseDir: str):
|
|||
"title-text": "white",
|
||||
"title-background": "#003366",
|
||||
"quote-right-margin": "0.1em",
|
||||
"column-left-width": "10vw",
|
||||
"column-center-width": "70vw",
|
||||
"column-right-width": "20vw",
|
||||
"column-right-icon-size": "11%"
|
||||
}
|
||||
setThemeFromDict(baseDir, name, themeParams, bgParams)
|
||||
|
||||
|
|
@ -311,6 +320,7 @@ def setThemeBlue(baseDir: str):
|
|||
"gallery-font-size": "35px",
|
||||
"gallery-font-size-mobile": "55px",
|
||||
"main-bg-color": "#002365",
|
||||
"column-left-color": "#002365",
|
||||
"text-entry-background": "#002365",
|
||||
"link-bg-color": "#002365",
|
||||
"main-bg-color-reply": "#002365",
|
||||
|
|
@ -348,11 +358,13 @@ def setThemeNight(baseDir: str):
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#0f0d10",
|
||||
"column-left-color": "#0f0d10",
|
||||
"text-entry-background": "#0f0d10",
|
||||
"link-bg-color": "#0f0d10",
|
||||
"main-link-color": "ff9900",
|
||||
"main-link-color-hover": "#d09338",
|
||||
"main-fg-color": "#a961ab",
|
||||
"column-left-fg-color": "#a961ab",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
"border-color": "#606984",
|
||||
"main-bg-color-reply": "#0f0d10",
|
||||
|
|
@ -398,6 +410,7 @@ def setThemeStarlight(baseDir: str):
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#0f0d10",
|
||||
"column-left-color": "#0f0d10",
|
||||
"text-entry-background": "#0f0d10",
|
||||
"link-bg-color": "#0f0d10",
|
||||
"main-link-color": "#ffc4bc",
|
||||
|
|
@ -405,6 +418,7 @@ def setThemeStarlight(baseDir: str):
|
|||
"title-color": "#ffc4bc",
|
||||
"main-visited-color": "#e1c4bc",
|
||||
"main-fg-color": "#ffc4bc",
|
||||
"column-left-fg-color": "#ffc4bc",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
"border-color": "#69282c",
|
||||
"border-width": "3px",
|
||||
|
|
@ -457,6 +471,7 @@ def setThemeHenge(baseDir: str):
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#383335",
|
||||
"column-left-color": "#383335",
|
||||
"text-entry-background": "#383335",
|
||||
"link-bg-color": "#383335",
|
||||
"main-link-color": "white",
|
||||
|
|
@ -464,6 +479,7 @@ def setThemeHenge(baseDir: str):
|
|||
"title-color": "white",
|
||||
"main-visited-color": "#e1c4bc",
|
||||
"main-fg-color": "white",
|
||||
"column-left-fg-color": "white",
|
||||
"main-bg-color-dm": "#343335",
|
||||
"border-color": "#222",
|
||||
"border-width": "5px",
|
||||
|
|
@ -506,6 +522,7 @@ def setThemeZen(baseDir: str):
|
|||
setThemeInConfig(baseDir, name)
|
||||
themeParams = {
|
||||
"main-bg-color": "#5c4e41",
|
||||
"column-left-color": "#5c4e41",
|
||||
"text-entry-background": "#5c4e41",
|
||||
"link-bg-color": "#5c4e41",
|
||||
"main-bg-color-reply": "#5c4e41",
|
||||
|
|
@ -565,6 +582,7 @@ def setThemeLCD(baseDir: str):
|
|||
name = 'lcd'
|
||||
themeParams = {
|
||||
"main-bg-color": "#9fb42b",
|
||||
"column-left-color": "#9fb42b",
|
||||
"link-bg-color": "#33390d",
|
||||
"text-entry-foreground": "#33390d",
|
||||
"text-entry-background": "#9fb42b",
|
||||
|
|
@ -573,6 +591,7 @@ def setThemeLCD(baseDir: str):
|
|||
"main-bg-color-dm": "#5fb42b",
|
||||
"main-header-color-roles": "#9fb42b",
|
||||
"main-fg-color": "#33390d",
|
||||
"column-left-fg-color": "#33390d",
|
||||
"border-color": "#33390d",
|
||||
"border-width": "5px",
|
||||
"main-link-color": "#9fb42b",
|
||||
|
|
@ -641,11 +660,13 @@ def setThemePurple(baseDir: str):
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"main-bg-color": "#1f152d",
|
||||
"column-left-color": "#1f152d",
|
||||
"link-bg-color": "#1f152d",
|
||||
"main-bg-color-reply": "#1a142d",
|
||||
"main-bg-color-report": "#12152d",
|
||||
"main-header-color-roles": "#1f192d",
|
||||
"main-fg-color": "#f98bb0",
|
||||
"column-left-fg-color": "#f98bb0",
|
||||
"border-color": "#3f2145",
|
||||
"main-link-color": "#ff42a0",
|
||||
"main-link-color-hover": "white",
|
||||
|
|
@ -689,12 +710,14 @@ def setThemeHacker(baseDir: str):
|
|||
themeParams = {
|
||||
"focus-color": "green",
|
||||
"main-bg-color": "black",
|
||||
"column-left-color": "black",
|
||||
"link-bg-color": "black",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
"main-bg-color-reply": "#030202",
|
||||
"main-bg-color-report": "#050202",
|
||||
"main-header-color-roles": "#1f192d",
|
||||
"main-fg-color": "#00ff00",
|
||||
"column-left-fg-color": "#00ff00",
|
||||
"border-color": "#035103",
|
||||
"main-link-color": "#2fff2f",
|
||||
"main-link-color-hover": "#afff2f",
|
||||
|
|
@ -747,6 +770,7 @@ def setThemeLight(baseDir: str):
|
|||
"font-size4": "24px",
|
||||
"font-size5": "22px",
|
||||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||
"column-left-color": "#e6ebf0",
|
||||
"main-bg-color": "#e6ebf0",
|
||||
"main-bg-color-dm": "#e3dbf0",
|
||||
"link-bg-color": "#e6ebf0",
|
||||
|
|
@ -754,6 +778,7 @@ def setThemeLight(baseDir: str):
|
|||
"main-bg-color-report": "#e3dbf0",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"column-left-fg-color": "#2d2c37",
|
||||
"border-color": "#c0cdd9",
|
||||
"main-link-color": "#2a2c37",
|
||||
"main-link-color-hover": "#aa2c37",
|
||||
|
|
@ -804,12 +829,14 @@ def setThemeSolidaric(baseDir: str):
|
|||
"font-size5": "22px",
|
||||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||
"main-bg-color": "white",
|
||||
"column-left-color": "white",
|
||||
"main-bg-color-dm": "white",
|
||||
"link-bg-color": "white",
|
||||
"main-bg-color-reply": "white",
|
||||
"main-bg-color-report": "white",
|
||||
"main-header-color-roles": "#ebebf0",
|
||||
"main-fg-color": "#2d2c37",
|
||||
"column-left-fg-color": "#2d2c37",
|
||||
"border-color": "#c0cdd9",
|
||||
"main-link-color": "#2a2c37",
|
||||
"main-link-color-hover": "#aa2c37",
|
||||
|
|
@ -864,6 +891,10 @@ def setThemeImages(baseDir: str, name: str) -> None:
|
|||
baseDir + '/img/banner.png'
|
||||
searchBannerFilename = \
|
||||
baseDir + '/img/search_banner.png'
|
||||
leftColImageFilename = \
|
||||
baseDir + '/img/left_col_image.png'
|
||||
rightColImageFilename = \
|
||||
baseDir + '/img/right_col_image.png'
|
||||
else:
|
||||
profileImageFilename = \
|
||||
baseDir + '/img/image_' + themeNameLower + '.png'
|
||||
|
|
@ -871,6 +902,10 @@ def setThemeImages(baseDir: str, name: str) -> None:
|
|||
baseDir + '/img/banner_' + themeNameLower + '.png'
|
||||
searchBannerFilename = \
|
||||
baseDir + '/img/search_banner_' + themeNameLower + '.png'
|
||||
leftColImageFilename = \
|
||||
baseDir + '/img/left_col_image_' + themeNameLower + '.png'
|
||||
rightColImageFilename = \
|
||||
baseDir + '/img/right_col_image_' + themeNameLower + '.png'
|
||||
|
||||
backgroundNames = ('login', 'shares', 'delete', 'follow',
|
||||
'options', 'block', 'search', 'calendar')
|
||||
|
|
@ -937,6 +972,29 @@ def setThemeImages(baseDir: str, name: str) -> None:
|
|||
except BaseException:
|
||||
pass
|
||||
|
||||
try:
|
||||
if os.path.isfile(leftColImageFilename):
|
||||
copyfile(leftColImageFilename,
|
||||
accountDir + '/left_col_image.png')
|
||||
else:
|
||||
if os.path.isfile(accountDir +
|
||||
'/left_col_image.png'):
|
||||
os.remove(accountDir + '/left_col_image.png')
|
||||
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
try:
|
||||
if os.path.isfile(rightColImageFilename):
|
||||
copyfile(rightColImageFilename,
|
||||
accountDir + '/right_col_image.png')
|
||||
else:
|
||||
if os.path.isfile(accountDir +
|
||||
'/right_col_image.png'):
|
||||
os.remove(accountDir + '/right_col_image.png')
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
|
||||
def setTheme(baseDir: str, name: str) -> bool:
|
||||
result = False
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "علامات التجزئة المُنشأة تلقائيًا",
|
||||
"Autogenerated Content Warnings": "تحذيرات المحتوى المُنشأ تلقائيًا",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag محظور"
|
||||
"Hashtag Blocked": "Hashtag محظور",
|
||||
"This is a blogging instance": "هذا مثال على المدونات",
|
||||
"Edit Links": "تحرير الارتباطات",
|
||||
"One link per line. Description followed by the link.": "رابط واحد في كل سطر. الوصف متبوع بالرابط.",
|
||||
"Left column image": "صورة العمود الأيسر",
|
||||
"Right column image": "صورة العمود الأيمن",
|
||||
"RSS feed for this site": "تغذية RSS لهذا الموقع",
|
||||
"Edit newswire": "تحرير الأخبار",
|
||||
"Add RSS feed links below.": "إضافة روابط تغذية RSS أدناه.",
|
||||
"Newswire RSS Feed": "Newswire موجز RSS"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Hashtags autogenerats",
|
||||
"Autogenerated Content Warnings": "Advertiments de contingut autogenerats",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag bloquejat"
|
||||
"Hashtag Blocked": "Hashtag bloquejat",
|
||||
"This is a blogging instance": "Aquesta és una instància de blocs",
|
||||
"Edit Links": "Edita els enllaços",
|
||||
"One link per line. Description followed by the link.": "Un enllaç per línia. Descripció seguida de l'enllaç.",
|
||||
"Left column image": "Imatge de la columna esquerra",
|
||||
"Right column image": "Imatge de la columna dreta",
|
||||
"RSS feed for this site": "Feed RSS per a aquest lloc",
|
||||
"Edit newswire": "Editeu newswire",
|
||||
"Add RSS feed links below.": "Afegiu enllaços de canals RSS a continuació.",
|
||||
"Newswire RSS Feed": "Feed RSS de Newswire"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Hashtags awtogeneiddiedig",
|
||||
"Autogenerated Content Warnings": "Rhybuddion Cynnwys Autogenerated",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag wedi'i Blocio"
|
||||
"Hashtag Blocked": "Hashtag wedi'i Blocio",
|
||||
"This is a blogging instance": "Dyma enghraifft blogio",
|
||||
"Edit Links": "Golygu Dolenni",
|
||||
"One link per line. Description followed by the link.": "Un dolen y llinell. Disgrifiad wedi'i ddilyn gan y ddolen.",
|
||||
"Left column image": "Delwedd colofn chwith",
|
||||
"Right column image": "Delwedd colofn dde",
|
||||
"RSS feed for this site": "Porthiant RSS ar gyfer y wefan hon",
|
||||
"Edit newswire": "Golygu newyddion",
|
||||
"Add RSS feed links below.": "Ychwanegwch ddolenni porthiant RSS isod.",
|
||||
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Automatisch generierte Hashtags",
|
||||
"Autogenerated Content Warnings": "Warnungen vor automatisch generierten Inhalten",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag blockiert"
|
||||
"Hashtag Blocked": "Hashtag blockiert",
|
||||
"This is a blogging instance": "Dies ist eine Blogging-Instanz",
|
||||
"Edit Links": "Links bearbeiten",
|
||||
"One link per line. Description followed by the link.": "Ein Link pro Zeile. Beschreibung gefolgt vom Link.",
|
||||
"Left column image": "Bild in der linken Spalte",
|
||||
"Right column image": "Bild in der rechten Spalte",
|
||||
"RSS feed for this site": "RSS-Feed für diese Site",
|
||||
"Edit newswire": "Newswire bearbeiten",
|
||||
"Add RSS feed links below.": "Fügen Sie unten RSS-Feed-Links hinzu.",
|
||||
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Autogenerated Hashtags",
|
||||
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag Blocked"
|
||||
"Hashtag Blocked": "Hashtag Blocked",
|
||||
"This is a blogging instance": "This is a blogging instance",
|
||||
"Edit Links": "Edit Links",
|
||||
"One link per line. Description followed by the link.": "One link per line. Description followed by the link. Titles should begin with #",
|
||||
"Left column image": "Left column image",
|
||||
"Right column image": "Right column image",
|
||||
"RSS feed for this site": "RSS feed for this site",
|
||||
"Edit newswire": "Edit newswire",
|
||||
"Add RSS feed links below.": "Add RSS feed links below.",
|
||||
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Hashtags autogenerados",
|
||||
"Autogenerated Content Warnings": "Advertencias de contenido generado automáticamente",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag bloqueada"
|
||||
"Hashtag Blocked": "Hashtag bloqueada",
|
||||
"This is a blogging instance": "Esta es una instancia de blogs",
|
||||
"Edit Links": "Editar enlaces",
|
||||
"One link per line. Description followed by the link.": "Un enlace por línea. Descripción seguida del enlace.",
|
||||
"Left column image": "Imagen de la columna izquierda",
|
||||
"Right column image": "Imagen de la columna derecha",
|
||||
"RSS feed for this site": "Fuente RSS para este sitio",
|
||||
"Edit newswire": "Editar newswire",
|
||||
"Add RSS feed links below.": "Agregue los enlaces de fuentes RSS a continuación.",
|
||||
"Newswire RSS Feed": "Canal RSS de Newswire"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Hashtags générés automatiquement",
|
||||
"Autogenerated Content Warnings": "Avertissements de contenu générés automatiquement",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag bloqué"
|
||||
"Hashtag Blocked": "Hashtag bloqué",
|
||||
"This is a blogging instance": "Ceci est une instance de blog",
|
||||
"Edit Links": "Modifier les liens",
|
||||
"One link per line. Description followed by the link.": "Un lien par ligne. Description suivie du lien.",
|
||||
"Left column image": "Image de la colonne de gauche",
|
||||
"Right column image": "Image de la colonne de droite",
|
||||
"RSS feed for this site": "Flux RSS de ce site",
|
||||
"Edit newswire": "Modifier le fil d'actualité",
|
||||
"Add RSS feed links below.": "Ajoutez des liens de flux RSS ci-dessous.",
|
||||
"Newswire RSS Feed": "Flux RSS de Newswire"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Hashtags uathghinte",
|
||||
"Autogenerated Content Warnings": "Rabhaidh Ábhar Uathghinte",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag Blocáilte"
|
||||
"Hashtag Blocked": "Hashtag Blocáilte",
|
||||
"This is a blogging instance": "Seo sampla blagála",
|
||||
"Edit Links": "Cuir Naisc in eagar",
|
||||
"One link per line. Description followed by the link.": "Nasc amháin in aghaidh an líne. Cur síos agus an nasc ina dhiaidh sin.",
|
||||
"Left column image": "Íomhá colún ar chlé",
|
||||
"Right column image": "Íomhá colún ar dheis",
|
||||
"RSS feed for this site": "Fotha RSS don láithreán seo",
|
||||
"Edit newswire": "Cuir sreang nuachta in eagar",
|
||||
"Add RSS feed links below.": "Cuir naisc beatha RSS thíos.",
|
||||
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "ऑटोजेनरेटेड हैशटैग",
|
||||
"Autogenerated Content Warnings": "स्वतः प्राप्त सामग्री चेतावनी",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "हैशटैग अवरुद्ध"
|
||||
"Hashtag Blocked": "हैशटैग अवरुद्ध",
|
||||
"This is a blogging instance": "यह एक ब्लॉगिंग उदाहरण है",
|
||||
"Edit Links": "लिंक संपादित करें",
|
||||
"One link per line. Description followed by the link.": "प्रति पंक्ति एक लिंक। लिंक के बाद विवरण।",
|
||||
"Left column image": "बाएं स्तंभ की छवि",
|
||||
"Right column image": "राइट कॉलम छवि",
|
||||
"RSS feed for this site": "इस साइट के लिए आरएसएस फ़ीड",
|
||||
"Edit newswire": "नवांश संपादित करें",
|
||||
"Add RSS feed links below.": "नीचे आरएसएस फ़ीड लिंक जोड़ें।",
|
||||
"Newswire RSS Feed": "Newswire RSS फ़ीड"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Hashtag generati automaticamente",
|
||||
"Autogenerated Content Warnings": "Avvisi sui contenuti generati automaticamente",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag bloccato"
|
||||
"Hashtag Blocked": "Hashtag bloccato",
|
||||
"This is a blogging instance": "Questa è un'istanza di blog",
|
||||
"Edit Links": "Modifica collegamenti",
|
||||
"One link per line. Description followed by the link.": "Un collegamento per riga. Descrizione seguita dal collegamento.",
|
||||
"Left column image": "Immagine della colonna di sinistra",
|
||||
"Right column image": "Immagine della colonna di destra",
|
||||
"RSS feed for this site": "Feed RSS per questo sito",
|
||||
"Edit newswire": "Modifica newswire",
|
||||
"Add RSS feed links below.": "Aggiungi i link ai feed RSS di seguito.",
|
||||
"Newswire RSS Feed": "Feed RSS di Newswire"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "自動生成されたハッシュタグ",
|
||||
"Autogenerated Content Warnings": "自動生成されたコンテンツの警告",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "ハッシュタグがブロックされました"
|
||||
"Hashtag Blocked": "ハッシュタグがブロックされました",
|
||||
"This is a blogging instance": "これはブログのインスタンスです",
|
||||
"Edit Links": "リンクの編集",
|
||||
"One link per line. Description followed by the link.": "1行に1つのリンク。 説明の後にリンクが続きます。",
|
||||
"Left column image": "左の列の画像",
|
||||
"Right column image": "右の列の画像",
|
||||
"RSS feed for this site": "このサイトのRSSフィード",
|
||||
"Edit newswire": "ニュースワイヤーを編集",
|
||||
"Add RSS feed links below.": "以下にRSSフィードリンクを追加します。",
|
||||
"Newswire RSS Feed": "NewswireRSSフィード"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,5 +283,14 @@
|
|||
"Autogenerated Hashtags": "Autogenerated Hashtags",
|
||||
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag Blocked"
|
||||
"Hashtag Blocked": "Hashtag Blocked",
|
||||
"This is a blogging instance": "This is a blogging instance",
|
||||
"Edit Links": "Edit Links",
|
||||
"One link per line. Description followed by the link.": "One link per line. Description followed by the link. Titles should begin with #",
|
||||
"Left column image": "Left column image",
|
||||
"Right column image": "Right column image",
|
||||
"RSS feed for this site": "RSS feed for this site",
|
||||
"Edit newswire": "Edit newswire",
|
||||
"Add RSS feed links below.": "Add RSS feed links below.",
|
||||
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Hashtags autogeradas",
|
||||
"Autogenerated Content Warnings": "Avisos de conteúdo gerado automaticamente",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Hashtag bloqueada"
|
||||
"Hashtag Blocked": "Hashtag bloqueada",
|
||||
"This is a blogging instance": "Esta é uma instância de blog",
|
||||
"Edit Links": "Editar Links",
|
||||
"One link per line. Description followed by the link.": "Um link por linha. Descrição seguida pelo link.",
|
||||
"Left column image": "Imagem da coluna esquerda",
|
||||
"Right column image": "Imagem da coluna direita",
|
||||
"RSS feed for this site": "Feed RSS para este site",
|
||||
"Edit newswire": "Editar notícias",
|
||||
"Add RSS feed links below.": "Adicione links de feed RSS abaixo.",
|
||||
"Newswire RSS Feed": "Feed RSS da Newswire"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "Автоматически сгенерированные хештеги",
|
||||
"Autogenerated Content Warnings": "Автоматические предупреждения о содержании",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "Хештег заблокирован"
|
||||
"Hashtag Blocked": "Хештег заблокирован",
|
||||
"This is a blogging instance": "Это экземпляр блога",
|
||||
"Edit Links": "Редактировать ссылки",
|
||||
"One link per line. Description followed by the link.": "По одной ссылке в строке. Описание с последующей ссылкой.",
|
||||
"Left column image": "Изображение в левом столбце",
|
||||
"Right column image": "Изображение в правом столбце",
|
||||
"RSS feed for this site": "RSS-канал для этого сайта",
|
||||
"Edit newswire": "Редактировать ленту новостей",
|
||||
"Add RSS feed links below.": "Добавьте ссылки на RSS-канал ниже.",
|
||||
"Newswire RSS Feed": "Лента новостей RSS"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,5 +287,14 @@
|
|||
"Autogenerated Hashtags": "自动生成的标签",
|
||||
"Autogenerated Content Warnings": "自动生成的内容警告",
|
||||
"Indymedia": "Indymedia",
|
||||
"Hashtag Blocked": "标签被阻止"
|
||||
"Hashtag Blocked": "标签被阻止",
|
||||
"This is a blogging instance": "这是一个博客实例",
|
||||
"Edit Links": "编辑连结",
|
||||
"One link per line. Description followed by the link.": "每行一个链接。 描述,然后是链接。",
|
||||
"Left column image": "左栏图片",
|
||||
"Right column image": "右栏图片",
|
||||
"RSS feed for this site": "该站点的RSS feed",
|
||||
"Edit newswire": "编辑新闻专线",
|
||||
"Add RSS feed links below.": "在下面添加RSS feed链接。",
|
||||
"Newswire RSS Feed": "Newswire RSS提要"
|
||||
}
|
||||
|
|
|
|||
575
webinterface.py
|
|
@ -104,7 +104,7 @@ def getContentWarningButton(postID: str, translate: {},
|
|||
return ' <details><summary><b>' + \
|
||||
translate['SHOW MORE'] + '</b></summary>' + \
|
||||
'<div id="' + postID + '">' + content + \
|
||||
'</div></details>'
|
||||
'</div></details>\n'
|
||||
|
||||
|
||||
def getBlogAddress(actorJson: {}) -> str:
|
||||
|
|
@ -581,7 +581,8 @@ def htmlSearchSharedItems(translate: {},
|
|||
'name="searchtext" value="' + \
|
||||
searchStrLower + '"><br>\n'
|
||||
sharedItemsForm += \
|
||||
' <center><a href="' + actor + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + actor + \
|
||||
'" type="submit" name="submitSearch">\n'
|
||||
sharedItemsForm += \
|
||||
' <img loading="lazy" ' + \
|
||||
|
|
@ -614,7 +615,8 @@ def htmlSearchSharedItems(translate: {},
|
|||
'name="searchtext" value="' + \
|
||||
searchStrLower + '"><br>\n'
|
||||
sharedItemsForm += \
|
||||
' <center><a href="' + actor + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + actor + \
|
||||
'" type="submit" name="submitSearch">\n'
|
||||
sharedItemsForm += \
|
||||
' <img loading="lazy" ' + \
|
||||
|
|
@ -777,13 +779,14 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
|
|||
if startIndex > 0:
|
||||
# previous page link
|
||||
hashtagSearchForm += \
|
||||
'<center><a href="/tags/' + hashtag + '?page=' + \
|
||||
' <center>\n' + \
|
||||
' <a href="/tags/' + hashtag + '?page=' + \
|
||||
str(pageNumber - 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||
iconsDir + '/pageup.png" title="' + \
|
||||
translate['Page up'] + \
|
||||
'" alt="' + translate['Page up'] + \
|
||||
'"></a></center>\n'
|
||||
'"></a>\n </center>\n'
|
||||
index = startIndex
|
||||
while index <= endIndex:
|
||||
postId = lines[index].strip('\n').strip('\r')
|
||||
|
|
@ -832,11 +835,13 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
|
|||
if endIndex < noOfLines - 1:
|
||||
# next page link
|
||||
hashtagSearchForm += \
|
||||
'<center><a href="/tags/' + hashtag + \
|
||||
' <center>\n' + \
|
||||
' <a href="/tags/' + hashtag + \
|
||||
'?page=' + str(pageNumber + 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + iconsDir + \
|
||||
'/pagedown.png" title="' + translate['Page down'] + \
|
||||
'" alt="' + translate['Page down'] + '"></a></center>'
|
||||
'" alt="' + translate['Page down'] + '"></a>' + \
|
||||
' </center>'
|
||||
hashtagSearchForm += htmlFooter()
|
||||
return hashtagSearchForm
|
||||
|
||||
|
|
@ -1201,6 +1206,142 @@ def scheduledPostsExist(baseDir: str, nickname: str, domain: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def htmlEditLinks(translate: {}, baseDir: str, path: str,
|
||||
domain: str, port: int, httpPrefix: str) -> str:
|
||||
"""Shows the edit links screen
|
||||
"""
|
||||
if '/users/' not in path:
|
||||
return ''
|
||||
pathOriginal = path
|
||||
path = path.replace('/inbox', '').replace('/outbox', '')
|
||||
path = path.replace('/shares', '')
|
||||
|
||||
nickname = getNicknameFromActor(path)
|
||||
if not nickname:
|
||||
return ''
|
||||
|
||||
# is the user a moderator?
|
||||
if not isModerator(baseDir, nickname):
|
||||
return ''
|
||||
|
||||
cssFilename = baseDir + '/epicyon-links.css'
|
||||
if os.path.isfile(baseDir + '/links.css'):
|
||||
cssFilename = baseDir + '/links.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
editCSS = cssFile.read()
|
||||
if httpPrefix != 'https':
|
||||
editCSS = \
|
||||
editCSS.replace('https://', httpPrefix + '://')
|
||||
|
||||
editLinksForm = htmlHeader(cssFilename, editCSS)
|
||||
editLinksForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/linksdata">\n'
|
||||
editLinksForm += \
|
||||
' <div class="vertical-center">\n'
|
||||
editLinksForm += \
|
||||
' <p class="new-post-text">' + translate['Edit Links'] + '</p>'
|
||||
editLinksForm += \
|
||||
' <div class="container">\n'
|
||||
editLinksForm += \
|
||||
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
editLinksForm += \
|
||||
' <input type="submit" name="submitLinks" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
editLinksForm += \
|
||||
' </div>\n'
|
||||
|
||||
linksFilename = baseDir + '/accounts/links.txt'
|
||||
linksStr = ''
|
||||
if os.path.isfile(linksFilename):
|
||||
with open(linksFilename, 'r') as fp:
|
||||
linksStr = fp.read()
|
||||
|
||||
editLinksForm += \
|
||||
'<div class="container">'
|
||||
editLinksForm += \
|
||||
' ' + \
|
||||
translate['One link per line. Description followed by the link.'] + \
|
||||
'<br>'
|
||||
editLinksForm += \
|
||||
' <textarea id="message" name="editedLinks" style="height:500px">' + \
|
||||
linksStr + '</textarea>'
|
||||
editLinksForm += \
|
||||
'</div>'
|
||||
|
||||
editLinksForm += htmlFooter()
|
||||
return editLinksForm
|
||||
|
||||
|
||||
def htmlEditNewswire(translate: {}, baseDir: str, path: str,
|
||||
domain: str, port: int, httpPrefix: str) -> str:
|
||||
"""Shows the edit newswire screen
|
||||
"""
|
||||
if '/users/' not in path:
|
||||
return ''
|
||||
pathOriginal = path
|
||||
path = path.replace('/inbox', '').replace('/outbox', '')
|
||||
path = path.replace('/shares', '')
|
||||
|
||||
nickname = getNicknameFromActor(path)
|
||||
if not nickname:
|
||||
return ''
|
||||
|
||||
# is the user a moderator?
|
||||
if not isModerator(baseDir, nickname):
|
||||
return ''
|
||||
|
||||
cssFilename = baseDir + '/epicyon-links.css'
|
||||
if os.path.isfile(baseDir + '/links.css'):
|
||||
cssFilename = baseDir + '/links.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
editCSS = cssFile.read()
|
||||
if httpPrefix != 'https':
|
||||
editCSS = \
|
||||
editCSS.replace('https://', httpPrefix + '://')
|
||||
|
||||
editNewswireForm = htmlHeader(cssFilename, editCSS)
|
||||
editNewswireForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/newswiredata">\n'
|
||||
editNewswireForm += \
|
||||
' <div class="vertical-center">\n'
|
||||
editNewswireForm += \
|
||||
' <p class="new-post-text">' + translate['Edit newswire'] + '</p>'
|
||||
editNewswireForm += \
|
||||
' <div class="container">\n'
|
||||
editNewswireForm += \
|
||||
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
editNewswireForm += \
|
||||
' <input type="submit" name="submitNewswire" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
editNewswireForm += \
|
||||
' </div>\n'
|
||||
|
||||
newswireFilename = baseDir + '/accounts/newswire.txt'
|
||||
newswireStr = ''
|
||||
if os.path.isfile(newswireFilename):
|
||||
with open(newswireFilename, 'r') as fp:
|
||||
newswireStr = fp.read()
|
||||
|
||||
editNewswireForm += \
|
||||
'<div class="container">'
|
||||
editNewswireForm += \
|
||||
' ' + \
|
||||
translate['Add RSS feed links below.'] + \
|
||||
'<br>'
|
||||
editNewswireForm += \
|
||||
' <textarea id="message" name="editedNewswire" ' + \
|
||||
'style="height:500px">' + newswireStr + '</textarea>'
|
||||
editNewswireForm += \
|
||||
'</div>'
|
||||
|
||||
editNewswireForm += htmlFooter()
|
||||
return editNewswireForm
|
||||
|
||||
|
||||
def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||
domain: str, port: int, httpPrefix: str) -> str:
|
||||
"""Shows the edit profile screen
|
||||
|
|
@ -1230,6 +1371,7 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
|||
notifyLikes = ''
|
||||
hideLikeButton = ''
|
||||
mediaInstanceStr = ''
|
||||
blogsInstanceStr = ''
|
||||
displayNickname = nickname
|
||||
bioStr = ''
|
||||
donateUrl = ''
|
||||
|
|
@ -1287,6 +1429,13 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
|||
if mediaInstance:
|
||||
if mediaInstance is True:
|
||||
mediaInstanceStr = 'checked'
|
||||
blogsInstanceStr = ''
|
||||
|
||||
blogsInstance = getConfigParam(baseDir, "blogsInstance")
|
||||
if blogsInstance:
|
||||
if blogsInstance is True:
|
||||
blogsInstanceStr = 'checked'
|
||||
mediaInstanceStr = ''
|
||||
|
||||
filterStr = ''
|
||||
filterFilename = \
|
||||
|
|
@ -1594,6 +1743,18 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
|||
editProfileForm += 'name="search_banner"'
|
||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||
|
||||
editProfileForm += ' <br><label class="labels">' + \
|
||||
translate['Left column image'] + '</label>\n'
|
||||
editProfileForm += ' <input type="file" id="left_col_image" '
|
||||
editProfileForm += 'name="left_col_image"'
|
||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||
|
||||
editProfileForm += ' <br><label class="labels">' + \
|
||||
translate['Right column image'] + '</label>\n'
|
||||
editProfileForm += ' <input type="file" id="right_col_image" '
|
||||
editProfileForm += 'name="right_col_image"'
|
||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||
|
||||
editProfileForm += ' </div>\n'
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
|
|
@ -1607,6 +1768,19 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
|||
editProfileForm += \
|
||||
' <input type="text" name="passwordconfirm" value="">\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
|
||||
if path.startswith('/users/' + adminNickname + '/'):
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="mediaInstance" ' + mediaInstanceStr + '> ' + \
|
||||
translate['This is a media instance'] + '<br>\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="blogsInstance" ' + blogsInstanceStr + '> ' + \
|
||||
translate['This is a blogging instance'] + '<br>\n'
|
||||
editProfileForm += ' </div>\n'
|
||||
|
||||
editProfileForm += ' <div class="container">\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
|
|
@ -1628,11 +1802,6 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
|||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="removeTwitter" ' + removeTwitter + '> ' + \
|
||||
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>\n'
|
||||
editProfileForm += \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="notifyLikes" ' + notifyLikes + '> ' + \
|
||||
|
|
@ -2639,6 +2808,7 @@ def htmlHeader(cssFilename: str, css: str, lang='en') -> str:
|
|||
htmlStr += ' <style>\n' + css + '</style>\n'
|
||||
htmlStr += ' <link rel="manifest" href="/manifest.json">\n'
|
||||
htmlStr += ' <meta name="theme-color" content="grey">\n'
|
||||
htmlStr += ' <title>Epicyon</title>\n'
|
||||
htmlStr += ' </head>\n'
|
||||
htmlStr += ' <body>\n'
|
||||
return htmlStr
|
||||
|
|
@ -2719,12 +2889,14 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
|||
if authorized and pageNumber > 1:
|
||||
# page up arrow
|
||||
profileStr += \
|
||||
'<center>\n<a href="' + actor + '/' + feedName + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + actor + '/' + feedName + \
|
||||
'?page=' + str(pageNumber - 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||
iconsDir + '/pageup.png" title="' + \
|
||||
translate['Page up'] + '" alt="' + \
|
||||
translate['Page up'] + '"></a>\n</center>\n'
|
||||
translate['Page up'] + '"></a>\n' + \
|
||||
' </center>\n'
|
||||
|
||||
for item in followingJson['orderedItems']:
|
||||
profileStr += \
|
||||
|
|
@ -2737,12 +2909,14 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
|||
if len(followingJson['orderedItems']) >= maxItemsPerPage:
|
||||
# page down arrow
|
||||
profileStr += \
|
||||
'<center>\n<a href="' + actor + '/' + feedName + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + actor + '/' + feedName + \
|
||||
'?page=' + str(pageNumber + 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||
iconsDir + '/pagedown.png" title="' + \
|
||||
translate['Page down'] + '" alt="' + \
|
||||
translate['Page down'] + '"></a>\n</center>\n'
|
||||
translate['Page down'] + '"></a>\n' + \
|
||||
' </center>\n'
|
||||
return profileStr
|
||||
|
||||
|
||||
|
|
@ -2899,11 +3073,13 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
|||
if pageNumber > 1:
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
timelineStr += \
|
||||
'<center>\n<a href="' + actor + '/tlshares?page=' + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + actor + '/tlshares?page=' + \
|
||||
str(pageNumber - 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||
iconsDir + '/pageup.png" title="' + translate['Page up'] + \
|
||||
'" alt="' + translate['Page up'] + '"></a>\n</center>\n'
|
||||
'" alt="' + translate['Page up'] + '"></a>\n' + \
|
||||
' </center>\n'
|
||||
|
||||
for published, item in sharesJson.items():
|
||||
showContactButton = False
|
||||
|
|
@ -2919,11 +3095,13 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
|||
if not lastPage:
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
timelineStr += \
|
||||
'<center>\n<a href="' + actor + '/tlshares?page=' + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + actor + '/tlshares?page=' + \
|
||||
str(pageNumber + 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||
iconsDir + '/pagedown.png" title="' + translate['Page down'] + \
|
||||
'" alt="' + translate['Page down'] + '"></a>\n</center>\n'
|
||||
'" alt="' + translate['Page down'] + '"></a>\n' + \
|
||||
' </center>\n'
|
||||
|
||||
return timelineStr
|
||||
|
||||
|
|
@ -4130,7 +4308,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
|
||||
avatarLink += \
|
||||
' <img loading="lazy" src="' + avatarUrl + '" title="' + \
|
||||
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>'
|
||||
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>\n'
|
||||
|
||||
if showAvatarOptions and \
|
||||
fullDomain + '/users/' + nickname not in postActor:
|
||||
|
|
@ -4280,18 +4458,21 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
else:
|
||||
if isDM(postJsonObject):
|
||||
replyStr += \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'?replydm=' + replyToLink + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
'" title="' + translate['Reply to this post'] + '">\n'
|
||||
else:
|
||||
replyStr += \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'?replyfollowers=' + replyToLink + \
|
||||
'?actor=' + postJsonObject['actor'] + \
|
||||
'" title="' + translate['Reply to this post'] + '">\n'
|
||||
|
||||
replyStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + \
|
||||
translate['Reply to this post'] + '" alt="' + \
|
||||
translate['Reply to this post'] + \
|
||||
|
|
@ -4317,6 +4498,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
if isBlogPost(postJsonObject):
|
||||
blogPostId = postJsonObject['object']['id']
|
||||
editStr += \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'/tlblogs?editblogpost=' + \
|
||||
blogPostId.split('/statuses/')[1] + \
|
||||
|
|
@ -4329,6 +4511,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
elif isEvent:
|
||||
eventPostId = postJsonObject['object']['id']
|
||||
editStr += \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||
'/tlblogs?editeventpost=' + \
|
||||
eventPostId.split('/statuses/')[1] + \
|
||||
|
|
@ -4360,6 +4543,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'?bm=' + timelinePostBookmark + \
|
||||
'?tl=' + boxName + '" title="' + announceTitle + '">\n'
|
||||
announceStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + translate['Repeat this post'] + \
|
||||
'" alt="' + translate['Repeat this post'] + \
|
||||
' |" src="/' + iconsDir + '/' + announceIcon + '"/></a>\n'
|
||||
|
|
@ -4425,6 +4609,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'?tl=' + boxName + '" title="' + \
|
||||
likeTitle + likeCountStr + '">\n'
|
||||
likeStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + likeTitle + likeCountStr + \
|
||||
'" alt="' + likeTitle + \
|
||||
' |" src="/' + iconsDir + '/' + likeIcon + '"/></a>\n'
|
||||
|
|
@ -4457,6 +4642,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'?bm=' + timelinePostBookmark + \
|
||||
'?tl=' + boxName + '" title="' + bookmarkTitle + '">\n'
|
||||
bookmarkStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + bookmarkTitle + '" alt="' + \
|
||||
bookmarkTitle + ' |" src="/' + iconsDir + \
|
||||
'/' + bookmarkIcon + '"/></a>\n'
|
||||
|
|
@ -4486,6 +4672,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'?delete=' + messageId + pageNumberParam + \
|
||||
'" title="' + translate['Delete this post'] + '">\n'
|
||||
deleteStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" alt="' + translate['Delete this post'] + \
|
||||
' |" title="' + translate['Delete this post'] + \
|
||||
'" src="/' + iconsDir + '/delete.png"/></a>\n'
|
||||
|
|
@ -4497,6 +4684,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'?bm=' + timelinePostBookmark + \
|
||||
'" title="' + translate['Mute this post'] + '">\n'
|
||||
muteStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" alt="' + \
|
||||
translate['Mute this post'] + \
|
||||
' |" title="' + translate['Mute this post'] + \
|
||||
|
|
@ -4509,6 +4697,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
timelinePostBookmark + '" title="' + \
|
||||
translate['Undo mute'] + '">\n'
|
||||
muteStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" alt="' + translate['Undo mute'] + \
|
||||
' |" title="' + translate['Undo mute'] + \
|
||||
'" src="/' + iconsDir+'/unmute.png"/></a>\n'
|
||||
|
|
@ -4574,11 +4763,13 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
' 13.3.1 = ' + str(timeDiff))
|
||||
|
||||
titleStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + \
|
||||
translate['announces'] + '" alt="' + \
|
||||
translate['announces'] + '" src="/' + \
|
||||
iconsDir + '/repeat_inactive.png" ' + \
|
||||
'class="announceOrReply"/> <a href="' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + '">' + \
|
||||
announceDisplayName + '</a>\n'
|
||||
# show avatar of person replied to
|
||||
|
|
@ -4599,6 +4790,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
if announceAvatarUrl:
|
||||
idx = 'Show options for this person'
|
||||
replyAvatarImageInPost = \
|
||||
' ' \
|
||||
'<div class="timeline-avatar-reply">\n' \
|
||||
' <a class="imageAnchor" ' + \
|
||||
'href="/users/' + nickname + \
|
||||
|
|
@ -4618,7 +4810,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'" alt="' + translate['announces'] + \
|
||||
'" src="/' + iconsDir + \
|
||||
'/repeat_inactive.png" ' + \
|
||||
'class="announceOrReply"/> <a href="' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + '">@' + \
|
||||
announceNickname + '@' + \
|
||||
announceDomain + '</a>\n'
|
||||
|
|
@ -4628,16 +4821,19 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
translate['announces'] + '" alt="' + \
|
||||
translate['announces'] + '" src="/' + iconsDir + \
|
||||
'/repeat_inactive.png" ' + \
|
||||
'class="announceOrReply"/> <a href="' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + \
|
||||
'">@unattributed</a>\n'
|
||||
else:
|
||||
titleStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + translate['announces'] + \
|
||||
'" alt="' + translate['announces'] + \
|
||||
'" src="/' + iconsDir + \
|
||||
'/repeat_inactive.png" ' + \
|
||||
'class="announceOrReply"/> <a href="' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['id'] + '">@unattributed</a>\n'
|
||||
else:
|
||||
if postJsonObject['object'].get('inReplyTo'):
|
||||
|
|
@ -4694,13 +4890,15 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
boxName + ' 13.6 = ' +
|
||||
str(timeDiff))
|
||||
titleStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + \
|
||||
translate['replying to'] + \
|
||||
'" alt="' + \
|
||||
translate['replying to'] + \
|
||||
'" src="/' + \
|
||||
iconsDir + '/reply.png" ' + \
|
||||
'class="announceOrReply"/> ' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' ' + \
|
||||
'<a href="' + inReplyTo + \
|
||||
'">' + replyDisplayName + '</a>\n'
|
||||
|
||||
|
|
@ -4732,6 +4930,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
' <div class=' + \
|
||||
'"timeline-avatar-reply">\n'
|
||||
replyAvatarImageInPost += \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" ' + \
|
||||
'href="/users/' + nickname + \
|
||||
'?options=' + replyActor + \
|
||||
|
|
@ -4739,6 +4938,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
replyAvatarUrl + \
|
||||
messageIdStr + '">\n'
|
||||
replyAvatarImageInPost += \
|
||||
' ' + \
|
||||
'<img loading="lazy" src="' + \
|
||||
replyAvatarUrl + '" '
|
||||
replyAvatarImageInPost += \
|
||||
|
|
@ -4746,18 +4946,20 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
translate['Show profile']
|
||||
replyAvatarImageInPost += \
|
||||
'" alt=" "' + \
|
||||
avatarPosition + '/></a>\n</div>\n'
|
||||
avatarPosition + '/></a>\n' + \
|
||||
' </div>\n'
|
||||
else:
|
||||
inReplyTo = \
|
||||
postJsonObject['object']['inReplyTo']
|
||||
titleStr += \
|
||||
' ' + \
|
||||
'<img loading="lazy" title="' + \
|
||||
translate['replying to'] + \
|
||||
'" alt="' + \
|
||||
translate['replying to'] + \
|
||||
'" src="/' + \
|
||||
iconsDir + '/reply.png" ' + \
|
||||
'class="announceOrReply"/> ' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
inReplyTo + '">@' + \
|
||||
replyNickname + '@' + \
|
||||
|
|
@ -4770,7 +4972,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
translate['replying to'] + \
|
||||
'" src="/' + \
|
||||
iconsDir + \
|
||||
'/reply.png" class="announceOrReply"/> ' + \
|
||||
'/reply.png" class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['inReplyTo'] + \
|
||||
'">@unknown</a>\n'
|
||||
|
|
@ -4789,7 +4991,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
'" alt="' + translate['replying to'] + \
|
||||
'" src="/' + \
|
||||
iconsDir + '/reply.png" ' + \
|
||||
'class="announceOrReply"/> <a href="' + \
|
||||
'class="announceOrReply"/>\n' + \
|
||||
' <a href="' + \
|
||||
postJsonObject['object']['inReplyTo'] + \
|
||||
'">' + postDomain + '</a>\n'
|
||||
|
||||
|
|
@ -4849,12 +5052,12 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
containerClass = 'container dm'
|
||||
|
||||
if showIcons:
|
||||
footerStr = '<div class="' + containerClassIcons + '">'
|
||||
footerStr = '\n <div class="' + containerClassIcons + '">\n'
|
||||
footerStr += replyStr + announceStr + likeStr + bookmarkStr + \
|
||||
deleteStr + muteStr + editStr
|
||||
footerStr += ' <a href="' + publishedLink + '" class="' + \
|
||||
timeClass + '">' + publishedStr + '</a>\n'
|
||||
footerStr += '</div>'
|
||||
footerStr += ' </div>\n'
|
||||
|
||||
postIsSensitive = False
|
||||
if postJsonObject['object'].get('sensitive'):
|
||||
|
|
@ -4948,7 +5151,9 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
contentStr = ''
|
||||
else:
|
||||
if not isPatch:
|
||||
contentStr = '<div class="message">' + contentStr + '</div>\n'
|
||||
contentStr = ' <div class="message">' + \
|
||||
contentStr + \
|
||||
' </div>\n'
|
||||
else:
|
||||
contentStr = \
|
||||
'<div class="gitpatch"><pre><code>' + contentStr + \
|
||||
|
|
@ -4959,8 +5164,9 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
postHtml = ' <div id="' + timelinePostBookmark + \
|
||||
'" class="' + containerClass + '">\n'
|
||||
postHtml += avatarImageInPost
|
||||
postHtml += '<p class="post-title">' + titleStr + \
|
||||
replyAvatarImageInPost + '</p>\n'
|
||||
postHtml += ' <div class="post-title">\n' + \
|
||||
' ' + titleStr + \
|
||||
replyAvatarImageInPost + ' </div>\n'
|
||||
postHtml += contentStr + footerStr + '\n'
|
||||
postHtml += ' </div>\n'
|
||||
else:
|
||||
|
|
@ -5019,6 +5225,184 @@ def htmlHighlightLabel(label: str, highlight: bool) -> str:
|
|||
return '*' + label + '*'
|
||||
|
||||
|
||||
def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||
httpPrefix: str, translate: {},
|
||||
iconsDir: str, moderator: bool) -> str:
|
||||
"""Returns html content for the left column
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
||||
domain = domainFull
|
||||
if ':' in domain:
|
||||
domain = domain.split(':')
|
||||
|
||||
leftColumnImageFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + \
|
||||
'/left_col_image.png'
|
||||
if not os.path.isfile(leftColumnImageFilename):
|
||||
theme = getConfigParam(baseDir, 'theme').lower()
|
||||
if theme == 'default':
|
||||
theme = ''
|
||||
else:
|
||||
theme = '_' + theme
|
||||
themeLeftColumnImageFilename = \
|
||||
baseDir + '/img/left_col_image' + theme + '.png'
|
||||
if os.path.isfile(themeLeftColumnImageFilename):
|
||||
copyfile(themeLeftColumnImageFilename, leftColumnImageFilename)
|
||||
|
||||
# show the image at the top of the column
|
||||
editImageClass = 'leftColEdit'
|
||||
if os.path.isfile(leftColumnImageFilename):
|
||||
editImageClass = 'leftColEditImage'
|
||||
htmlStr += \
|
||||
'\n <center>\n' + \
|
||||
' <img class="leftColImg" loading="lazy" src="/users/' + \
|
||||
nickname + '/left_col_image.png" />\n' + \
|
||||
' </center>\n'
|
||||
|
||||
if editImageClass == 'leftColEdit':
|
||||
htmlStr += '\n <center>\n'
|
||||
|
||||
if moderator:
|
||||
# show the edit icon
|
||||
htmlStr += \
|
||||
' <a href="' + \
|
||||
'/users/' + nickname + '/editlinks">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Edit Links'] + '" title="' + \
|
||||
translate['Edit Links'] + '" src="/' + \
|
||||
iconsDir + '/edit.png" /></a>\n'
|
||||
|
||||
# RSS icon
|
||||
htmlStr += \
|
||||
' <a href="' + \
|
||||
httpPrefix + '://' + domainFull + \
|
||||
'/blog/' + nickname + '/rss.xml">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['RSS feed for this site'] + \
|
||||
'" title="' + translate['RSS feed for this site'] + \
|
||||
'" src="/' + iconsDir + '/rss.png" /></a>\n'
|
||||
|
||||
if editImageClass == 'leftColEdit':
|
||||
htmlStr += ' </center>\n'
|
||||
else:
|
||||
htmlStr += ' <br>\n'
|
||||
|
||||
linksFilename = baseDir + '/accounts/links.txt'
|
||||
if os.path.isfile(linksFilename):
|
||||
linksList = None
|
||||
with open(linksFilename, "r") as f:
|
||||
linksList = f.readlines()
|
||||
if linksList:
|
||||
for lineStr in linksList:
|
||||
if ' ' not in lineStr:
|
||||
if '#' not in lineStr:
|
||||
if '*' not in lineStr:
|
||||
continue
|
||||
lineStr = lineStr.strip()
|
||||
words = lineStr.split(' ')
|
||||
# get the link
|
||||
linkStr = None
|
||||
for word in words:
|
||||
if word == '#':
|
||||
continue
|
||||
if word == '*':
|
||||
continue
|
||||
if '://' in word:
|
||||
linkStr = word
|
||||
break
|
||||
if linkStr:
|
||||
lineStr = lineStr.replace(linkStr, '').strip()
|
||||
# avoid any dubious scripts being added
|
||||
if '<' not in lineStr:
|
||||
# remove trailing comma if present
|
||||
if lineStr.endswith(','):
|
||||
lineStr = lineStr[:len(lineStr)-1]
|
||||
# add link to the returned html
|
||||
htmlStr += \
|
||||
' <p><a href="' + linkStr + '">' + \
|
||||
lineStr + '</a></p>\n'
|
||||
else:
|
||||
if lineStr.startswith('#') or lineStr.startswith('*'):
|
||||
lineStr = lineStr[1:].strip()
|
||||
htmlStr += \
|
||||
' <h3 class="linksHeader">' + \
|
||||
lineStr + '</h3>\n'
|
||||
else:
|
||||
htmlStr += \
|
||||
' <p>' + lineStr + '</p>\n'
|
||||
|
||||
return htmlStr
|
||||
|
||||
|
||||
def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||
httpPrefix: str, translate: {},
|
||||
iconsDir: str, moderator: bool) -> str:
|
||||
"""Returns html content for the right column
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
||||
domain = domainFull
|
||||
if ':' in domain:
|
||||
domain = domain.split(':')
|
||||
|
||||
rightColumnImageFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + \
|
||||
'/right_col_image.png'
|
||||
if not os.path.isfile(rightColumnImageFilename):
|
||||
theme = getConfigParam(baseDir, 'theme').lower()
|
||||
if theme == 'default':
|
||||
theme = ''
|
||||
else:
|
||||
theme = '_' + theme
|
||||
themeRightColumnImageFilename = \
|
||||
baseDir + '/img/right_col_image' + theme + '.png'
|
||||
if os.path.isfile(themeRightColumnImageFilename):
|
||||
copyfile(themeRightColumnImageFilename, rightColumnImageFilename)
|
||||
|
||||
# show the image at the top of the column
|
||||
editImageClass = 'rightColEdit'
|
||||
if os.path.isfile(rightColumnImageFilename):
|
||||
editImageClass = 'rightColEditImage'
|
||||
htmlStr += \
|
||||
'\n <center>\n' + \
|
||||
' <img class="rightColImg" ' + \
|
||||
'loading="lazy" src="/users/' + \
|
||||
nickname + '/right_col_image.png" />\n' + \
|
||||
' </center>\n'
|
||||
|
||||
if editImageClass == 'rightColEdit':
|
||||
htmlStr += '\n <center>\n'
|
||||
|
||||
if moderator:
|
||||
# show the edit icon
|
||||
htmlStr += \
|
||||
' <a href="' + \
|
||||
'/users/' + nickname + '/editnewswire">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Edit newswire'] + '" title="' + \
|
||||
translate['Edit newswire'] + '" src="/' + \
|
||||
iconsDir + '/edit.png" /></a>\n'
|
||||
|
||||
htmlStr += \
|
||||
' <a href="/newswire.xml">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Newswire RSS Feed'] + '" title="' + \
|
||||
translate['Newswire RSS Feed'] + '" src="/' + \
|
||||
iconsDir + '/rss.png" /></a>\n'
|
||||
|
||||
if editImageClass == 'rightColEdit':
|
||||
htmlStr += ' </center>\n'
|
||||
else:
|
||||
htmlStr += ' <br>\n'
|
||||
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlTimeline(defaultTimeline: str,
|
||||
recentPostsCache: {}, maxRecentPosts: int,
|
||||
translate: {}, pageNumber: int,
|
||||
|
|
@ -5300,8 +5684,8 @@ def htmlTimeline(defaultTimeline: str,
|
|||
# This creates a link to the profile page when viewed
|
||||
# in lynx, but should be invisible in a graphical web browser
|
||||
tlStr += \
|
||||
'<a href="/users/' + nickname + '"><label class="transparent">' + \
|
||||
translate['Switch to profile view'] + '</label></a>\n'
|
||||
'<label class="transparent"><a href="/users/' + nickname + '">' + \
|
||||
translate['Switch to profile view'] + '</a></label>\n'
|
||||
|
||||
# banner and row of buttons
|
||||
tlStr += \
|
||||
|
|
@ -5310,8 +5694,34 @@ def htmlTimeline(defaultTimeline: str,
|
|||
translate['Switch to profile view'] + '">\n'
|
||||
tlStr += '<div class="timeline-banner">'
|
||||
tlStr += '</div>\n</a>\n'
|
||||
tlStr += '<div class="container">\n'
|
||||
|
||||
# start the timeline
|
||||
tlStr += '<table class="timeline">\n'
|
||||
tlStr += ' <colgroup>\n'
|
||||
tlStr += ' <col span="1" class="column-left">\n'
|
||||
tlStr += ' <col span="1" class="column-center">\n'
|
||||
tlStr += ' <col span="1" class="column-right">\n'
|
||||
tlStr += ' </colgroup>\n'
|
||||
tlStr += ' <tbody>\n'
|
||||
tlStr += ' <tr>\n'
|
||||
|
||||
domainFull = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
|
||||
# left column
|
||||
leftColumnStr = \
|
||||
getLeftColumnContent(baseDir, nickname, domainFull,
|
||||
httpPrefix, translate, iconsDir,
|
||||
moderator)
|
||||
tlStr += ' <td valign="top" class="col-left">' + \
|
||||
leftColumnStr + ' </td>\n'
|
||||
# center column containing posts
|
||||
tlStr += ' <td valign="top" class="col-center">\n'
|
||||
|
||||
# start of the button header with inbox, outbox, etc
|
||||
tlStr += ' <div class="container">\n'
|
||||
# first button
|
||||
if defaultTimeline == 'tlmedia':
|
||||
tlStr += \
|
||||
|
|
@ -5388,6 +5798,32 @@ def htmlTimeline(defaultTimeline: str,
|
|||
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
|
||||
moderationButtonStr + newPostButtonStr
|
||||
|
||||
# show todays events buttons on the first inbox page
|
||||
if boxName == 'inbox' and pageNumber == 1:
|
||||
if todaysEventsCheck(baseDir, nickname, domain):
|
||||
now = datetime.now()
|
||||
|
||||
# happening today button
|
||||
tlStr += \
|
||||
' <a href="' + usersPath + '/calendar?year=' + \
|
||||
str(now.year) + '?month=' + str(now.month) + \
|
||||
'?day=' + str(now.day) + '"><button class="buttonevent">' + \
|
||||
translate['Happening Today'] + '</button></a>\n'
|
||||
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
tlStr += \
|
||||
' <a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>\n'
|
||||
else:
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
tlStr += \
|
||||
' <a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>\n'
|
||||
|
||||
# the search button
|
||||
tlStr += \
|
||||
' <a class="imageAnchor" href="' + usersPath + \
|
||||
|
|
@ -5420,7 +5856,8 @@ def htmlTimeline(defaultTimeline: str,
|
|||
'" alt="| ' + translate['Show/Hide Buttons'] + \
|
||||
'" class="timelineicon"/></a>\n'
|
||||
tlStr += followApprovals
|
||||
tlStr += '</div>'
|
||||
# end of the button header with inbox, outbox, etc
|
||||
tlStr += ' </div>\n'
|
||||
|
||||
# second row of buttons for moderator actions
|
||||
if moderator and boxName == 'moderation':
|
||||
|
|
@ -5479,34 +5916,6 @@ def htmlTimeline(defaultTimeline: str,
|
|||
if timeDiff > 100:
|
||||
print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff))
|
||||
|
||||
# show todays events buttons on the first inbox page
|
||||
if boxName == 'inbox' and pageNumber == 1:
|
||||
if todaysEventsCheck(baseDir, nickname, domain):
|
||||
now = datetime.now()
|
||||
|
||||
# happening today button
|
||||
tlStr += \
|
||||
'<center>\n<a href="' + usersPath + '/calendar?year=' + \
|
||||
str(now.year) + '?month=' + str(now.month) + \
|
||||
'?day=' + str(now.day) + '"><button class="buttonevent">' + \
|
||||
translate['Happening Today'] + '</button></a>\n'
|
||||
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
tlStr += \
|
||||
'<a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>\n'
|
||||
tlStr += '</center>\n'
|
||||
else:
|
||||
# happening this week button
|
||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||
tlStr += \
|
||||
'<center>\n<a href="' + usersPath + \
|
||||
'/calendar"><button class="buttonevent">' + \
|
||||
translate['Happening This Week'] + '</button></a>\n' + \
|
||||
'</center>\n'
|
||||
|
||||
# benchmark 8
|
||||
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
||||
if timeDiff > 100:
|
||||
|
|
@ -5515,12 +5924,14 @@ def htmlTimeline(defaultTimeline: str,
|
|||
# page up arrow
|
||||
if pageNumber > 1:
|
||||
tlStr += \
|
||||
'<center>\n<a href="' + usersPath + '/' + boxName + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + usersPath + '/' + boxName + \
|
||||
'?page=' + str(pageNumber - 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||
iconsDir + '/pageup.png" title="' + \
|
||||
translate['Page up'] + '" alt="' + \
|
||||
translate['Page up'] + '"></a>\n</center>\n'
|
||||
translate['Page up'] + '"></a>\n' + \
|
||||
' </center>\n'
|
||||
|
||||
# show the posts
|
||||
itemCtr = 0
|
||||
|
|
@ -5604,6 +6015,17 @@ def htmlTimeline(defaultTimeline: str,
|
|||
if boxName == 'tlmedia':
|
||||
tlStr += '</div>\n'
|
||||
|
||||
# end of column-center
|
||||
tlStr += ' </td>\n'
|
||||
|
||||
# right column
|
||||
rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull,
|
||||
httpPrefix, translate, iconsDir,
|
||||
moderator)
|
||||
tlStr += ' <td valign="top" class="col-right">' + \
|
||||
rightColumnStr + ' </td>\n'
|
||||
tlStr += ' </tr>\n'
|
||||
|
||||
# benchmark 9
|
||||
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
||||
if timeDiff > 100:
|
||||
|
|
@ -5612,12 +6034,23 @@ def htmlTimeline(defaultTimeline: str,
|
|||
# page down arrow
|
||||
if itemCtr > 2:
|
||||
tlStr += \
|
||||
'<center>\n<a href="' + usersPath + '/' + boxName + '?page=' + \
|
||||
' <tr>\n' + \
|
||||
' <td class="col-left"></td>\n' + \
|
||||
' <td class="col-center">\n' + \
|
||||
' <center>\n' + \
|
||||
' <a href="' + usersPath + '/' + boxName + '?page=' + \
|
||||
str(pageNumber + 1) + \
|
||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||
iconsDir + '/pagedown.png" title="' + \
|
||||
translate['Page down'] + '" alt="' + \
|
||||
translate['Page down'] + '"></a>\n</center>\n'
|
||||
translate['Page down'] + '"></a>\n' + \
|
||||
' </center>\n' + \
|
||||
' </td>\n' + \
|
||||
' <td class="col-right"></td>\n' + \
|
||||
' </tr>\n'
|
||||
|
||||
tlStr += ' </tbody>\n'
|
||||
tlStr += '</table>\n'
|
||||
tlStr += htmlFooter()
|
||||
return tlStr
|
||||
|
||||
|
|
|
|||