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

main
Bob Mottram 2020-10-16 19:31:57 +01:00
commit 5d9a52c4f8
98 changed files with 898 additions and 309 deletions

21
blog.py
View File

@ -365,7 +365,7 @@ def htmlBlogPost(authorized: bool,
blogStr += '<img style="width:3%;min-width:50px" ' + \ blogStr += '<img style="width:3%;min-width:50px" ' + \
'loading="lazy" alt="RSS 2.0" ' + \ 'loading="lazy" alt="RSS 2.0" ' + \
'title="RSS 2.0" src="/' + \ 'title="RSS 2.0" src="/' + \
iconsDir + '/rss.png" /></a>' iconsDir + '/logorss.png" /></a>'
# blogStr += '<a href="' + httpPrefix + '://' + \ # blogStr += '<a href="' + httpPrefix + '://' + \
# domainFull + '/blog/' + nickname + '/rss.txt">' # domainFull + '/blog/' + nickname + '/rss.txt">'
@ -461,7 +461,7 @@ def htmlBlogPage(authorized: bool, session,
domainFull + '/blog/' + nickname + '/rss.xml">' domainFull + '/blog/' + nickname + '/rss.xml">'
blogStr += '<img loading="lazy" alt="RSS 2.0" ' + \ blogStr += '<img loading="lazy" alt="RSS 2.0" ' + \
'title="RSS 2.0" src="/' + \ 'title="RSS 2.0" src="/' + \
iconsDir + '/rss.png" /></a>' iconsDir + '/logorss.png" /></a>'
# blogStr += '<a href="' + httpPrefix + '://' + \ # blogStr += '<a href="' + httpPrefix + '://' + \
# domainFull + '/blog/' + nickname + '/rss.txt">' # domainFull + '/blog/' + nickname + '/rss.txt">'
@ -478,7 +478,8 @@ def htmlBlogPage(authorized: bool, session,
def htmlBlogPageRSS2(authorized: bool, session, def htmlBlogPageRSS2(authorized: bool, session,
baseDir: str, httpPrefix: str, translate: {}, baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, port: int, nickname: str, domain: str, port: int,
noOfItems: int, pageNumber: int) -> str: noOfItems: int, pageNumber: int,
includeHeader: bool) -> str:
"""Returns an RSS version 2 feed containing posts """Returns an RSS version 2 feed containing posts
""" """
if ' ' in nickname or '@' in nickname or \ if ' ' in nickname or '@' in nickname or \
@ -490,12 +491,18 @@ def htmlBlogPageRSS2(authorized: bool, session,
if port != 80 and port != 443: if port != 80 and port != 443:
domainFull = domain + ':' + str(port) domainFull = domain + ':' + str(port)
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, 'Blog', translate) blogRSS2 = ''
if includeHeader:
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull,
'Blog', translate)
blogsIndex = baseDir + '/accounts/' + \ blogsIndex = baseDir + '/accounts/' + \
nickname + '@' + domain + '/tlblogs.index' nickname + '@' + domain + '/tlblogs.index'
if not os.path.isfile(blogsIndex): if not os.path.isfile(blogsIndex):
if includeHeader:
return blogRSS2 + rss2Footer() return blogRSS2 + rss2Footer()
else:
return blogRSS2
timelineJson = createBlogsTimeline(session, baseDir, timelineJson = createBlogsTimeline(session, baseDir,
nickname, domain, port, nickname, domain, port,
@ -504,7 +511,10 @@ def htmlBlogPageRSS2(authorized: bool, session,
pageNumber) pageNumber)
if not timelineJson: if not timelineJson:
if includeHeader:
return blogRSS2 + rss2Footer() return blogRSS2 + rss2Footer()
else:
return blogRSS2
if pageNumber is not None: if pageNumber is not None:
for item in timelineJson['orderedItems']: for item in timelineJson['orderedItems']:
@ -518,7 +528,10 @@ def htmlBlogPageRSS2(authorized: bool, session,
domainFull, item, domainFull, item,
None, True) None, True)
if includeHeader:
return blogRSS2 + rss2Footer() return blogRSS2 + rss2Footer()
else:
return blogRSS2
def htmlBlogPageRSS3(authorized: bool, session, def htmlBlogPageRSS3(authorized: bool, session,

440
daemon.py
View File

@ -164,6 +164,8 @@ from shares import getSharesFeedForPerson
from shares import addShare from shares import addShare
from shares import removeShare from shares import removeShare
from shares import expireShares from shares import expireShares
from utils import containsInvalidChars
from utils import isSystemAccount
from utils import setConfigParam from utils import setConfigParam
from utils import getConfigParam from utils import getConfigParam
from utils import removeIdEnding from utils import removeIdEnding
@ -194,6 +196,7 @@ from media import removeMetaData
from cache import storePersonInCache from cache import storePersonInCache
from cache import getPersonFromCache from cache import getPersonFromCache
from httpsig import verifyPostHeaders from httpsig import verifyPostHeaders
from theme import setNewsAvatar
from theme import setTheme from theme import setTheme
from theme import getTheme from theme import getTheme
from theme import enableGrayscale from theme import enableGrayscale
@ -212,6 +215,8 @@ from devices import E2EEdevicesCollection
from devices import E2EEvalidDevice from devices import E2EEvalidDevice
from devices import E2EEaddDevice from devices import E2EEaddDevice
from newswire import getRSSfromDict from newswire import getRSSfromDict
from newswire import rss2Header
from newswire import rss2Footer
from newsdaemon import runNewswireWatchdog from newsdaemon import runNewswireWatchdog
from newsdaemon import runNewswireDaemon from newsdaemon import runNewswireDaemon
import os import os
@ -300,11 +305,11 @@ class PubServer(BaseHTTPRequestHandler):
accountDir = self.server.baseDir + '/accounts/' + \ accountDir = self.server.baseDir + '/accounts/' + \
nickname + '@' + self.server.domain nickname + '@' + self.server.domain
if not os.path.isdir(accountDir): if not os.path.isdir(accountDir):
return False
minimalFilename = accountDir + '/minimal'
if os.path.isfile(minimalFilename):
return True return True
minimalFilename = accountDir + '/.notminimal'
if os.path.isfile(minimalFilename):
return False return False
return True
def _setMinimal(self, nickname: str, minimal: bool) -> None: def _setMinimal(self, nickname: str, minimal: bool) -> None:
"""Sets whether an account should display minimal buttons """Sets whether an account should display minimal buttons
@ -313,11 +318,11 @@ class PubServer(BaseHTTPRequestHandler):
nickname + '@' + self.server.domain nickname + '@' + self.server.domain
if not os.path.isdir(accountDir): if not os.path.isdir(accountDir):
return return
minimalFilename = accountDir + '/minimal' minimalFilename = accountDir + '/.notminimal'
minimalFileExists = os.path.isfile(minimalFilename) minimalFileExists = os.path.isfile(minimalFilename)
if not minimal and minimalFileExists: if minimal and minimalFileExists:
os.remove(minimalFilename) os.remove(minimalFilename)
elif minimal and not minimalFileExists: elif not minimal and not minimalFileExists:
with open(minimalFilename, 'w+') as fp: with open(minimalFilename, 'w+') as fp:
fp.write('\n') fp.write('\n')
@ -521,6 +526,21 @@ class PubServer(BaseHTTPRequestHandler):
self.send_header('X-Robots-Tag', 'noindex') self.send_header('X-Robots-Tag', 'noindex')
self.end_headers() self.end_headers()
def _logout_redirect(self, redirect: str, cookie: str,
callingDomain: str) -> None:
if '://' not in redirect:
print('REDIRECT ERROR: redirect is not an absolute url ' +
redirect)
self.send_response(303)
self.send_header('Set-Cookie', 'epicyon=; SameSite=Strict')
self.send_header('Location', redirect)
self.send_header('Host', callingDomain)
self.send_header('InstanceID', self.server.instanceId)
self.send_header('Content-Length', '0')
self.send_header('X-Robots-Tag', 'noindex')
self.end_headers()
def _set_headers_base(self, fileFormat: str, length: int, cookie: str, def _set_headers_base(self, fileFormat: str, length: int, cookie: str,
callingDomain: str) -> None: callingDomain: str) -> None:
self.send_response(200) self.send_response(200)
@ -1240,8 +1260,9 @@ class PubServer(BaseHTTPRequestHandler):
loginNickname, loginPassword, register = \ loginNickname, loginPassword, register = \
htmlGetLoginCredentials(loginParams, self.server.lastLoginTime) htmlGetLoginCredentials(loginParams, self.server.lastLoginTime)
if loginNickname: if loginNickname:
if loginNickname == 'news' or loginNickname == 'inbox': if isSystemAccount(loginNickname):
print('Invalid username login: ' + loginNickname) print('Invalid username login: ' + loginNickname +
' (system account)')
self._clearLoginDetails(loginNickname, callingDomain) self._clearLoginDetails(loginNickname, callingDomain)
self.server.POSTbusy = False self.server.POSTbusy = False
return return
@ -1625,7 +1646,8 @@ class PubServer(BaseHTTPRequestHandler):
if debug: if debug:
print('You cannot perform an option action on yourself') print('You cannot perform an option action on yourself')
# view button on person option screen # person options screen, view button
# See htmlPersonOptions
if '&submitView=' in optionsConfirmParams: if '&submitView=' in optionsConfirmParams:
if debug: if debug:
print('Viewing ' + optionsActor) print('Viewing ' + optionsActor)
@ -1634,7 +1656,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# petname submit button on person option screen # person options screen, petname submit button
# See htmlPersonOptions
if '&submitPetname=' in optionsConfirmParams and petname: if '&submitPetname=' in optionsConfirmParams and petname:
if debug: if debug:
print('Change petname to ' + petname) print('Change petname to ' + petname)
@ -1650,7 +1673,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# person notes submit button on person option screen # person options screen, person notes submit button
# See htmlPersonOptions
if '&submitPersonNotes=' in optionsConfirmParams: if '&submitPersonNotes=' in optionsConfirmParams:
if debug: if debug:
print('Change person notes') print('Change person notes')
@ -1668,7 +1692,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# person on calendar checkbox on person option screen # person options screen, on calendar checkbox
# See htmlPersonOptions
if '&submitOnCalendar=' in optionsConfirmParams: if '&submitOnCalendar=' in optionsConfirmParams:
onCalendar = None onCalendar = None
if 'onCalendar=' in optionsConfirmParams: if 'onCalendar=' in optionsConfirmParams:
@ -1694,7 +1719,35 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# block person button on person option screen # person options screen, permission to post to newswire
# See htmlPersonOptions
if '&submitPostToNews=' in optionsConfirmParams:
if isModerator(self.server.baseDir, chooserNickname):
postsToNews = None
if 'postsToNews=' in optionsConfirmParams:
postsToNews = optionsConfirmParams.split('postsToNews=')[1]
if '&' in postsToNews:
postsToNews = postsToNews.split('&')[0]
newswireBlockedFilename = \
self.server.baseDir + '/accounts/' + \
optionsNickname + '@' + optionsDomain + '/.nonewswire'
if postsToNews == 'on':
if os.path.isfile(newswireBlockedFilename):
os.remove(newswireBlockedFilename)
else:
noNewswireFile = open(newswireBlockedFilename, "w+")
if noNewswireFile:
noNewswireFile.write('\n')
noNewswireFile.close()
self._redirect_headers(usersPath + '/' +
self.server.defaultTimeline +
'?page='+str(pageNumber), cookie,
callingDomain)
self.server.POSTbusy = False
return
# person options screen, block button
# See htmlPersonOptions
if '&submitBlock=' in optionsConfirmParams: if '&submitBlock=' in optionsConfirmParams:
if debug: if debug:
print('Adding block by ' + chooserNickname + print('Adding block by ' + chooserNickname +
@ -1703,7 +1756,8 @@ class PubServer(BaseHTTPRequestHandler):
domain, domain,
optionsNickname, optionsDomainFull) optionsNickname, optionsDomainFull)
# unblock button on person option screen # person options screen, unblock button
# See htmlPersonOptions
if '&submitUnblock=' in optionsConfirmParams: if '&submitUnblock=' in optionsConfirmParams:
if debug: if debug:
print('Unblocking ' + optionsActor) print('Unblocking ' + optionsActor)
@ -1719,7 +1773,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# follow button on person option screen # person options screen, follow button
# See htmlPersonOptions
if '&submitFollow=' in optionsConfirmParams: if '&submitFollow=' in optionsConfirmParams:
if debug: if debug:
print('Following ' + optionsActor) print('Following ' + optionsActor)
@ -1735,7 +1790,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# unfollow button on person option screen # person options screen, unfollow button
# See htmlPersonOptions
if '&submitUnfollow=' in optionsConfirmParams: if '&submitUnfollow=' in optionsConfirmParams:
if debug: if debug:
print('Unfollowing ' + optionsActor) print('Unfollowing ' + optionsActor)
@ -1751,7 +1807,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# DM button on person option screen # person options screen, DM button
# See htmlPersonOptions
if '&submitDM=' in optionsConfirmParams: if '&submitDM=' in optionsConfirmParams:
if debug: if debug:
print('Sending DM to ' + optionsActor) print('Sending DM to ' + optionsActor)
@ -1771,7 +1828,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# snooze button on person option screen # person options screen, snooze button
# See htmlPersonOptions
if '&submitSnooze=' in optionsConfirmParams: if '&submitSnooze=' in optionsConfirmParams:
usersPath = path.split('/personoptions')[0] usersPath = path.split('/personoptions')[0]
thisActor = httpPrefix + '://' + domainFull + usersPath thisActor = httpPrefix + '://' + domainFull + usersPath
@ -1792,7 +1850,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# unsnooze button on person option screen # person options screen, unsnooze button
# See htmlPersonOptions
if '&submitUnSnooze=' in optionsConfirmParams: if '&submitUnSnooze=' in optionsConfirmParams:
usersPath = path.split('/personoptions')[0] usersPath = path.split('/personoptions')[0]
thisActor = httpPrefix + '://' + domainFull + usersPath thisActor = httpPrefix + '://' + domainFull + usersPath
@ -1813,7 +1872,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
# report button on person option screen # person options screen, report button
# See htmlPersonOptions
if '&submitReport=' in optionsConfirmParams: if '&submitReport=' in optionsConfirmParams:
if debug: if debug:
print('Reporting ' + optionsActor) print('Reporting ' + optionsActor)
@ -3309,10 +3369,95 @@ class PubServer(BaseHTTPRequestHandler):
if fields['displayNickname'] != actorJson['name']: if fields['displayNickname'] != actorJson['name']:
actorJson['name'] = fields['displayNickname'] actorJson['name'] = fields['displayNickname']
actorChanged = True actorChanged = True
# change media instance status
if fields.get('mediaInstance'):
self.server.mediaInstance = False
self.server.defaultTimeline = 'inbox'
if fields['mediaInstance'] == 'on':
self.server.mediaInstance = True
self.server.blogsInstance = False
self.server.newsInstance = False
self.server.defaultTimeline = 'tlmedia'
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
setConfigParam(baseDir,
"newsInstance",
self.server.newsInstance)
else:
if self.server.mediaInstance:
self.server.mediaInstance = False
self.server.defaultTimeline = 'inbox'
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
# change news instance status
if fields.get('newsInstance'):
self.server.newsInstance = False
self.server.defaultTimeline = 'inbox'
if fields['newsInstance'] == 'on':
self.server.newsInstance = True
self.server.blogsInstance = False
self.server.mediaInstance = False
self.server.defaultTimeline = 'tlnews'
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
setConfigParam(baseDir,
"newsInstance",
self.server.newsInstance)
else:
if self.server.newsInstance:
self.server.newsInstance = False
self.server.defaultTimeline = 'inbox'
setConfigParam(baseDir,
"newsInstance",
self.server.mediaInstance)
# change blog instance status
if fields.get('blogsInstance'):
self.server.blogsInstance = False
self.server.defaultTimeline = 'inbox'
if fields['blogsInstance'] == 'on':
self.server.blogsInstance = True
self.server.mediaInstance = False
self.server.newsInstance = False
self.server.defaultTimeline = 'tlblogs'
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
setConfigParam(baseDir,
"newsInstance",
self.server.newsInstance)
else:
if self.server.blogsInstance:
self.server.blogsInstance = False
self.server.defaultTimeline = 'inbox'
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
# change theme
if fields.get('themeDropdown'): if fields.get('themeDropdown'):
setTheme(baseDir, setTheme(baseDir,
fields['themeDropdown'], fields['themeDropdown'],
domain) domain)
setNewsAvatar(baseDir,
fields['themeDropdown'],
httpPrefix,
domain,
domainFull)
# change email address # change email address
currentEmailAddress = getEmailAddress(actorJson) currentEmailAddress = getEmailAddress(actorJson)
@ -3653,84 +3798,6 @@ class PubServer(BaseHTTPRequestHandler):
if currTheme: if currTheme:
setTheme(baseDir, currTheme, domain) setTheme(baseDir, currTheme, domain)
# change media instance status
if fields.get('mediaInstance'):
self.server.mediaInstance = False
self.server.defaultTimeline = 'inbox'
if fields['mediaInstance'] == 'on':
self.server.mediaInstance = True
self.server.blogsInstance = False
self.server.newsInstance = False
self.server.defaultTimeline = 'tlmedia'
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
setConfigParam(baseDir,
"newsInstance",
self.server.newsInstance)
else:
if self.server.mediaInstance:
self.server.mediaInstance = False
self.server.defaultTimeline = 'inbox'
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
# change news instance status
if fields.get('newsInstance'):
self.server.newsInstance = False
self.server.defaultTimeline = 'inbox'
if fields['newsInstance'] == 'on':
self.server.newsInstance = True
self.server.blogsInstance = False
self.server.mediaInstance = False
self.server.defaultTimeline = 'tlnews'
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
setConfigParam(baseDir,
"newsInstance",
self.server.newsInstance)
else:
if self.server.newsInstance:
self.server.newsInstance = False
self.server.defaultTimeline = 'inbox'
setConfigParam(baseDir,
"newsInstance",
self.server.mediaInstance)
# change blog instance status
if fields.get('blogsInstance'):
self.server.blogsInstance = False
self.server.defaultTimeline = 'inbox'
if fields['blogsInstance'] == 'on':
self.server.blogsInstance = True
self.server.mediaInstance = False
self.server.newsInstance = False
self.server.defaultTimeline = 'tlblogs'
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
setConfigParam(baseDir,
"mediaInstance",
self.server.mediaInstance)
setConfigParam(baseDir,
"newsInstance",
self.server.newsInstance)
else:
if self.server.blogsInstance:
self.server.blogsInstance = False
self.server.defaultTimeline = 'inbox'
setConfigParam(baseDir,
"blogsInstance",
self.server.blogsInstance)
# only receive DMs from accounts you follow # only receive DMs from accounts you follow
followDMsFilename = \ followDMsFilename = \
baseDir + '/accounts/' + \ baseDir + '/accounts/' + \
@ -4211,7 +4278,8 @@ class PubServer(BaseHTTPRequestHandler):
nickname, nickname,
domain, domain,
port, port,
maxPostsInRSSFeed, 1) maxPostsInRSSFeed, 1,
True)
if msg is not None: if msg is not None:
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self._set_headers('text/xml', len(msg), self._set_headers('text/xml', len(msg),
@ -4229,6 +4297,66 @@ class PubServer(BaseHTTPRequestHandler):
path + ' ' + callingDomain) path + ' ' + callingDomain)
self._404() self._404()
def _getRSS2site(self, authorized: bool,
callingDomain: str, path: str,
baseDir: str, httpPrefix: str,
domainFull: str, port: int, proxyType: str,
translate: {},
GETstartTime, GETtimings: {},
debug: bool):
"""Returns an RSS2 feed for all blogs on this instance
"""
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 = ''
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct or 'news@' in acct:
continue
nickname = acct.split('@')[0]
domain = acct.split('@')[1]
msg += \
htmlBlogPageRSS2(authorized,
self.server.session,
baseDir,
httpPrefix,
self.server.translate,
nickname,
domain,
port,
maxPostsInRSSFeed, 1,
False)
if msg:
msg = rss2Header(httpPrefix,
'news', domainFull,
'Site', translate) + msg + rss2Footer()
msg = msg.encode('utf-8')
self._set_headers('text/xml', len(msg),
None, callingDomain)
self._write(msg)
if debug:
print('Sent rss2 feed: ' +
path + ' ' + callingDomain)
self._benchmarkGETtimings(GETstartTime, GETtimings,
'sharedInbox enabled',
'blog rss2')
return
if debug:
print('Failed to get rss2 feed: ' +
path + ' ' + callingDomain)
self._404()
def _getNewswireFeed(self, authorized: bool, def _getNewswireFeed(self, authorized: bool,
callingDomain: str, path: str, callingDomain: str, path: str,
baseDir: str, httpPrefix: str, baseDir: str, httpPrefix: str,
@ -4358,6 +4486,7 @@ class PubServer(BaseHTTPRequestHandler):
PGPfingerprint = getPGPfingerprint(actorJson) PGPfingerprint = getPGPfingerprint(actorJson)
msg = htmlPersonOptions(self.server.translate, msg = htmlPersonOptions(self.server.translate,
baseDir, domain, baseDir, domain,
domainFull,
originPathStr, originPathStr,
optionsActor, optionsActor,
optionsProfileUrl, optionsProfileUrl,
@ -5870,6 +5999,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, self.server.personCache,
YTReplacementDomain, YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire,
actorJson['roles'], actorJson['roles'],
None, None) None, None)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
@ -5943,6 +6073,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, self.server.personCache,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
self.server.newswire,
actorJson['skills'], actorJson['skills'],
None, None) None, None)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
@ -7405,6 +7536,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, self.server.personCache,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire,
shares, shares,
pageNumber, sharesPerPage) pageNumber, sharesPerPage)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
@ -7492,6 +7624,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, self.server.personCache,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire,
following, following,
pageNumber, pageNumber,
followsPerPage).encode('utf-8') followsPerPage).encode('utf-8')
@ -7579,6 +7712,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, self.server.personCache,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire,
followers, followers,
pageNumber, pageNumber,
followsPerPage).encode('utf-8') followsPerPage).encode('utf-8')
@ -7641,6 +7775,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, self.server.personCache,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.newswire,
None, None).encode('utf-8') None, None).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
@ -7748,23 +7883,28 @@ class PubServer(BaseHTTPRequestHandler):
divertToLoginScreen = False divertToLoginScreen = False
if divertToLoginScreen and not authorized: if divertToLoginScreen and not authorized:
if debug: divertPath = '/login'
if self.server.newsInstance:
# for news instances if not logged in then show the
# front page
divertPath = '/users/news'
# if debug:
print('DEBUG: divertToLoginScreen=' + print('DEBUG: divertToLoginScreen=' +
str(divertToLoginScreen)) str(divertToLoginScreen))
print('DEBUG: authorized=' + str(authorized)) print('DEBUG: authorized=' + str(authorized))
print('DEBUG: path=' + path) print('DEBUG: path=' + path)
if callingDomain.endswith('.onion') and onionDomain: if callingDomain.endswith('.onion') and onionDomain:
self._redirect_headers('http://' + self._redirect_headers('http://' +
onionDomain + '/login', onionDomain + divertPath,
None, callingDomain) None, callingDomain)
elif callingDomain.endswith('.i2p') and i2pDomain: elif callingDomain.endswith('.i2p') and i2pDomain:
self._redirect_headers('http://' + self._redirect_headers('http://' +
i2pDomain + '/login', i2pDomain + divertPath,
None, callingDomain) None, callingDomain)
else: else:
self._redirect_headers(httpPrefix + '://' + self._redirect_headers(httpPrefix + '://' +
domainFull + domainFull +
'/login', None, callingDomain) divertPath, None, callingDomain)
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
'robots txt', 'robots txt',
'show login screen') 'show login screen')
@ -8328,11 +8468,31 @@ class PubServer(BaseHTTPRequestHandler):
'_mastoApi(callingDomain)') '_mastoApi(callingDomain)')
if self.path == '/logout': if self.path == '/logout':
if not self.server.newsInstance:
msg = \ msg = \
htmlLogin(self.server.translate, htmlLogin(self.server.translate,
self.server.baseDir, False).encode('utf-8') self.server.baseDir, False).encode('utf-8')
self._logout_headers('text/html', len(msg), callingDomain) self._logout_headers('text/html', len(msg), callingDomain)
self._write(msg) self._write(msg)
else:
if callingDomain.endswith('.onion') and \
self.server.onionDomain:
self._logout_redirect('http://' +
self.server.onionDomain +
'/users/news', None,
callingDomain)
elif (callingDomain.endswith('.i2p') and
self.server.i2pDomain):
self._logout_redirect('http://' +
self.server.i2pDomain +
'/users/news', None,
callingDomain)
else:
self._logout_redirect(self.server.httpPrefix +
'://' +
self.server.domainFull +
'/users/news',
None, callingDomain)
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
'_nodeinfo(callingDomain)', '_nodeinfo(callingDomain)',
'logout') 'logout')
@ -8465,6 +8625,7 @@ class PubServer(BaseHTTPRequestHandler):
# RSS 2.0 # RSS 2.0
if self.path.startswith('/blog/') and \ if self.path.startswith('/blog/') and \
self.path.endswith('/rss.xml'): self.path.endswith('/rss.xml'):
if not self.path == '/blog/rss.xml':
self._getRSS2feed(authorized, self._getRSS2feed(authorized,
callingDomain, self.path, callingDomain, self.path,
self.server.baseDir, self.server.baseDir,
@ -8474,6 +8635,17 @@ class PubServer(BaseHTTPRequestHandler):
self.server.proxyType, self.server.proxyType,
GETstartTime, GETtimings, GETstartTime, GETtimings,
self.server.debug) self.server.debug)
else:
self._getRSS2site(authorized,
callingDomain, self.path,
self.server.baseDir,
self.server.httpPrefix,
self.server.domainFull,
self.server.port,
self.server.proxyType,
self.server.translate,
GETstartTime, GETtimings,
self.server.debug)
return return
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
@ -9067,8 +9239,11 @@ class PubServer(BaseHTTPRequestHandler):
'GET busy time', 'GET busy time',
'permitted directory') 'permitted directory')
if self.path.startswith('/login') or \ # show the login screen
(self.path == '/' and not authorized): if (self.path.startswith('/login') or
(self.path == '/' and
not authorized and
not self.server.newsInstance)):
# request basic auth # request basic auth
msg = htmlLogin(self.server.translate, msg = htmlLogin(self.server.translate,
self.server.baseDir).encode('utf-8') self.server.baseDir).encode('utf-8')
@ -9080,6 +9255,33 @@ class PubServer(BaseHTTPRequestHandler):
'login shown') 'login shown')
return return
# show the news front page
if self.path == '/' and \
not authorized and \
self.server.newsInstance:
if callingDomain.endswith('.onion') and \
self.server.onionDomain:
self._logout_redirect('http://' +
self.server.onionDomain +
'/users/news', None,
callingDomain)
elif (callingDomain.endswith('.i2p') and
self.server.i2pDomain):
self._logout_redirect('http://' +
self.server.i2pDomain +
'/users/news', None,
callingDomain)
else:
self._logout_redirect(self.server.httpPrefix +
'://' +
self.server.domainFull +
'/users/news',
None, callingDomain)
self._benchmarkGETtimings(GETstartTime, GETtimings,
'permitted directory',
'news front page shown')
return
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
'permitted directory', 'permitted directory',
'login shown done') 'login shown done')
@ -11558,6 +11760,11 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False self.server.POSTbusy = False
return return
if containsInvalidChars(messageBytes.decode("utf-8")):
self._400()
self.server.POSTbusy = False
return
# convert the raw bytes to json # convert the raw bytes to json
messageJson = json.loads(messageBytes) messageJson = json.loads(messageBytes)
@ -11744,7 +11951,9 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
tokensLookup[token] = nickname tokensLookup[token] = nickname
def runDaemon(showPublishedDateOnly: bool, def runDaemon(maxNewswireFeedSizeKb: int,
maxNewswirePostsPerSource: int,
showPublishedDateOnly: bool,
votingTimeMins: int, votingTimeMins: int,
positiveVoting: bool, positiveVoting: bool,
newswireVotesThreshold: int, newswireVotesThreshold: int,
@ -11864,6 +12073,15 @@ def runDaemon(showPublishedDateOnly: bool,
# number of votes needed to remove a newswire item from the news timeline # number of votes needed to remove a newswire item from the news timeline
# or if positive voting is anabled to add the item to the news timeline # or if positive voting is anabled to add the item to the news timeline
httpd.newswireVotesThreshold = newswireVotesThreshold httpd.newswireVotesThreshold = newswireVotesThreshold
# maximum overall size of an rss/atom feed read by the newswire daemon
# If the feed is too large then this is probably a DoS attempt
httpd.maxNewswireFeedSizeKb = maxNewswireFeedSizeKb
# For each newswire source (account or rss feed)
# this is the maximum number of posts to show for each.
# This avoids one or two sources from dominating the news,
# and also prevents big feeds from slowing down page load times
httpd.maxNewswirePostsPerSource = maxNewswirePostsPerSource
# Show only the date at the bottom of posts, and not the time # Show only the date at the bottom of posts, and not the time
httpd.showPublishedDateOnly = showPublishedDateOnly httpd.showPublishedDateOnly = showPublishedDateOnly
@ -11929,6 +12147,16 @@ def runDaemon(showPublishedDateOnly: bool,
print('Creating news inbox: news@' + domain) print('Creating news inbox: news@' + domain)
createNewsInbox(baseDir, domain, port, httpPrefix) createNewsInbox(baseDir, domain, port, httpPrefix)
# set the avatar for the news account
themeName = getConfigParam(baseDir, 'theme')
if not themeName:
themeName = 'default'
setNewsAvatar(baseDir,
themeName,
httpPrefix,
domain,
httpd.domainFull)
if not os.path.isdir(baseDir + '/cache'): if not os.path.isdir(baseDir + '/cache'):
os.mkdir(baseDir + '/cache') os.mkdir(baseDir + '/cache')
if not os.path.isdir(baseDir + '/cache/actors'): if not os.path.isdir(baseDir + '/cache/actors'):

View File

@ -41,7 +41,9 @@
--font-size-tox2: 8px; --font-size-tox2: 8px;
--time-color: #aaa; --time-color: #aaa;
--time-vertical-align: 4px; --time-vertical-align: 4px;
--publish-button-text: #FFFFFF;
--button-text: #FFFFFF; --button-text: #FFFFFF;
--publish-button-background: #999;
--button-background: #999; --button-background: #999;
--button-background-hover: #777; --button-background-hover: #777;
--button-selected: #666; --button-selected: #666;
@ -50,6 +52,7 @@
--button-selected-highlighted: darkgreen; --button-selected-highlighted: darkgreen;
--button-approve: darkgreen; --button-approve: darkgreen;
--button-deny: darkred; --button-deny: darkred;
--button-width-chars: 10ch;
--button-height: 10px; --button-height: 10px;
--button-height-padding-mobile: 20px; --button-height-padding-mobile: 20px;
--button-height-padding: 10px; --button-height-padding: 10px;
@ -68,7 +71,7 @@
--quote-font-weight: normal; --quote-font-weight: normal;
--quote-font-size: 120%; --quote-font-size: 120%;
--line-spacing: 130%; --line-spacing: 130%;
--line-spacing-newswire: 100%; --line-spacing-newswire: 120%;
--newswire-item-moderated-color: white; --newswire-item-moderated-color: white;
--newswire-date-moderated-color: white; --newswire-date-moderated-color: white;
--column-left-width: 10vw; --column-left-width: 10vw;
@ -81,9 +84,13 @@
--column-left-icon-size: 20%; --column-left-icon-size: 20%;
--column-left-icon-size-mobile: 10%; --column-left-icon-size-mobile: 10%;
--column-left-image-width-mobile: 40vw; --column-left-image-width-mobile: 40vw;
--column-right-image-width-mobile: 100vw;
--column-right-icon-size: 20%; --column-right-icon-size: 20%;
--column-right-icon-size-mobile: 10%;
--newswire-date-color: white; --newswire-date-color: white;
--newswire-voted-background-color: black; --newswire-voted-background-color: black;
--login-button-color: #2965;
--login-button-fg-color: black;
} }
@font-face { @font-face {
@ -577,8 +584,8 @@ input[type=submit] {
} }
.loginButton { .loginButton {
background-color: #2965; background-color: var(--login-button-color);
color: #000; color: var(--login-button-fg-color);
float: none; float: none;
margin: 0px 10px; margin: 0px 10px;
padding: 12px 40px; padding: 12px 40px;
@ -1224,11 +1231,27 @@ aside .toggle-inside li {
padding: var(--button-height-padding); padding: var(--button-height-padding);
width: 10%; width: 10%;
max-width: 200px; max-width: 200px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 5px; margin: 5px;
} }
.publishbtn {
border-radius: var(--button-corner-radius);
background-color: var(--publish-button-background);
border: none;
color: var(--publish-button-text);
text-align: center;
font-size: var(--font-size-header);
font-family: Arial, Helvetica, sans-serif;
padding: var(--button-height-padding);
width: 10%;
max-width: 200px;
min-width: var(--button-width-chars);
transition: all 0.5s;
cursor: pointer;
margin: -20px 0px;
}
.buttonhighlighted { .buttonhighlighted {
border-radius: var(--button-corner-radius); border-radius: var(--button-corner-radius);
background-color: var(--button-highlighted); background-color: var(--button-highlighted);
@ -1240,7 +1263,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding); padding: var(--button-height-padding);
width: 10%; width: 10%;
max-width: 100px; max-width: 100px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 5px; margin: 5px;
@ -1256,7 +1279,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding); padding: var(--button-height-padding);
width: 10%; width: 10%;
max-width: 100px; max-width: 100px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 5px; margin: 5px;
@ -1272,7 +1295,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding); padding: var(--button-height-padding);
width: 10%; width: 10%;
max-width: 100px; max-width: 100px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 5px; margin: 5px;
@ -1566,13 +1589,14 @@ aside .toggle-inside li {
} }
.rightColEditImage { .rightColEditImage {
background: var(--main-bg-color); background: var(--main-bg-color);
width: var(--column-right-icon-size); width: var(--column-right-icon-size-mobile);
float: right; float: right;
margin: 20px 0px; margin: 20px 0px;
} }
.rightColImg { .rightColImg {
background: var(--main-bg-color); background: var(--main-bg-color);
width: 100vw; width: var(--column-right-image-width-mobile);
float: right;
margin: 0 0; margin: 0 0;
padding: 0 0; padding: 0 0;
} }
@ -1790,7 +1814,23 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile); padding: var(--button-height-padding-mobile);
width: 20%; width: 20%;
max-width: 400px; max-width: 400px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s;
cursor: pointer;
margin: 15px;
}
.publishbtn {
border-radius: var(--button-corner-radius);
background-color: var(--publish-button-background);
border: none;
color: var(--publish-button-text);
text-align: center;
font-size: var(--font-size-newswire-mobile);
font-family: Arial, Helvetica, sans-serif;
padding: var(--button-height-padding-mobile);
width: 20%;
max-width: 400px;
min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 15px; margin: 15px;
@ -1806,7 +1846,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile); padding: var(--button-height-padding-mobile);
width: 20%; width: 20%;
max-width: 400px; max-width: 400px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 15px; margin: 15px;
@ -1822,7 +1862,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile); padding: var(--button-height-padding-mobile);
width: 20%; width: 20%;
max-width: 400px; max-width: 400px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 15px; margin: 15px;
@ -1838,7 +1878,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile); padding: var(--button-height-padding-mobile);
width: 20%; width: 20%;
max-width: 400px; max-width: 400px;
min-width: 10ch; min-width: var(--button-width-chars);
transition: all 0.5s; transition: all 0.5s;
cursor: pointer; cursor: pointer;
margin: 15px; margin: 15px;

View File

@ -112,6 +112,14 @@ parser.add_argument('--i2pDomain', dest='i2pDomain', type=str,
parser.add_argument('-p', '--port', dest='port', type=int, parser.add_argument('-p', '--port', dest='port', type=int,
default=None, default=None,
help='Port number to run on') help='Port number to run on')
parser.add_argument('--postsPerSource',
dest='maxNewswirePostsPerSource', type=int,
default=5,
help='Maximum newswire posts per feed or account')
parser.add_argument('--maxFeedSize',
dest='maxNewswireFeedSizeKb', type=int,
default=2048,
help='Maximum newswire rss/atom feed size in K')
parser.add_argument('--postcache', dest='maxRecentPosts', type=int, parser.add_argument('--postcache', dest='maxRecentPosts', type=int,
default=512, default=512,
help='The maximum number of recent posts to store in RAM') help='The maximum number of recent posts to store in RAM')
@ -1925,6 +1933,20 @@ dateonly = getConfigParam(baseDir, 'dateonly')
if dateonly: if dateonly:
args.dateonly = dateonly args.dateonly = dateonly
# set the maximum number of newswire posts per account or rss feed
maxNewswirePostsPerSource = \
getConfigParam(baseDir, 'maxNewswirePostsPerSource')
if maxNewswirePostsPerSource:
if maxNewswirePostsPerSource.isdigit():
args.maxNewswirePostsPerSource = maxNewswirePostsPerSource
# set the maximum size of a newswire rss/atom feed in Kilobytes
maxNewswireFeedSizeKb = \
getConfigParam(baseDir, 'maxNewswireFeedSizeKb')
if maxNewswireFeedSizeKb:
if maxNewswireFeedSizeKb.isdigit():
args.maxNewswireFeedSizeKb = maxNewswireFeedSizeKb
YTDomain = getConfigParam(baseDir, 'youtubedomain') YTDomain = getConfigParam(baseDir, 'youtubedomain')
if YTDomain: if YTDomain:
if '://' in YTDomain: if '://' in YTDomain:
@ -1938,7 +1960,9 @@ if setTheme(baseDir, themeName, domain):
print('Theme set to ' + themeName) print('Theme set to ' + themeName)
if __name__ == "__main__": if __name__ == "__main__":
runDaemon(args.dateonly, runDaemon(args.maxNewswireFeedSizeKb,
args.maxNewswirePostsPerSource,
args.dateonly,
args.votingtime, args.votingtime,
args.positivevoting, args.positivevoting,
args.minimumvotes, args.minimumvotes,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -202,9 +202,8 @@ def mergeWithPreviousNewswire(oldNewswire: {}, newNewswire: {}) -> None:
for published, fields in oldNewswire.items(): for published, fields in oldNewswire.items():
if not newNewswire.get(published): if not newNewswire.get(published):
continue continue
newNewswire[published][1] = fields[1] for i in range(1, 5):
newNewswire[published][2] = fields[2] newNewswire[published][i] = fields[i]
newNewswire[published][3] = fields[3]
def runNewswireDaemon(baseDir: str, httpd, def runNewswireDaemon(baseDir: str, httpd,
@ -226,7 +225,10 @@ def runNewswireDaemon(baseDir: str, httpd,
# try to update the feeds # try to update the feeds
newNewswire = None newNewswire = None
try: try:
newNewswire = getDictFromNewswire(httpd.session, baseDir) newNewswire = \
getDictFromNewswire(httpd.session, baseDir,
httpd.maxNewswirePostsPerSource,
httpd.maxNewswireFeedSizeKb)
except Exception as e: except Exception as e:
print('WARN: unable to update newswire ' + str(e)) print('WARN: unable to update newswire ' + str(e))
time.sleep(120) time.sleep(120)

View File

@ -16,6 +16,8 @@ from utils import locatePost
from utils import loadJson from utils import loadJson
from utils import saveJson from utils import saveJson
from utils import isSuspended from utils import isSuspended
from utils import containsInvalidChars
from blocking import isBlockedDomain
def rss2Header(httpPrefix: str, def rss2Header(httpPrefix: str,
@ -26,14 +28,17 @@ def rss2Header(httpPrefix: str,
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
rssStr += "<rss version=\"2.0\">" rssStr += "<rss version=\"2.0\">"
rssStr += '<channel>' rssStr += '<channel>'
if title.startswith('News'): if title.startswith('News'):
rssStr += ' <title>Newswire</title>' rssStr += ' <title>Newswire</title>'
else:
rssStr += ' <title>' + translate[title] + '</title>'
if title.startswith('News'):
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \ rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
'/newswire.xml' + '</link>' '/newswire.xml' + '</link>'
elif title.startswith('Site'):
rssStr += ' <title>' + domainFull + '</title>'
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
'/blog/rss.xml' + '</link>'
else: else:
rssStr += ' <title>' + translate[title] + '</title>'
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \ rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
'/users/' + nickname + '/rss.xml' + '</link>' '/users/' + nickname + '/rss.xml' + '</link>'
return rssStr return rssStr
@ -47,13 +52,15 @@ def rss2Footer() -> str:
return rssStr return rssStr
def xml2StrToDict(xmlStr: str, moderated: bool) -> {}: def xml2StrToDict(baseDir: str, xmlStr: str, moderated: bool,
maxPostsPerSource: int) -> {}:
"""Converts an xml 2.0 string to a dictionary """Converts an xml 2.0 string to a dictionary
""" """
if '<item>' not in xmlStr: if '<item>' not in xmlStr:
return {} return {}
result = {} result = {}
rssItems = xmlStr.split('<item>') rssItems = xmlStr.split('<item>')
postCtr = 0
for rssItem in rssItems: for rssItem in rssItems:
if '<title>' not in rssItem: if '<title>' not in rssItem:
continue continue
@ -75,6 +82,13 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
description = description.split('</description>')[0] description = description.split('</description>')[0]
link = rssItem.split('<link>')[1] link = rssItem.split('<link>')[1]
link = link.split('</link>')[0] link = link.split('</link>')[0]
if '://' not in link:
continue
domain = link.split('://')[1]
if '/' in domain:
domain = domain.split('/')[0]
if isBlockedDomain(baseDir, domain):
continue
pubDate = rssItem.split('<pubDate>')[1] pubDate = rssItem.split('<pubDate>')[1]
pubDate = pubDate.split('</pubDate>')[0] pubDate = pubDate.split('</pubDate>')[0]
parsed = False parsed = False
@ -86,6 +100,9 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
result[str(publishedDate)] = [title, link, result[str(publishedDate)] = [title, link,
votesStatus, postFilename, votesStatus, postFilename,
description, moderated] description, moderated]
postCtr += 1
if postCtr >= maxPostsPerSource:
break
parsed = True parsed = True
except BaseException: except BaseException:
pass pass
@ -93,7 +110,15 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
try: try:
publishedDate = \ publishedDate = \
datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S UT") datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S UT")
result[str(publishedDate) + '+00:00'] = [title, link] postFilename = ''
votesStatus = []
result[str(publishedDate) + '+00:00'] = \
[title, link,
votesStatus, postFilename,
description, moderated]
postCtr += 1
if postCtr >= maxPostsPerSource:
break
parsed = True parsed = True
except BaseException: except BaseException:
print('WARN: unrecognized RSS date format: ' + pubDate) print('WARN: unrecognized RSS date format: ' + pubDate)
@ -101,13 +126,15 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
return result return result
def atomFeedToDict(xmlStr: str, moderated: bool) -> {}: def atomFeedToDict(baseDir: str, xmlStr: str, moderated: bool,
maxPostsPerSource: int) -> {}:
"""Converts an atom feed string to a dictionary """Converts an atom feed string to a dictionary
""" """
if '<entry>' not in xmlStr: if '<entry>' not in xmlStr:
return {} return {}
result = {} result = {}
rssItems = xmlStr.split('<entry>') rssItems = xmlStr.split('<entry>')
postCtr = 0
for rssItem in rssItems: for rssItem in rssItems:
if '<title>' not in rssItem: if '<title>' not in rssItem:
continue continue
@ -129,6 +156,13 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
description = description.split('</summary>')[0] description = description.split('</summary>')[0]
link = rssItem.split('<link>')[1] link = rssItem.split('<link>')[1]
link = link.split('</link>')[0] link = link.split('</link>')[0]
if '://' not in link:
continue
domain = link.split('://')[1]
if '/' in domain:
domain = domain.split('/')[0]
if isBlockedDomain(baseDir, domain):
continue
pubDate = rssItem.split('<updated>')[1] pubDate = rssItem.split('<updated>')[1]
pubDate = pubDate.split('</updated>')[0] pubDate = pubDate.split('</updated>')[0]
parsed = False parsed = False
@ -140,6 +174,9 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
result[str(publishedDate)] = [title, link, result[str(publishedDate)] = [title, link,
votesStatus, postFilename, votesStatus, postFilename,
description, moderated] description, moderated]
postCtr += 1
if postCtr >= maxPostsPerSource:
break
parsed = True parsed = True
except BaseException: except BaseException:
pass pass
@ -147,7 +184,15 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
try: try:
publishedDate = \ publishedDate = \
datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S UT") datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S UT")
result[str(publishedDate) + '+00:00'] = [title, link] postFilename = ''
votesStatus = []
result[str(publishedDate) + '+00:00'] = \
[title, link,
votesStatus, postFilename,
description, moderated]
postCtr += 1
if postCtr >= maxPostsPerSource:
break
parsed = True parsed = True
except BaseException: except BaseException:
print('WARN: unrecognized atom feed date format: ' + pubDate) print('WARN: unrecognized atom feed date format: ' + pubDate)
@ -155,17 +200,20 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
return result return result
def xmlStrToDict(xmlStr: str, moderated: bool) -> {}: def xmlStrToDict(baseDir: str, xmlStr: str, moderated: bool,
maxPostsPerSource: int) -> {}:
"""Converts an xml string to a dictionary """Converts an xml string to a dictionary
""" """
if 'rss version="2.0"' in xmlStr: if 'rss version="2.0"' in xmlStr:
return xml2StrToDict(xmlStr, moderated) return xml2StrToDict(baseDir, xmlStr, moderated, maxPostsPerSource)
elif 'xmlns="http://www.w3.org/2005/Atom"' in xmlStr: elif 'xmlns="http://www.w3.org/2005/Atom"' in xmlStr:
return atomFeedToDict(xmlStr, moderated) return atomFeedToDict(baseDir, xmlStr, moderated, maxPostsPerSource)
return {} return {}
def getRSS(session, url: str, moderated: bool) -> {}: def getRSS(baseDir: str, session, url: str, moderated: bool,
maxPostsPerSource: int,
maxFeedSizeKb: int) -> {}:
"""Returns an RSS url as a dict """Returns an RSS url as a dict
""" """
if not isinstance(url, str): if not isinstance(url, str):
@ -188,7 +236,13 @@ def getRSS(session, url: str, moderated: bool) -> {}:
print('WARN: no session specified for getRSS') print('WARN: no session specified for getRSS')
try: try:
result = session.get(url, headers=sessionHeaders, params=sessionParams) result = session.get(url, headers=sessionHeaders, params=sessionParams)
return xmlStrToDict(result.text, moderated) if result:
if int(len(result.text) / 1024) < maxFeedSizeKb and \
not containsInvalidChars(result.text):
return xmlStrToDict(baseDir, result.text, moderated,
maxPostsPerSource)
else:
print('WARN: feed is too large: ' + url)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' + print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' +
'headers: ' + str(sessionHeaders) + '\n' + 'headers: ' + str(sessionHeaders) + '\n' +
@ -365,13 +419,16 @@ def addBlogsToNewswire(baseDir: str, newswire: {},
os.remove(newswireModerationFilename) os.remove(newswireModerationFilename)
def getDictFromNewswire(session, baseDir: str) -> {}: def getDictFromNewswire(session, baseDir: str,
maxPostsPerSource: int, maxFeedSizeKb: int) -> {}:
"""Gets rss feeds as a dictionary from newswire file """Gets rss feeds as a dictionary from newswire file
""" """
subscriptionsFilename = baseDir + '/accounts/newswire.txt' subscriptionsFilename = baseDir + '/accounts/newswire.txt'
if not os.path.isfile(subscriptionsFilename): if not os.path.isfile(subscriptionsFilename):
return {} return {}
maxPostsPerSource = 5
# add rss feeds # add rss feeds
rssFeed = [] rssFeed = []
with open(subscriptionsFilename, 'r') as fp: with open(subscriptionsFilename, 'r') as fp:
@ -394,12 +451,13 @@ def getDictFromNewswire(session, baseDir: str) -> {}:
moderated = True moderated = True
url = url.replace('*', '').strip() url = url.replace('*', '').strip()
itemsList = getRSS(session, url, moderated) itemsList = getRSS(baseDir, session, url, moderated,
maxPostsPerSource, maxFeedSizeKb)
for dateStr, item in itemsList.items(): for dateStr, item in itemsList.items():
result[dateStr] = item result[dateStr] = item
# add blogs from each user account # add blogs from each user account
addBlogsToNewswire(baseDir, result, 5) addBlogsToNewswire(baseDir, result, maxPostsPerSource)
# sort into chronological order, latest first # sort into chronological order, latest first
sortedResult = OrderedDict(sorted(result.items(), reverse=True)) sortedResult = OrderedDict(sorted(result.items(), reverse=True))

View File

@ -1211,13 +1211,12 @@ def createPublicPost(baseDir: str,
def createBlogPost(baseDir: str, def createBlogPost(baseDir: str,
nickname: str, domain: str, port: int, httpPrefix: str, nickname: str, domain: str, port: int, httpPrefix: str,
content: str, followersOnly: bool, saveToFile: bool, content: str, followersOnly: bool, saveToFile: bool,
clientToServer: bool, clientToServer: bool, commentsEnabled: bool,
attachImageFilename: str, mediaType: str, attachImageFilename: str, mediaType: str,
imageDescription: str, useBlurhash: bool, imageDescription: str, useBlurhash: bool,
inReplyTo=None, inReplyToAtomUri=None, subject=None, inReplyTo=None, inReplyToAtomUri=None, subject=None,
schedulePost=False, schedulePost=False,
eventDate=None, eventTime=None, location=None) -> {}: eventDate=None, eventTime=None, location=None) -> {}:
commentsEnabled = True
blog = \ blog = \
createPublicPost(baseDir, createPublicPost(baseDir,
nickname, domain, port, httpPrefix, nickname, domain, port, httpPrefix,
@ -3532,6 +3531,7 @@ def rejectAnnounce(announceFilename: str):
""" """
if not os.path.isfile(announceFilename + '.reject'): if not os.path.isfile(announceFilename + '.reject'):
rejectAnnounceFile = open(announceFilename + '.reject', "w+") rejectAnnounceFile = open(announceFilename + '.reject', "w+")
if rejectAnnounceFile:
rejectAnnounceFile.write('\n') rejectAnnounceFile.write('\n')
rejectAnnounceFile.close() rejectAnnounceFile.close()

View File

@ -288,7 +288,7 @@ def createServerAlice(path: str, domain: str, port: int,
onionDomain = None onionDomain = None
i2pDomain = None i2pDomain = None
print('Server running: Alice') print('Server running: Alice')
runDaemon(False, 0, False, 1, False, False, False, runDaemon(1024, 5, False, 0, False, 1, False, False, False,
5, True, True, 'en', __version__, 5, True, True, 'en', __version__,
"instanceId", False, path, domain, "instanceId", False, path, domain,
onionDomain, i2pDomain, None, port, port, onionDomain, i2pDomain, None, port, port,
@ -351,7 +351,7 @@ def createServerBob(path: str, domain: str, port: int,
onionDomain = None onionDomain = None
i2pDomain = None i2pDomain = None
print('Server running: Bob') print('Server running: Bob')
runDaemon(False, 0, False, 1, False, False, False, runDaemon(1024, 5, False, 0, False, 1, False, False, False,
5, True, True, 'en', __version__, 5, True, True, 'en', __version__,
"instanceId", False, path, domain, "instanceId", False, path, domain,
onionDomain, i2pDomain, None, port, port, onionDomain, i2pDomain, None, port, port,
@ -388,7 +388,7 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
onionDomain = None onionDomain = None
i2pDomain = None i2pDomain = None
print('Server running: Eve') print('Server running: Eve')
runDaemon(False, 0, False, 1, False, False, False, runDaemon(1024, 5, False, 0, False, 1, False, False, False,
5, True, True, 'en', __version__, 5, True, True, 'en', __version__,
"instanceId", False, path, domain, "instanceId", False, path, domain,
onionDomain, i2pDomain, None, port, port, onionDomain, i2pDomain, None, port, port,

View File

@ -293,6 +293,8 @@ def setThemeIndymedia(baseDir: str):
"hashtag-vertical-spacing3": "100px", "hashtag-vertical-spacing3": "100px",
"hashtag-vertical-spacing4": "150px", "hashtag-vertical-spacing4": "150px",
"button-background-hover": "darkblue", "button-background-hover": "darkblue",
"publish-button-background": "#ff9900",
"publish-button-text": "#003366",
"button-background": "#003366", "button-background": "#003366",
"button-selected": "blue", "button-selected": "blue",
"calendar-bg-color": "#0f0d10", "calendar-bg-color": "#0f0d10",
@ -310,7 +312,9 @@ def setThemeIndymedia(baseDir: str):
"column-left-width": "10vw", "column-left-width": "10vw",
"column-center-width": "70vw", "column-center-width": "70vw",
"column-right-width": "20vw", "column-right-width": "20vw",
"column-right-icon-size": "11%" "column-right-icon-size": "11%",
"login-button-color": "red",
"login-button-fg-color": "white"
} }
setThemeFromDict(baseDir, name, themeParams, bgParams) setThemeFromDict(baseDir, name, themeParams, bgParams)
@ -320,6 +324,7 @@ def setThemeBlue(baseDir: str):
removeTheme(baseDir) removeTheme(baseDir)
setThemeInConfig(baseDir, name) setThemeInConfig(baseDir, name)
themeParams = { themeParams = {
"newswire-date-color": "blue",
"font-size-header": "22px", "font-size-header": "22px",
"font-size-header-mobile": "32px", "font-size-header-mobile": "32px",
"font-size": "45px", "font-size": "45px",
@ -373,17 +378,18 @@ def setThemeNight(baseDir: str):
"link-bg-color": "#0f0d10", "link-bg-color": "#0f0d10",
"main-link-color": "ff9900", "main-link-color": "ff9900",
"main-link-color-hover": "#d09338", "main-link-color-hover": "#d09338",
"main-fg-color": "#a961ab", "main-fg-color": "#0481f5",
"column-left-fg-color": "#a961ab", "column-left-fg-color": "#0481f5",
"main-bg-color-dm": "#0b0a0a", "main-bg-color-dm": "#0b0a0a",
"border-color": "#606984", "border-color": "#606984",
"main-bg-color-reply": "#0f0d10", "main-bg-color-reply": "#0f0d10",
"main-bg-color-report": "#0f0d10", "main-bg-color-report": "#0f0d10",
"hashtag-vertical-spacing3": "100px", "hashtag-vertical-spacing3": "100px",
"hashtag-vertical-spacing4": "150px", "hashtag-vertical-spacing4": "150px",
"button-background-hover": "#6961ab", "button-background-hover": "#0481f5",
"button-background": "#a961ab", "publish-button-background": "#07447c",
"button-selected": "#86579d", "button-background": "#07447c",
"button-selected": "#0481f5",
"calendar-bg-color": "#0f0d10", "calendar-bg-color": "#0f0d10",
"lines-color": "#a961ab", "lines-color": "#a961ab",
"day-number": "#a961ab", "day-number": "#a961ab",
@ -412,6 +418,8 @@ def setThemeStarlight(baseDir: str):
removeTheme(baseDir) removeTheme(baseDir)
setThemeInConfig(baseDir, name) setThemeInConfig(baseDir, name)
themeParams = { themeParams = {
"column-left-image-width-mobile": "40vw",
"line-spacing-newswire": "120%",
"focus-color": "darkred", "focus-color": "darkred",
"font-size-button-mobile": "36px", "font-size-button-mobile": "36px",
"font-size": "32px", "font-size": "32px",
@ -437,6 +445,7 @@ def setThemeStarlight(baseDir: str):
"hashtag-vertical-spacing3": "100px", "hashtag-vertical-spacing3": "100px",
"hashtag-vertical-spacing4": "150px", "hashtag-vertical-spacing4": "150px",
"button-background-hover": "#a9282c", "button-background-hover": "#a9282c",
"publish-button-background": "#69282c",
"button-background": "#69282c", "button-background": "#69282c",
"button-small-background": "darkblue", "button-small-background": "darkblue",
"button-selected": "#a34046", "button-selected": "#a34046",
@ -474,6 +483,8 @@ def setThemeHenge(baseDir: str):
removeTheme(baseDir) removeTheme(baseDir)
setThemeInConfig(baseDir, name) setThemeInConfig(baseDir, name)
themeParams = { themeParams = {
"column-left-image-width-mobile": "40vw",
"column-right-image-width-mobile": "40vw",
"font-size-button-mobile": "36px", "font-size-button-mobile": "36px",
"font-size": "32px", "font-size": "32px",
"font-size2": "26px", "font-size2": "26px",
@ -498,6 +509,7 @@ def setThemeHenge(baseDir: str):
"hashtag-vertical-spacing3": "100px", "hashtag-vertical-spacing3": "100px",
"hashtag-vertical-spacing4": "150px", "hashtag-vertical-spacing4": "150px",
"button-background-hover": "#444", "button-background-hover": "#444",
"publish-button-background": "#222",
"button-background": "#222", "button-background": "#222",
"button-selected": "black", "button-selected": "black",
"dropdown-fg-color": "#dddddd", "dropdown-fg-color": "#dddddd",
@ -545,6 +557,7 @@ def setThemeZen(baseDir: str):
"title-color": "#dddddd", "title-color": "#dddddd",
"main-visited-color": "#dddddd", "main-visited-color": "#dddddd",
"button-background-hover": "#a63b35", "button-background-hover": "#a63b35",
"publish-button-background": "#463b35",
"button-background": "#463b35", "button-background": "#463b35",
"button-selected": "#26201d", "button-selected": "#26201d",
"main-bg-color-dm": "#5c4a40", "main-bg-color-dm": "#5c4a40",
@ -591,8 +604,12 @@ def setThemeHighVis(baseDir: str):
def setThemeLCD(baseDir: str): def setThemeLCD(baseDir: str):
name = 'lcd' name = 'lcd'
themeParams = { themeParams = {
"newswire-date-color": "#cfb42b",
"column-left-header-background": "#9fb42b",
"column-left-header-color": "#33390d",
"main-bg-color": "#9fb42b", "main-bg-color": "#9fb42b",
"column-left-color": "#9fb42b", "column-left-color": "#33390d",
"column-left-fg-color": "#9fb42b",
"link-bg-color": "#33390d", "link-bg-color": "#33390d",
"text-entry-foreground": "#33390d", "text-entry-foreground": "#33390d",
"text-entry-background": "#9fb42b", "text-entry-background": "#9fb42b",
@ -601,7 +618,6 @@ def setThemeLCD(baseDir: str):
"main-bg-color-dm": "#5fb42b", "main-bg-color-dm": "#5fb42b",
"main-header-color-roles": "#9fb42b", "main-header-color-roles": "#9fb42b",
"main-fg-color": "#33390d", "main-fg-color": "#33390d",
"column-left-fg-color": "#33390d",
"border-color": "#33390d", "border-color": "#33390d",
"border-width": "5px", "border-width": "5px",
"main-link-color": "#9fb42b", "main-link-color": "#9fb42b",
@ -611,9 +627,11 @@ def setThemeLCD(baseDir: str):
"button-selected": "black", "button-selected": "black",
"button-highlighted": "green", "button-highlighted": "green",
"button-background-hover": "#a3390d", "button-background-hover": "#a3390d",
"publish-button-background": "#33390d",
"button-background": "#33390d", "button-background": "#33390d",
"button-small-background": "#33390d", "button-small-background": "#33390d",
"button-text": "#9fb42b", "button-text": "#9fb42b",
"publish-button-text": "#9fb42b",
"button-small-text": "#9fb42b", "button-small-text": "#9fb42b",
"color: #FFFFFE;": "color: #9fb42b;", "color: #FFFFFE;": "color: #9fb42b;",
"calendar-bg-color": "#eee", "calendar-bg-color": "#eee",
@ -684,9 +702,11 @@ def setThemePurple(baseDir: str):
"main-visited-color": "#f93bb0", "main-visited-color": "#f93bb0",
"button-selected": "#c042a0", "button-selected": "#c042a0",
"button-background-hover": "#af42a0", "button-background-hover": "#af42a0",
"publish-button-background": "#ff42a0",
"button-background": "#ff42a0", "button-background": "#ff42a0",
"button-small-background": "#ff42a0", "button-small-background": "#ff42a0",
"button-text": "white", "button-text": "white",
"publish-button-text": "white",
"button-small-text": "white", "button-small-text": "white",
"color: #FFFFFE;": "color: #1f152d;", "color: #FFFFFE;": "color: #1f152d;",
"calendar-bg-color": "#eee", "calendar-bg-color": "#eee",
@ -735,9 +755,11 @@ def setThemeHacker(baseDir: str):
"main-visited-color": "#3c8234", "main-visited-color": "#3c8234",
"button-selected": "#063200", "button-selected": "#063200",
"button-background-hover": "#a62200", "button-background-hover": "#a62200",
"publish-button-background": "#062200",
"button-background": "#062200", "button-background": "#062200",
"button-small-background": "#062200", "button-small-background": "#062200",
"button-text": "#00ff00", "button-text": "#00ff00",
"publish-button-text": "#00ff00",
"button-small-text": "#00ff00", "button-small-text": "#00ff00",
"button-corner-radius": "4px", "button-corner-radius": "4px",
"timeline-border-radius": "4px", "timeline-border-radius": "4px",
@ -830,6 +852,9 @@ def setThemeLight(baseDir: str):
def setThemeSolidaric(baseDir: str): def setThemeSolidaric(baseDir: str):
name = 'solidaric' name = 'solidaric'
themeParams = { themeParams = {
"button-highlighted": "darkred",
"button-selected-highlighted": "darkred",
"newswire-date-color": "grey",
"focus-color": "grey", "focus-color": "grey",
"font-size-button-mobile": "36px", "font-size-button-mobile": "36px",
"font-size": "32px", "font-size": "32px",
@ -1006,6 +1031,31 @@ def setThemeImages(baseDir: str, name: str) -> None:
pass pass
def setNewsAvatar(baseDir: str, name: str,
httpPrefix: str,
domain: str, domainFull: str) -> None:
"""Sets the avatar for the news account
"""
nickname = 'news'
newFilename = baseDir + '/img/icons/' + name + '/avatar_news.png'
if not os.path.isfile(newFilename):
newFilename = baseDir + '/img/icons/avatar_news.png'
if not os.path.isfile(newFilename):
return
avatarFilename = \
httpPrefix + '://' + domainFull + '/users/' + nickname + '.png'
avatarFilename = avatarFilename.replace('/', '-')
filename = baseDir + '/cache/avatars/' + avatarFilename
if os.path.isfile(filename):
os.remove(filename)
if os.path.isdir(baseDir + '/cache/avatars'):
copyfile(newFilename, filename)
copyfile(newFilename,
baseDir + '/accounts/' +
nickname + '@' + domain + '/avatar.png')
def setTheme(baseDir: str, name: str, domain: str) -> bool: def setTheme(baseDir: str, name: str, domain: str) -> bool:
result = False result = False

View File

@ -308,5 +308,8 @@
"Read more...": "اقرأ أكثر...", "Read more...": "اقرأ أكثر...",
"Edit News Post": "تحرير منشور الأخبار", "Edit News Post": "تحرير منشور الأخبار",
"A list of editor nicknames. One per line.": "قائمة بأسماء المحرر. واحد في كل سطر.", "A list of editor nicknames. One per line.": "قائمة بأسماء المحرر. واحد في كل سطر.",
"Site Editors": "محررو الموقع" "Site Editors": "محررو الموقع",
"Allow news posts": "السماح بنشر الأخبار",
"Publish": "ينشر",
"Publish a news article": "انشر مقالة إخبارية"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Llegeix més...", "Read more...": "Llegeix més...",
"Edit News Post": "Edita la publicació de notícies", "Edit News Post": "Edita la publicació de notícies",
"A list of editor nicknames. One per line.": "Una llista de sobrenoms de l'editor. Un per línia.", "A list of editor nicknames. One per line.": "Una llista de sobrenoms de l'editor. Un per línia.",
"Site Editors": "Editors de llocs" "Site Editors": "Editors de llocs",
"Allow news posts": "Permet publicacions de notícies",
"Publish": "Publica",
"Publish a news article": "Publicar un article de notícies"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Darllen mwy...", "Read more...": "Darllen mwy...",
"Edit News Post": "Golygu News News", "Edit News Post": "Golygu News News",
"A list of editor nicknames. One per line.": "Rhestr o lysenwau golygydd. Un i bob llinell.", "A list of editor nicknames. One per line.": "Rhestr o lysenwau golygydd. Un i bob llinell.",
"Site Editors": "Golygyddion Safle" "Site Editors": "Golygyddion Safle",
"Allow news posts": "Caniatáu swyddi newyddion",
"Publish": "Cyhoeddi",
"Publish a news article": "Cyhoeddi erthygl newyddion"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Weiterlesen...", "Read more...": "Weiterlesen...",
"Edit News Post": "Nachrichtenbeitrag bearbeiten", "Edit News Post": "Nachrichtenbeitrag bearbeiten",
"A list of editor nicknames. One per line.": "Eine Liste der Editor-Spitznamen. Eine pro Zeile.", "A list of editor nicknames. One per line.": "Eine Liste der Editor-Spitznamen. Eine pro Zeile.",
"Site Editors": "Site-Editoren" "Site Editors": "Site-Editoren",
"Allow news posts": "Nachrichtenbeiträge zulassen",
"Publish": "Veröffentlichen",
"Publish a news article": "Veröffentlichen Sie einen Nachrichtenartikel"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Read more...", "Read more...": "Read more...",
"Edit News Post": "Edit News Post", "Edit News Post": "Edit News Post",
"A list of editor nicknames. One per line.": "A list of editor nicknames. One per line.", "A list of editor nicknames. One per line.": "A list of editor nicknames. One per line.",
"Site Editors": "Site Editors" "Site Editors": "Site Editors",
"Allow news posts": "Allow news posts",
"Publish": "Publish",
"Publish a news article": "Publish a news article"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Lee mas...", "Read more...": "Lee mas...",
"Edit News Post": "Editar publicación de noticias", "Edit News Post": "Editar publicación de noticias",
"A list of editor nicknames. One per line.": "Una lista de apodos de los editores. Uno por línea.", "A list of editor nicknames. One per line.": "Una lista de apodos de los editores. Uno por línea.",
"Site Editors": "Editores del sitio" "Site Editors": "Editores del sitio",
"Allow news posts": "Permitir publicaciones de noticias",
"Publish": "Publicar",
"Publish a news article": "Publica un artículo de noticias"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Lire la suite...", "Read more...": "Lire la suite...",
"Edit News Post": "Modifier l'article d'actualité", "Edit News Post": "Modifier l'article d'actualité",
"A list of editor nicknames. One per line.": "Une liste de surnoms d'éditeur. Un par ligne.", "A list of editor nicknames. One per line.": "Une liste de surnoms d'éditeur. Un par ligne.",
"Site Editors": "Éditeurs du site" "Site Editors": "Éditeurs du site",
"Allow news posts": "Autoriser les articles d'actualité",
"Publish": "Publier",
"Publish a news article": "Publier un article de presse"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Leigh Nios mo...", "Read more...": "Leigh Nios mo...",
"Edit News Post": "Cuir News Post in eagar", "Edit News Post": "Cuir News Post in eagar",
"A list of editor nicknames. One per line.": "Liosta leasainmneacha eagarthóra. Ceann in aghaidh na líne.", "A list of editor nicknames. One per line.": "Liosta leasainmneacha eagarthóra. Ceann in aghaidh na líne.",
"Site Editors": "Eagarthóirí Suímh" "Site Editors": "Eagarthóirí Suímh",
"Allow news posts": "Ceadaigh poist nuachta",
"Publish": "Fhoilsiú",
"Publish a news article": "Foilsigh alt nuachta"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "अधिक पढ़ें...", "Read more...": "अधिक पढ़ें...",
"Edit News Post": "समाचार पोस्ट संपादित करें", "Edit News Post": "समाचार पोस्ट संपादित करें",
"A list of editor nicknames. One per line.": "संपादक उपनामों की एक सूची। प्रति पंक्ति एक।", "A list of editor nicknames. One per line.": "संपादक उपनामों की एक सूची। प्रति पंक्ति एक।",
"Site Editors": "साइट संपादकों" "Site Editors": "साइट संपादकों",
"Allow news posts": "समाचार पोस्ट की अनुमति दें",
"Publish": "प्रकाशित करना",
"Publish a news article": "एक समाचार लेख प्रकाशित करें"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Leggi di più...", "Read more...": "Leggi di più...",
"Edit News Post": "Modifica post di notizie", "Edit News Post": "Modifica post di notizie",
"A list of editor nicknames. One per line.": "Un elenco di soprannomi dell'editor. Uno per riga.", "A list of editor nicknames. One per line.": "Un elenco di soprannomi dell'editor. Uno per riga.",
"Site Editors": "Editori del sito" "Site Editors": "Editori del sito",
"Allow news posts": "Consenti post di notizie",
"Publish": "Pubblicare",
"Publish a news article": "Pubblica un articolo di notizie"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "続きを読む...", "Read more...": "続きを読む...",
"Edit News Post": "ニュース投稿を編集する", "Edit News Post": "ニュース投稿を編集する",
"A list of editor nicknames. One per line.": "編集者のニックネームのリスト。 1行に1つ。", "A list of editor nicknames. One per line.": "編集者のニックネームのリスト。 1行に1つ。",
"Site Editors": "サイト編集者" "Site Editors": "サイト編集者",
"Allow news posts": "ニュース投稿を許可する",
"Publish": "公開する",
"Publish a news article": "ニュース記事を公開する"
} }

View File

@ -304,5 +304,8 @@
"Read more...": "Read more...", "Read more...": "Read more...",
"Edit News Post": "Edit News Post", "Edit News Post": "Edit News Post",
"A list of editor nicknames. One per line.": "A list of editor nicknames. One per line.", "A list of editor nicknames. One per line.": "A list of editor nicknames. One per line.",
"Site Editors": "Site Editors" "Site Editors": "Site Editors",
"Allow news posts": "Allow news posts",
"Publish": "Publish",
"Publish a news article": "Publish a news article"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Consulte Mais informação...", "Read more...": "Consulte Mais informação...",
"Edit News Post": "Editar Postagem de Notícias", "Edit News Post": "Editar Postagem de Notícias",
"A list of editor nicknames. One per line.": "Uma lista de apelidos de editores. Um por linha.", "A list of editor nicknames. One per line.": "Uma lista de apelidos de editores. Um por linha.",
"Site Editors": "Editores do site" "Site Editors": "Editores do site",
"Allow news posts": "Permitir postagens de notícias",
"Publish": "Publicar",
"Publish a news article": "Publique um artigo de notícias"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "Подробнее...", "Read more...": "Подробнее...",
"Edit News Post": "Редактировать новость", "Edit News Post": "Редактировать новость",
"A list of editor nicknames. One per line.": "Список ников редакторов. По одному на строку.", "A list of editor nicknames. One per line.": "Список ников редакторов. По одному на строку.",
"Site Editors": "Редакторы сайта" "Site Editors": "Редакторы сайта",
"Allow news posts": "Разрешить публикации новостей",
"Publish": "Публиковать",
"Publish a news article": "Опубликовать новостную статью"
} }

View File

@ -308,5 +308,8 @@
"Read more...": "阅读更多...", "Read more...": "阅读更多...",
"Edit News Post": "编辑新闻帖子", "Edit News Post": "编辑新闻帖子",
"A list of editor nicknames. One per line.": "编辑者昵称列表。 每行一个。", "A list of editor nicknames. One per line.": "编辑者昵称列表。 每行一个。",
"Site Editors": "网站编辑" "Site Editors": "网站编辑",
"Allow news posts": "允许新闻发布",
"Publish": "发布",
"Publish a news article": "发布新闻文章"
} }

View File

@ -19,6 +19,14 @@ from calendar import monthrange
from followingCalendar import addPersonToCalendar from followingCalendar import addPersonToCalendar
def isSystemAccount(nickname: str) -> bool:
"""Returns true if the given nickname is a system account
"""
if nickname == 'news' or nickname == 'inbox':
return True
return False
def createConfig(baseDir: str) -> None: def createConfig(baseDir: str) -> None:
"""Creates a configuration file """Creates a configuration file
""" """
@ -49,7 +57,7 @@ def getConfigParam(baseDir: str, variableName: str):
configFilename = baseDir + '/config.json' configFilename = baseDir + '/config.json'
configJson = loadJson(configFilename) configJson = loadJson(configFilename)
if configJson: if configJson:
if configJson.get(variableName): if variableName in configJson:
return configJson[variableName] return configJson[variableName]
return None return None
@ -265,6 +273,19 @@ def isEvil(domain: str) -> bool:
return False return False
def containsInvalidChars(jsonStr: str) -> bool:
"""Does the given json string contain invalid characters?
e.g. dubious clacks/admin dogwhistles
"""
invalidStrings = {
'', '', '', '', '', ''
}
for isInvalid in invalidStrings:
if isInvalid in jsonStr:
return True
return False
def createPersonDir(nickname: str, domain: str, baseDir: str, def createPersonDir(nickname: str, domain: str, baseDir: str,
dirname: str) -> str: dirname: str) -> str:
"""Create a directory for a person """Create a directory for a person

View File

@ -25,6 +25,7 @@ from ssb import getSSBAddress
from tox import getToxAddress from tox import getToxAddress
from matrix import getMatrixAddress from matrix import getMatrixAddress
from donate import getDonationUrl from donate import getDonationUrl
from utils import isSystemAccount
from utils import removeIdEnding from utils import removeIdEnding
from utils import getProtocolPrefixes from utils import getProtocolPrefixes
from utils import searchBoxPosts from utils import searchBoxPosts
@ -3232,7 +3233,7 @@ def htmlProfile(defaultTimeline: str,
session, wfRequest: {}, personCache: {}, session, wfRequest: {}, personCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
extraJson=None, newswire: {}, extraJson=None,
pageNumber=None, maxItemsPerPage=None) -> str: pageNumber=None, maxItemsPerPage=None) -> str:
"""Show the profile page as html """Show the profile page as html
""" """
@ -3296,7 +3297,7 @@ def htmlProfile(defaultTimeline: str,
PGPfingerprint or emailAddress: PGPfingerprint or emailAddress:
donateSection = '<div class="container">\n' donateSection = '<div class="container">\n'
donateSection += ' <center>\n' donateSection += ' <center>\n'
if donateUrl: if donateUrl and not isSystemAccount(nickname):
donateSection += \ donateSection += \
' <p><a href="' + donateUrl + \ ' <p><a href="' + donateUrl + \
'"><button class="donateButton">' + translate['Donate'] + \ '"><button class="donateButton">' + translate['Donate'] + \
@ -3415,13 +3416,37 @@ def htmlProfile(defaultTimeline: str,
avatarDescription = profileJson['summary'].replace('<br>', '\n') avatarDescription = profileJson['summary'].replace('<br>', '\n')
avatarDescription = avatarDescription.replace('<p>', '') avatarDescription = avatarDescription.replace('<p>', '')
avatarDescription = avatarDescription.replace('</p>', '') avatarDescription = avatarDescription.replace('</p>', '')
profileHeaderStr = '<div class="hero-image">'
profileHeaderStr += ' <div class="hero-text">' # If this is the news account then show a different banner
if isSystemAccount(nickname):
profileHeaderStr = '<div class="timeline-banner"></div>\n'
profileHeaderStr += '<center>' + loginButton + '</center>\n'
profileHeaderStr += '<table class="timeline">\n'
profileHeaderStr += ' <colgroup>\n'
profileHeaderStr += ' <col span="1" class="column-left">\n'
profileHeaderStr += ' <col span="1" class="column-center">\n'
profileHeaderStr += ' <col span="1" class="column-right">\n'
profileHeaderStr += ' </colgroup>\n'
profileHeaderStr += ' <tbody>\n'
profileHeaderStr += ' <tr>\n'
profileHeaderStr += ' <td valign="top" class="col-left">\n'
iconsDir = getIconsDir(baseDir)
profileHeaderStr += \
getLeftColumnContent(baseDir, 'news', domainFull,
httpPrefix, translate,
iconsDir, False,
False, None)
profileHeaderStr += ' </td>\n'
profileHeaderStr += ' <td valign="top" class="col-center">\n'
else:
profileHeaderStr = '<div class="hero-image">\n'
profileHeaderStr += ' <div class="hero-text">\n'
profileHeaderStr += \ profileHeaderStr += \
' <img loading="lazy" src="' + profileJson['icon']['url'] + \ ' <img loading="lazy" src="' + profileJson['icon']['url'] + \
'" title="' + avatarDescription + '" alt="' + \ '" title="' + avatarDescription + '" alt="' + \
avatarDescription + '" class="title">' avatarDescription + '" class="title">\n'
profileHeaderStr += ' <h1>' + displayName + '</h1>' profileHeaderStr += ' <h1>' + displayName + '</h1>\n'
iconsDir = getIconsDir(baseDir) iconsDir = getIconsDir(baseDir)
profileHeaderStr += \ profileHeaderStr += \
'<p><b>@' + nickname + '@' + domainFull + '</b><br>' '<p><b>@' + nickname + '@' + domainFull + '</b><br>'
@ -3429,17 +3454,19 @@ def htmlProfile(defaultTimeline: str,
'<a href="/users/' + nickname + \ '<a href="/users/' + nickname + \
'/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \ '/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \
translate['QR Code'] + '">' + \ translate['QR Code'] + '">' + \
'<img class="qrcode" src="/' + iconsDir + '/qrcode.png" /></a></p>' '<img class="qrcode" src="/' + iconsDir + \
profileHeaderStr += ' <p>' + profileDescriptionShort + '</p>' '/qrcode.png" /></a></p>\n'
profileHeaderStr += ' <p>' + profileDescriptionShort + '</p>\n'
profileHeaderStr += loginButton profileHeaderStr += loginButton
profileHeaderStr += ' </div>' profileHeaderStr += ' </div>\n'
profileHeaderStr += '</div>' profileHeaderStr += '</div>\n'
profileStr = \ profileStr = \
linkToTimelineStart + profileHeaderStr + \ linkToTimelineStart + profileHeaderStr + \
linkToTimelineEnd + donateSection linkToTimelineEnd + donateSection
profileStr += '<div class="container" id="buttonheader">\n' profileStr += '<div class="container" id="buttonheader">\n'
profileStr += ' <center>' profileStr += ' <center>'
if not isSystemAccount(nickname):
profileStr += \ profileStr += \
' <a href="' + usersPath + '#buttonheader"><button class="' + \ ' <a href="' + usersPath + '#buttonheader"><button class="' + \
postsButton + '"><span>' + translate['Posts'] + \ postsButton + '"><span>' + translate['Posts'] + \
@ -3454,7 +3481,8 @@ def htmlProfile(defaultTimeline: str,
'"><span>' + translate['Followers'] + ' </span></button></a>' '"><span>' + translate['Followers'] + ' </span></button></a>'
profileStr += \ profileStr += \
' <a href="' + usersPath + '/roles#buttonheader">' + \ ' <a href="' + usersPath + '/roles#buttonheader">' + \
'<button class="' + rolesButton + '"><span>' + translate['Roles'] + \ '<button class="' + rolesButton + '"><span>' + \
translate['Roles'] + \
' </span></button></a>' ' </span></button></a>'
profileStr += \ profileStr += \
' <a href="' + usersPath + '/skills#buttonheader">' + \ ' <a href="' + usersPath + '/skills#buttonheader">' + \
@ -3477,6 +3505,12 @@ def htmlProfile(defaultTimeline: str,
profileStyle = \ profileStyle = \
cssFile.read().replace('image.png', cssFile.read().replace('image.png',
profileJson['image']['url']) profileJson['image']['url'])
if isSystemAccount(nickname):
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain)
profileStyle = \
profileStyle.replace('banner.png',
'/users/' + nickname + '/' + bannerFile)
licenseStr = \ licenseStr = \
'<a href="https://gitlab.com/bashrc2/epicyon">' + \ '<a href="https://gitlab.com/bashrc2/epicyon">' + \
@ -3522,8 +3556,27 @@ def htmlProfile(defaultTimeline: str,
htmlProfileShares(actor, translate, htmlProfileShares(actor, translate,
nickname, domainFull, nickname, domainFull,
extraJson) + licenseStr extraJson) + licenseStr
# Footer which is only used for system accounts
profileFooterStr = ''
if isSystemAccount(nickname):
profileFooterStr = ' </td>\n'
profileFooterStr += ' <td valign="top" class="col-right">\n'
iconsDir = getIconsDir(baseDir)
profileFooterStr += \
getRightColumnContent(baseDir, 'news', domainFull,
httpPrefix, translate,
iconsDir, False, False,
newswire, False,
False, None, False)
profileFooterStr += ' </td>\n'
profileFooterStr += ' </tr>\n'
profileFooterStr += ' </tbody>\n'
profileFooterStr += '</table>\n'
profileStr = \ profileStr = \
htmlHeader(cssFilename, profileStyle) + profileStr + htmlFooter() htmlHeader(cssFilename, profileStyle) + \
profileStr + profileFooterStr + htmlFooter()
return profileStr return profileStr
@ -4426,13 +4479,21 @@ def individualPostAsHtml(allowDownloads: bool,
if timeDiff > 100: if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 7 = ' + str(timeDiff)) print('TIMING INDIV ' + boxName + ' 7 = ' + str(timeDiff))
if '/users/news/' not in avatarUrl:
avatarLink = ' <a class="imageAnchor" href="' + postActor + '">' avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
avatarLink += \ avatarLink += \
' <img loading="lazy" src="' + avatarUrl + '" title="' + \ ' <img loading="lazy" src="' + avatarUrl + '" title="' + \
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>\n' translate['Show profile'] + '" alt=" "' + avatarPosition + \
'/></a>\n'
else:
avatarLink += \
' <img loading="lazy" src="' + avatarUrl + '" title="' + \
translate['Show profile'] + '" alt=" "' + avatarPosition + \
'/>\n'
if showAvatarOptions and \ if showAvatarOptions and \
fullDomain + '/users/' + nickname not in postActor: fullDomain + '/users/' + nickname not in postActor:
if '/users/news/' not in avatarUrl:
avatarLink = \ avatarLink = \
' <a class="imageAnchor" href="/users/' + \ ' <a class="imageAnchor" href="/users/' + \
nickname + '?options=' + postActor + \ nickname + '?options=' + postActor + \
@ -4441,6 +4502,12 @@ def individualPostAsHtml(allowDownloads: bool,
' <img loading="lazy" title="' + \ ' <img loading="lazy" title="' + \
translate['Show options for this person'] + \ translate['Show options for this person'] + \
'" src="' + avatarUrl + '" ' + avatarPosition + '/></a>\n' '" src="' + avatarUrl + '" ' + avatarPosition + '/></a>\n'
else:
# don't link to the person options for the news account
avatarLink += \
' <img loading="lazy" title="' + \
translate['Show options for this person'] + \
'" src="' + avatarUrl + '" ' + avatarPosition + '/>\n'
avatarImageInPost = \ avatarImageInPost = \
' <div class="timeline-avatar">' + avatarLink.strip() + '</div>\n' ' <div class="timeline-avatar">' + avatarLink.strip() + '</div>\n'
@ -4929,13 +4996,17 @@ def individualPostAsHtml(allowDownloads: bool,
if announceAvatarUrl: if announceAvatarUrl:
idx = 'Show options for this person' idx = 'Show options for this person'
if '/users/news/' not in announceAvatarUrl:
replyAvatarImageInPost = \ replyAvatarImageInPost = \
' ' \ ' ' \
'<div class="timeline-avatar-reply">\n' \ '<div class=' + \
' <a class="imageAnchor" ' + \ '"timeline-avatar-reply">\n' \
' ' + \
'<a class="imageAnchor" ' + \
'href="/users/' + nickname + \ 'href="/users/' + nickname + \
'?options=' + \ '?options=' + \
announceActor + ';' + str(pageNumber) + \ announceActor + ';' + \
str(pageNumber) + \
';' + announceAvatarUrl + \ ';' + announceAvatarUrl + \
messageIdStr + '">' \ messageIdStr + '">' \
'<img loading="lazy" src="' + \ '<img loading="lazy" src="' + \
@ -5437,10 +5508,15 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
iconsDir + '/edit.png" /></a>\n' iconsDir + '/edit.png" /></a>\n'
# RSS icon # RSS icon
if nickname != 'news':
# rss feed for this account
rssUrl = httpPrefix + '://' + domainFull + \
'/blog/' + nickname + '/rss.xml'
else:
# rss feed for all accounts on the instance
rssUrl = httpPrefix + '://' + domainFull + '/blog/rss.xml'
htmlStr += \ htmlStr += \
' <a href="' + \ ' <a href="' + rssUrl + '">' + \
httpPrefix + '://' + domainFull + \
'/blog/' + nickname + '/rss.xml">' + \
'<img class="' + editImageClass + \ '<img class="' + editImageClass + \
'" loading="lazy" alt="' + \ '" loading="lazy" alt="' + \
translate['RSS feed for this site'] + \ translate['RSS feed for this site'] + \
@ -5513,7 +5589,7 @@ def votesIndicator(totalVotes: int, positiveVoting: bool) -> str:
return totalVotesStr return totalVotesStr
def htmlNewswire(newswire: str, nickname: str, moderator: bool, def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
translate: {}, positiveVoting: bool, iconsDir: str) -> str: translate: {}, positiveVoting: bool, iconsDir: str) -> str:
"""Converts a newswire dict into html """Converts a newswire dict into html
""" """
@ -5521,7 +5597,7 @@ def htmlNewswire(newswire: str, nickname: str, moderator: bool,
for dateStr, item in newswire.items(): for dateStr, item in newswire.items():
publishedDate = \ publishedDate = \
datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S+00:00") datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S+00:00")
dateShown = publishedDate.strftime("%Y-%m-%d") dateShown = publishedDate.strftime("%Y-%m-%d %H:%M")
dateStrLink = dateStr.replace('T', ' ') dateStrLink = dateStr.replace('T', ' ')
dateStrLink = dateStrLink.replace('Z', '') dateStrLink = dateStrLink.replace('Z', '')
@ -5584,7 +5660,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
httpPrefix: str, translate: {}, httpPrefix: str, translate: {},
iconsDir: str, moderator: bool, editor: bool, iconsDir: str, moderator: bool, editor: bool,
newswire: {}, positiveVoting: bool, newswire: {}, positiveVoting: bool,
showBackButton: bool, timelinePath: str) -> str: showBackButton: bool, timelinePath: str,
showPublishButton: bool) -> str:
"""Returns html content for the right column """Returns html content for the right column
""" """
htmlStr = '' htmlStr = ''
@ -5627,6 +5704,14 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
'<button class="cancelbtn">' + \ '<button class="cancelbtn">' + \
translate['Go Back'] + '</button></a>\n' translate['Go Back'] + '</button></a>\n'
if showPublishButton:
htmlStr += \
' <a href="' + \
'/users/' + nickname + '/newblog" ' + \
'title="' + translate['Publish a news article'] + '">' + \
'<button class="publishbtn">' + \
translate['Publish'] + '</button></a>\n'
if editor: if editor:
if os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'): if os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'):
# show the edit icon highlighted # show the edit icon highlighted
@ -5744,11 +5829,36 @@ def htmlNewswireMobile(baseDir: str, nickname: str,
httpPrefix, translate, httpPrefix, translate,
iconsDir, moderator, editor, iconsDir, moderator, editor,
newswire, positiveVoting, newswire, positiveVoting,
True, timelinePath) True, timelinePath, True)
htmlStr += htmlFooter() htmlStr += htmlFooter()
return htmlStr return htmlStr
def getBannerFile(baseDir: str, nickname: str, domain: str) -> (str, str):
"""
returns the banner filename
"""
# filename of the banner shown at the top
bannerFile = 'banner.png'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.jpg'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.gif'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.avif'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.webp'
return bannerFile, bannerFilename
def htmlTimeline(defaultTimeline: str, def htmlTimeline(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int, recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, translate: {}, pageNumber: int,
@ -5824,23 +5934,7 @@ def htmlTimeline(defaultTimeline: str,
cssFilename = baseDir + '/epicyon.css' cssFilename = baseDir + '/epicyon.css'
# filename of the banner shown at the top # filename of the banner shown at the top
bannerFile = 'banner.png' bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.jpg'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.gif'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.avif'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.webp'
# benchmark 1 # benchmark 1
timeDiff = int((time.time() - timelineStartTime) * 1000) timeDiff = int((time.time() - timelineStartTime) * 1000)
@ -6136,7 +6230,7 @@ def htmlTimeline(defaultTimeline: str,
# typically the blogs button # typically the blogs button
# but may change if this is a blogging oriented instance # but may change if this is a blogging oriented instance
if defaultTimeline != 'tlblogs': if defaultTimeline != 'tlblogs':
if not minimal: if not minimal or defaultTimeline == 'tlnews':
tlStr += \ tlStr += \
' <a href="' + usersPath + \ ' <a href="' + usersPath + \
'/tlblogs"><button class="' + \ '/tlblogs"><button class="' + \
@ -6422,7 +6516,7 @@ def htmlTimeline(defaultTimeline: str,
httpPrefix, translate, iconsDir, httpPrefix, translate, iconsDir,
moderator, editor, moderator, editor,
newswire, positiveVoting, newswire, positiveVoting,
False, None) False, None, True)
tlStr += ' <td valign="top" class="col-right">' + \ tlStr += ' <td valign="top" class="col-right">' + \
rightColumnStr + ' </td>\n' rightColumnStr + ' </td>\n'
tlStr += ' </tr>\n' tlStr += ' </tr>\n'
@ -7190,7 +7284,8 @@ def htmlUnfollowConfirm(translate: {}, baseDir: str,
def htmlPersonOptions(translate: {}, baseDir: str, def htmlPersonOptions(translate: {}, baseDir: str,
domain: str, originPathStr: str, domain: str, domainFull: str,
originPathStr: str,
optionsActor: str, optionsActor: str,
optionsProfileUrl: str, optionsProfileUrl: str,
optionsLink: str, optionsLink: str,
@ -7328,24 +7423,37 @@ def htmlPersonOptions(translate: {}, baseDir: str,
'name="submitPetname">' + \ 'name="submitPetname">' + \
translate['Submit'] + '</button><br>\n' translate['Submit'] + '</button><br>\n'
# checkbox for receiving calendar events
if isFollowingActor(baseDir, nickname, domain, optionsActor): if isFollowingActor(baseDir, nickname, domain, optionsActor):
if receivingCalendarEvents(baseDir, nickname, domain, checkboxStr = \
optionsNickname, optionsDomainFull):
optionsStr += \
' <input type="checkbox" ' + \ ' <input type="checkbox" ' + \
'class="profilecheckbox" name="onCalendar" checked> ' + \ 'class="profilecheckbox" name="onCalendar" checked> ' + \
translate['Receive calendar events from this account'] + \ translate['Receive calendar events from this account'] + \
'\n <button type="submit" class="buttonsmall" ' + \ '\n <button type="submit" class="buttonsmall" ' + \
'name="submitOnCalendar">' + \ 'name="submitOnCalendar">' + \
translate['Submit'] + '</button><br>\n' translate['Submit'] + '</button><br>\n'
else: if not receivingCalendarEvents(baseDir, nickname, domain,
optionsStr += \ optionsNickname, optionsDomainFull):
checkboxStr = checkboxStr.replace(' checked>', '>')
optionsStr += checkboxStr
# checkbox for permission to post to newswire
if optionsDomainFull == domainFull:
if isModerator(baseDir, nickname) and \
not isModerator(baseDir, optionsNickname):
newswireBlockedFilename = \
baseDir + '/accounts/' + \
optionsNickname + '@' + optionsDomain + '/.nonewswire'
checkboxStr = \
' <input type="checkbox" ' + \ ' <input type="checkbox" ' + \
'class="profilecheckbox" name="onCalendar"> ' + \ 'class="profilecheckbox" name="postsToNews" checked> ' + \
translate['Receive calendar events from this account'] + \ translate['Allow news posts'] + \
'\n <button type="submit" class="buttonsmall" ' + \ '\n <button type="submit" class="buttonsmall" ' + \
'name="submitOnCalendar">' + \ 'name="submitPostToNews">' + \
translate['Submit'] + '</button><br>\n' translate['Submit'] + '</button><br>\n'
if os.path.isfile(newswireBlockedFilename):
checkboxStr = checkboxStr.replace(' checked>', '>')
optionsStr += checkboxStr
optionsStr += optionsLinkStr optionsStr += optionsLinkStr
optionsStr += \ optionsStr += \