Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
21
blog.py
|
|
@ -365,7 +365,7 @@ def htmlBlogPost(authorized: bool,
|
|||
blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||
'loading="lazy" alt="RSS 2.0" ' + \
|
||||
'title="RSS 2.0" src="/' + \
|
||||
iconsDir + '/rss.png" /></a>'
|
||||
iconsDir + '/logorss.png" /></a>'
|
||||
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
|
|
@ -461,7 +461,7 @@ def htmlBlogPage(authorized: bool, session,
|
|||
domainFull + '/blog/' + nickname + '/rss.xml">'
|
||||
blogStr += '<img loading="lazy" alt="RSS 2.0" ' + \
|
||||
'title="RSS 2.0" src="/' + \
|
||||
iconsDir + '/rss.png" /></a>'
|
||||
iconsDir + '/logorss.png" /></a>'
|
||||
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
|
|
@ -478,7 +478,8 @@ def htmlBlogPage(authorized: bool, session,
|
|||
def htmlBlogPageRSS2(authorized: bool, session,
|
||||
baseDir: str, httpPrefix: str, translate: {},
|
||||
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
|
||||
"""
|
||||
if ' ' in nickname or '@' in nickname or \
|
||||
|
|
@ -490,12 +491,18 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
|||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
|
||||
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, 'Blog', translate)
|
||||
blogRSS2 = ''
|
||||
if includeHeader:
|
||||
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull,
|
||||
'Blog', translate)
|
||||
|
||||
blogsIndex = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/tlblogs.index'
|
||||
if not os.path.isfile(blogsIndex):
|
||||
if includeHeader:
|
||||
return blogRSS2 + rss2Footer()
|
||||
else:
|
||||
return blogRSS2
|
||||
|
||||
timelineJson = createBlogsTimeline(session, baseDir,
|
||||
nickname, domain, port,
|
||||
|
|
@ -504,7 +511,10 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
|||
pageNumber)
|
||||
|
||||
if not timelineJson:
|
||||
if includeHeader:
|
||||
return blogRSS2 + rss2Footer()
|
||||
else:
|
||||
return blogRSS2
|
||||
|
||||
if pageNumber is not None:
|
||||
for item in timelineJson['orderedItems']:
|
||||
|
|
@ -518,7 +528,10 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
|||
domainFull, item,
|
||||
None, True)
|
||||
|
||||
if includeHeader:
|
||||
return blogRSS2 + rss2Footer()
|
||||
else:
|
||||
return blogRSS2
|
||||
|
||||
|
||||
def htmlBlogPageRSS3(authorized: bool, session,
|
||||
|
|
|
|||
440
daemon.py
|
|
@ -164,6 +164,8 @@ from shares import getSharesFeedForPerson
|
|||
from shares import addShare
|
||||
from shares import removeShare
|
||||
from shares import expireShares
|
||||
from utils import containsInvalidChars
|
||||
from utils import isSystemAccount
|
||||
from utils import setConfigParam
|
||||
from utils import getConfigParam
|
||||
from utils import removeIdEnding
|
||||
|
|
@ -194,6 +196,7 @@ from media import removeMetaData
|
|||
from cache import storePersonInCache
|
||||
from cache import getPersonFromCache
|
||||
from httpsig import verifyPostHeaders
|
||||
from theme import setNewsAvatar
|
||||
from theme import setTheme
|
||||
from theme import getTheme
|
||||
from theme import enableGrayscale
|
||||
|
|
@ -212,6 +215,8 @@ from devices import E2EEdevicesCollection
|
|||
from devices import E2EEvalidDevice
|
||||
from devices import E2EEaddDevice
|
||||
from newswire import getRSSfromDict
|
||||
from newswire import rss2Header
|
||||
from newswire import rss2Footer
|
||||
from newsdaemon import runNewswireWatchdog
|
||||
from newsdaemon import runNewswireDaemon
|
||||
import os
|
||||
|
|
@ -300,11 +305,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
accountDir = self.server.baseDir + '/accounts/' + \
|
||||
nickname + '@' + self.server.domain
|
||||
if not os.path.isdir(accountDir):
|
||||
return False
|
||||
minimalFilename = accountDir + '/minimal'
|
||||
if os.path.isfile(minimalFilename):
|
||||
return True
|
||||
minimalFilename = accountDir + '/.notminimal'
|
||||
if os.path.isfile(minimalFilename):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _setMinimal(self, nickname: str, minimal: bool) -> None:
|
||||
"""Sets whether an account should display minimal buttons
|
||||
|
|
@ -313,11 +318,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
nickname + '@' + self.server.domain
|
||||
if not os.path.isdir(accountDir):
|
||||
return
|
||||
minimalFilename = accountDir + '/minimal'
|
||||
minimalFilename = accountDir + '/.notminimal'
|
||||
minimalFileExists = os.path.isfile(minimalFilename)
|
||||
if not minimal and minimalFileExists:
|
||||
if minimal and minimalFileExists:
|
||||
os.remove(minimalFilename)
|
||||
elif minimal and not minimalFileExists:
|
||||
elif not minimal and not minimalFileExists:
|
||||
with open(minimalFilename, 'w+') as fp:
|
||||
fp.write('\n')
|
||||
|
||||
|
|
@ -521,6 +526,21 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('X-Robots-Tag', 'noindex')
|
||||
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,
|
||||
callingDomain: str) -> None:
|
||||
self.send_response(200)
|
||||
|
|
@ -1240,8 +1260,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
loginNickname, loginPassword, register = \
|
||||
htmlGetLoginCredentials(loginParams, self.server.lastLoginTime)
|
||||
if loginNickname:
|
||||
if loginNickname == 'news' or loginNickname == 'inbox':
|
||||
print('Invalid username login: ' + loginNickname)
|
||||
if isSystemAccount(loginNickname):
|
||||
print('Invalid username login: ' + loginNickname +
|
||||
' (system account)')
|
||||
self._clearLoginDetails(loginNickname, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
|
@ -1625,7 +1646,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if debug:
|
||||
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 debug:
|
||||
print('Viewing ' + optionsActor)
|
||||
|
|
@ -1634,7 +1656,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# petname submit button on person option screen
|
||||
# person options screen, petname submit button
|
||||
# See htmlPersonOptions
|
||||
if '&submitPetname=' in optionsConfirmParams and petname:
|
||||
if debug:
|
||||
print('Change petname to ' + petname)
|
||||
|
|
@ -1650,7 +1673,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# person notes submit button on person option screen
|
||||
# person options screen, person notes submit button
|
||||
# See htmlPersonOptions
|
||||
if '&submitPersonNotes=' in optionsConfirmParams:
|
||||
if debug:
|
||||
print('Change person notes')
|
||||
|
|
@ -1668,7 +1692,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# person on calendar checkbox on person option screen
|
||||
# person options screen, on calendar checkbox
|
||||
# See htmlPersonOptions
|
||||
if '&submitOnCalendar=' in optionsConfirmParams:
|
||||
onCalendar = None
|
||||
if 'onCalendar=' in optionsConfirmParams:
|
||||
|
|
@ -1694,7 +1719,35 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
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 debug:
|
||||
print('Adding block by ' + chooserNickname +
|
||||
|
|
@ -1703,7 +1756,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
domain,
|
||||
optionsNickname, optionsDomainFull)
|
||||
|
||||
# unblock button on person option screen
|
||||
# person options screen, unblock button
|
||||
# See htmlPersonOptions
|
||||
if '&submitUnblock=' in optionsConfirmParams:
|
||||
if debug:
|
||||
print('Unblocking ' + optionsActor)
|
||||
|
|
@ -1719,7 +1773,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# follow button on person option screen
|
||||
# person options screen, follow button
|
||||
# See htmlPersonOptions
|
||||
if '&submitFollow=' in optionsConfirmParams:
|
||||
if debug:
|
||||
print('Following ' + optionsActor)
|
||||
|
|
@ -1735,7 +1790,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# unfollow button on person option screen
|
||||
# person options screen, unfollow button
|
||||
# See htmlPersonOptions
|
||||
if '&submitUnfollow=' in optionsConfirmParams:
|
||||
if debug:
|
||||
print('Unfollowing ' + optionsActor)
|
||||
|
|
@ -1751,7 +1807,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# DM button on person option screen
|
||||
# person options screen, DM button
|
||||
# See htmlPersonOptions
|
||||
if '&submitDM=' in optionsConfirmParams:
|
||||
if debug:
|
||||
print('Sending DM to ' + optionsActor)
|
||||
|
|
@ -1771,7 +1828,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# snooze button on person option screen
|
||||
# person options screen, snooze button
|
||||
# See htmlPersonOptions
|
||||
if '&submitSnooze=' in optionsConfirmParams:
|
||||
usersPath = path.split('/personoptions')[0]
|
||||
thisActor = httpPrefix + '://' + domainFull + usersPath
|
||||
|
|
@ -1792,7 +1850,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# unsnooze button on person option screen
|
||||
# person options screen, unsnooze button
|
||||
# See htmlPersonOptions
|
||||
if '&submitUnSnooze=' in optionsConfirmParams:
|
||||
usersPath = path.split('/personoptions')[0]
|
||||
thisActor = httpPrefix + '://' + domainFull + usersPath
|
||||
|
|
@ -1813,7 +1872,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# report button on person option screen
|
||||
# person options screen, report button
|
||||
# See htmlPersonOptions
|
||||
if '&submitReport=' in optionsConfirmParams:
|
||||
if debug:
|
||||
print('Reporting ' + optionsActor)
|
||||
|
|
@ -3309,10 +3369,95 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if fields['displayNickname'] != actorJson['name']:
|
||||
actorJson['name'] = fields['displayNickname']
|
||||
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'):
|
||||
setTheme(baseDir,
|
||||
fields['themeDropdown'],
|
||||
domain)
|
||||
setNewsAvatar(baseDir,
|
||||
fields['themeDropdown'],
|
||||
httpPrefix,
|
||||
domain,
|
||||
domainFull)
|
||||
|
||||
# change email address
|
||||
currentEmailAddress = getEmailAddress(actorJson)
|
||||
|
|
@ -3653,84 +3798,6 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if currTheme:
|
||||
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
|
||||
followDMsFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
|
|
@ -4211,7 +4278,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
nickname,
|
||||
domain,
|
||||
port,
|
||||
maxPostsInRSSFeed, 1)
|
||||
maxPostsInRSSFeed, 1,
|
||||
True)
|
||||
if msg is not None:
|
||||
msg = msg.encode('utf-8')
|
||||
self._set_headers('text/xml', len(msg),
|
||||
|
|
@ -4229,6 +4297,66 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
path + ' ' + callingDomain)
|
||||
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,
|
||||
callingDomain: str, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
|
|
@ -4358,6 +4486,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
PGPfingerprint = getPGPfingerprint(actorJson)
|
||||
msg = htmlPersonOptions(self.server.translate,
|
||||
baseDir, domain,
|
||||
domainFull,
|
||||
originPathStr,
|
||||
optionsActor,
|
||||
optionsProfileUrl,
|
||||
|
|
@ -5870,6 +5999,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.personCache,
|
||||
YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
actorJson['roles'],
|
||||
None, None)
|
||||
msg = msg.encode('utf-8')
|
||||
|
|
@ -5943,6 +6073,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.personCache,
|
||||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
actorJson['skills'],
|
||||
None, None)
|
||||
msg = msg.encode('utf-8')
|
||||
|
|
@ -7405,6 +7536,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.personCache,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
shares,
|
||||
pageNumber, sharesPerPage)
|
||||
msg = msg.encode('utf-8')
|
||||
|
|
@ -7492,6 +7624,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.personCache,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
following,
|
||||
pageNumber,
|
||||
followsPerPage).encode('utf-8')
|
||||
|
|
@ -7579,6 +7712,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.personCache,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
followers,
|
||||
pageNumber,
|
||||
followsPerPage).encode('utf-8')
|
||||
|
|
@ -7641,6 +7775,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.personCache,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.newswire,
|
||||
None, None).encode('utf-8')
|
||||
self._set_headers('text/html', len(msg),
|
||||
cookie, callingDomain)
|
||||
|
|
@ -7748,23 +7883,28 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
divertToLoginScreen = False
|
||||
|
||||
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=' +
|
||||
str(divertToLoginScreen))
|
||||
print('DEBUG: authorized=' + str(authorized))
|
||||
print('DEBUG: path=' + path)
|
||||
if callingDomain.endswith('.onion') and onionDomain:
|
||||
self._redirect_headers('http://' +
|
||||
onionDomain + '/login',
|
||||
onionDomain + divertPath,
|
||||
None, callingDomain)
|
||||
elif callingDomain.endswith('.i2p') and i2pDomain:
|
||||
self._redirect_headers('http://' +
|
||||
i2pDomain + '/login',
|
||||
i2pDomain + divertPath,
|
||||
None, callingDomain)
|
||||
else:
|
||||
self._redirect_headers(httpPrefix + '://' +
|
||||
domainFull +
|
||||
'/login', None, callingDomain)
|
||||
divertPath, None, callingDomain)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'robots txt',
|
||||
'show login screen')
|
||||
|
|
@ -8328,11 +8468,31 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'_mastoApi(callingDomain)')
|
||||
|
||||
if self.path == '/logout':
|
||||
if not self.server.newsInstance:
|
||||
msg = \
|
||||
htmlLogin(self.server.translate,
|
||||
self.server.baseDir, False).encode('utf-8')
|
||||
self._logout_headers('text/html', len(msg), callingDomain)
|
||||
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,
|
||||
'_nodeinfo(callingDomain)',
|
||||
'logout')
|
||||
|
|
@ -8465,6 +8625,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
# RSS 2.0
|
||||
if self.path.startswith('/blog/') and \
|
||||
self.path.endswith('/rss.xml'):
|
||||
if not self.path == '/blog/rss.xml':
|
||||
self._getRSS2feed(authorized,
|
||||
callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
|
|
@ -8474,6 +8635,17 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.proxyType,
|
||||
GETstartTime, GETtimings,
|
||||
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
|
||||
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
|
|
@ -9067,8 +9239,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'GET busy time',
|
||||
'permitted directory')
|
||||
|
||||
if self.path.startswith('/login') or \
|
||||
(self.path == '/' and not authorized):
|
||||
# show the login screen
|
||||
if (self.path.startswith('/login') or
|
||||
(self.path == '/' and
|
||||
not authorized and
|
||||
not self.server.newsInstance)):
|
||||
# request basic auth
|
||||
msg = htmlLogin(self.server.translate,
|
||||
self.server.baseDir).encode('utf-8')
|
||||
|
|
@ -9080,6 +9255,33 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'login shown')
|
||||
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,
|
||||
'permitted directory',
|
||||
'login shown done')
|
||||
|
|
@ -11558,6 +11760,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
if containsInvalidChars(messageBytes.decode("utf-8")):
|
||||
self._400()
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# convert the raw bytes to json
|
||||
messageJson = json.loads(messageBytes)
|
||||
|
||||
|
|
@ -11744,7 +11951,9 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
|
|||
tokensLookup[token] = nickname
|
||||
|
||||
|
||||
def runDaemon(showPublishedDateOnly: bool,
|
||||
def runDaemon(maxNewswireFeedSizeKb: int,
|
||||
maxNewswirePostsPerSource: int,
|
||||
showPublishedDateOnly: bool,
|
||||
votingTimeMins: int,
|
||||
positiveVoting: bool,
|
||||
newswireVotesThreshold: int,
|
||||
|
|
@ -11864,6 +12073,15 @@ def runDaemon(showPublishedDateOnly: bool,
|
|||
# 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
|
||||
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
|
||||
httpd.showPublishedDateOnly = showPublishedDateOnly
|
||||
|
|
@ -11929,6 +12147,16 @@ def runDaemon(showPublishedDateOnly: bool,
|
|||
print('Creating news inbox: news@' + domain)
|
||||
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'):
|
||||
os.mkdir(baseDir + '/cache')
|
||||
if not os.path.isdir(baseDir + '/cache/actors'):
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@
|
|||
--font-size-tox2: 8px;
|
||||
--time-color: #aaa;
|
||||
--time-vertical-align: 4px;
|
||||
--publish-button-text: #FFFFFF;
|
||||
--button-text: #FFFFFF;
|
||||
--publish-button-background: #999;
|
||||
--button-background: #999;
|
||||
--button-background-hover: #777;
|
||||
--button-selected: #666;
|
||||
|
|
@ -50,6 +52,7 @@
|
|||
--button-selected-highlighted: darkgreen;
|
||||
--button-approve: darkgreen;
|
||||
--button-deny: darkred;
|
||||
--button-width-chars: 10ch;
|
||||
--button-height: 10px;
|
||||
--button-height-padding-mobile: 20px;
|
||||
--button-height-padding: 10px;
|
||||
|
|
@ -68,7 +71,7 @@
|
|||
--quote-font-weight: normal;
|
||||
--quote-font-size: 120%;
|
||||
--line-spacing: 130%;
|
||||
--line-spacing-newswire: 100%;
|
||||
--line-spacing-newswire: 120%;
|
||||
--newswire-item-moderated-color: white;
|
||||
--newswire-date-moderated-color: white;
|
||||
--column-left-width: 10vw;
|
||||
|
|
@ -81,9 +84,13 @@
|
|||
--column-left-icon-size: 20%;
|
||||
--column-left-icon-size-mobile: 10%;
|
||||
--column-left-image-width-mobile: 40vw;
|
||||
--column-right-image-width-mobile: 100vw;
|
||||
--column-right-icon-size: 20%;
|
||||
--column-right-icon-size-mobile: 10%;
|
||||
--newswire-date-color: white;
|
||||
--newswire-voted-background-color: black;
|
||||
--login-button-color: #2965;
|
||||
--login-button-fg-color: black;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
|
@ -577,8 +584,8 @@ input[type=submit] {
|
|||
}
|
||||
|
||||
.loginButton {
|
||||
background-color: #2965;
|
||||
color: #000;
|
||||
background-color: var(--login-button-color);
|
||||
color: var(--login-button-fg-color);
|
||||
float: none;
|
||||
margin: 0px 10px;
|
||||
padding: 12px 40px;
|
||||
|
|
@ -1224,11 +1231,27 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding);
|
||||
width: 10%;
|
||||
max-width: 200px;
|
||||
min-width: 10ch;
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
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 {
|
||||
border-radius: var(--button-corner-radius);
|
||||
background-color: var(--button-highlighted);
|
||||
|
|
@ -1240,7 +1263,7 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding);
|
||||
width: 10%;
|
||||
max-width: 100px;
|
||||
min-width: 10ch;
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
|
|
@ -1256,7 +1279,7 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding);
|
||||
width: 10%;
|
||||
max-width: 100px;
|
||||
min-width: 10ch;
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
|
|
@ -1272,7 +1295,7 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding);
|
||||
width: 10%;
|
||||
max-width: 100px;
|
||||
min-width: 10ch;
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
|
|
@ -1566,13 +1589,14 @@ aside .toggle-inside li {
|
|||
}
|
||||
.rightColEditImage {
|
||||
background: var(--main-bg-color);
|
||||
width: var(--column-right-icon-size);
|
||||
width: var(--column-right-icon-size-mobile);
|
||||
float: right;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
.rightColImg {
|
||||
background: var(--main-bg-color);
|
||||
width: 100vw;
|
||||
width: var(--column-right-image-width-mobile);
|
||||
float: right;
|
||||
margin: 0 0;
|
||||
padding: 0 0;
|
||||
}
|
||||
|
|
@ -1790,7 +1814,23 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding-mobile);
|
||||
width: 20%;
|
||||
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;
|
||||
cursor: pointer;
|
||||
margin: 15px;
|
||||
|
|
@ -1806,7 +1846,7 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding-mobile);
|
||||
width: 20%;
|
||||
max-width: 400px;
|
||||
min-width: 10ch;
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 15px;
|
||||
|
|
@ -1822,7 +1862,7 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding-mobile);
|
||||
width: 20%;
|
||||
max-width: 400px;
|
||||
min-width: 10ch;
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 15px;
|
||||
|
|
@ -1838,7 +1878,7 @@ aside .toggle-inside li {
|
|||
padding: var(--button-height-padding-mobile);
|
||||
width: 20%;
|
||||
max-width: 400px;
|
||||
min-width: 10ch;
|
||||
min-width: var(--button-width-chars);
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 15px;
|
||||
|
|
|
|||
26
epicyon.py
|
|
@ -112,6 +112,14 @@ parser.add_argument('--i2pDomain', dest='i2pDomain', type=str,
|
|||
parser.add_argument('-p', '--port', dest='port', type=int,
|
||||
default=None,
|
||||
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,
|
||||
default=512,
|
||||
help='The maximum number of recent posts to store in RAM')
|
||||
|
|
@ -1925,6 +1933,20 @@ dateonly = getConfigParam(baseDir, 'dateonly')
|
|||
if 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')
|
||||
if YTDomain:
|
||||
if '://' in YTDomain:
|
||||
|
|
@ -1938,7 +1960,9 @@ if setTheme(baseDir, themeName, domain):
|
|||
print('Theme set to ' + themeName)
|
||||
|
||||
if __name__ == "__main__":
|
||||
runDaemon(args.dateonly,
|
||||
runDaemon(args.maxNewswireFeedSizeKb,
|
||||
args.maxNewswirePostsPerSource,
|
||||
args.dateonly,
|
||||
args.votingtime,
|
||||
args.positivevoting,
|
||||
args.minimumvotes,
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 978 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 95 KiB |
|
|
@ -202,9 +202,8 @@ def mergeWithPreviousNewswire(oldNewswire: {}, newNewswire: {}) -> None:
|
|||
for published, fields in oldNewswire.items():
|
||||
if not newNewswire.get(published):
|
||||
continue
|
||||
newNewswire[published][1] = fields[1]
|
||||
newNewswire[published][2] = fields[2]
|
||||
newNewswire[published][3] = fields[3]
|
||||
for i in range(1, 5):
|
||||
newNewswire[published][i] = fields[i]
|
||||
|
||||
|
||||
def runNewswireDaemon(baseDir: str, httpd,
|
||||
|
|
@ -226,7 +225,10 @@ def runNewswireDaemon(baseDir: str, httpd,
|
|||
# try to update the feeds
|
||||
newNewswire = None
|
||||
try:
|
||||
newNewswire = getDictFromNewswire(httpd.session, baseDir)
|
||||
newNewswire = \
|
||||
getDictFromNewswire(httpd.session, baseDir,
|
||||
httpd.maxNewswirePostsPerSource,
|
||||
httpd.maxNewswireFeedSizeKb)
|
||||
except Exception as e:
|
||||
print('WARN: unable to update newswire ' + str(e))
|
||||
time.sleep(120)
|
||||
|
|
|
|||
88
newswire.py
|
|
@ -16,6 +16,8 @@ from utils import locatePost
|
|||
from utils import loadJson
|
||||
from utils import saveJson
|
||||
from utils import isSuspended
|
||||
from utils import containsInvalidChars
|
||||
from blocking import isBlockedDomain
|
||||
|
||||
|
||||
def rss2Header(httpPrefix: str,
|
||||
|
|
@ -26,14 +28,17 @@ def rss2Header(httpPrefix: str,
|
|||
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
|
||||
rssStr += "<rss version=\"2.0\">"
|
||||
rssStr += '<channel>'
|
||||
|
||||
if title.startswith('News'):
|
||||
rssStr += ' <title>Newswire</title>'
|
||||
else:
|
||||
rssStr += ' <title>' + translate[title] + '</title>'
|
||||
if title.startswith('News'):
|
||||
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||
'/newswire.xml' + '</link>'
|
||||
elif title.startswith('Site'):
|
||||
rssStr += ' <title>' + domainFull + '</title>'
|
||||
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||
'/blog/rss.xml' + '</link>'
|
||||
else:
|
||||
rssStr += ' <title>' + translate[title] + '</title>'
|
||||
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||
'/users/' + nickname + '/rss.xml' + '</link>'
|
||||
return rssStr
|
||||
|
|
@ -47,13 +52,15 @@ def rss2Footer() -> str:
|
|||
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
|
||||
"""
|
||||
if '<item>' not in xmlStr:
|
||||
return {}
|
||||
result = {}
|
||||
rssItems = xmlStr.split('<item>')
|
||||
postCtr = 0
|
||||
for rssItem in rssItems:
|
||||
if '<title>' not in rssItem:
|
||||
continue
|
||||
|
|
@ -75,6 +82,13 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
description = description.split('</description>')[0]
|
||||
link = rssItem.split('<link>')[1]
|
||||
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 = pubDate.split('</pubDate>')[0]
|
||||
parsed = False
|
||||
|
|
@ -86,6 +100,9 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
result[str(publishedDate)] = [title, link,
|
||||
votesStatus, postFilename,
|
||||
description, moderated]
|
||||
postCtr += 1
|
||||
if postCtr >= maxPostsPerSource:
|
||||
break
|
||||
parsed = True
|
||||
except BaseException:
|
||||
pass
|
||||
|
|
@ -93,7 +110,15 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
try:
|
||||
publishedDate = \
|
||||
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
|
||||
except BaseException:
|
||||
print('WARN: unrecognized RSS date format: ' + pubDate)
|
||||
|
|
@ -101,13 +126,15 @@ def xml2StrToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
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
|
||||
"""
|
||||
if '<entry>' not in xmlStr:
|
||||
return {}
|
||||
result = {}
|
||||
rssItems = xmlStr.split('<entry>')
|
||||
postCtr = 0
|
||||
for rssItem in rssItems:
|
||||
if '<title>' not in rssItem:
|
||||
continue
|
||||
|
|
@ -129,6 +156,13 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
description = description.split('</summary>')[0]
|
||||
link = rssItem.split('<link>')[1]
|
||||
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 = pubDate.split('</updated>')[0]
|
||||
parsed = False
|
||||
|
|
@ -140,6 +174,9 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
result[str(publishedDate)] = [title, link,
|
||||
votesStatus, postFilename,
|
||||
description, moderated]
|
||||
postCtr += 1
|
||||
if postCtr >= maxPostsPerSource:
|
||||
break
|
||||
parsed = True
|
||||
except BaseException:
|
||||
pass
|
||||
|
|
@ -147,7 +184,15 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
try:
|
||||
publishedDate = \
|
||||
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
|
||||
except BaseException:
|
||||
print('WARN: unrecognized atom feed date format: ' + pubDate)
|
||||
|
|
@ -155,17 +200,20 @@ def atomFeedToDict(xmlStr: str, moderated: bool) -> {}:
|
|||
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
|
||||
"""
|
||||
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:
|
||||
return atomFeedToDict(xmlStr, moderated)
|
||||
return atomFeedToDict(baseDir, xmlStr, moderated, maxPostsPerSource)
|
||||
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
|
||||
"""
|
||||
if not isinstance(url, str):
|
||||
|
|
@ -188,7 +236,13 @@ def getRSS(session, url: str, moderated: bool) -> {}:
|
|||
print('WARN: no session specified for getRSS')
|
||||
try:
|
||||
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:
|
||||
print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' +
|
||||
'headers: ' + str(sessionHeaders) + '\n' +
|
||||
|
|
@ -365,13 +419,16 @@ def addBlogsToNewswire(baseDir: str, newswire: {},
|
|||
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
|
||||
"""
|
||||
subscriptionsFilename = baseDir + '/accounts/newswire.txt'
|
||||
if not os.path.isfile(subscriptionsFilename):
|
||||
return {}
|
||||
|
||||
maxPostsPerSource = 5
|
||||
|
||||
# add rss feeds
|
||||
rssFeed = []
|
||||
with open(subscriptionsFilename, 'r') as fp:
|
||||
|
|
@ -394,12 +451,13 @@ def getDictFromNewswire(session, baseDir: str) -> {}:
|
|||
moderated = True
|
||||
url = url.replace('*', '').strip()
|
||||
|
||||
itemsList = getRSS(session, url, moderated)
|
||||
itemsList = getRSS(baseDir, session, url, moderated,
|
||||
maxPostsPerSource, maxFeedSizeKb)
|
||||
for dateStr, item in itemsList.items():
|
||||
result[dateStr] = item
|
||||
|
||||
# add blogs from each user account
|
||||
addBlogsToNewswire(baseDir, result, 5)
|
||||
addBlogsToNewswire(baseDir, result, maxPostsPerSource)
|
||||
|
||||
# sort into chronological order, latest first
|
||||
sortedResult = OrderedDict(sorted(result.items(), reverse=True))
|
||||
|
|
|
|||
4
posts.py
|
|
@ -1211,13 +1211,12 @@ def createPublicPost(baseDir: str,
|
|||
def createBlogPost(baseDir: str,
|
||||
nickname: str, domain: str, port: int, httpPrefix: str,
|
||||
content: str, followersOnly: bool, saveToFile: bool,
|
||||
clientToServer: bool,
|
||||
clientToServer: bool, commentsEnabled: bool,
|
||||
attachImageFilename: str, mediaType: str,
|
||||
imageDescription: str, useBlurhash: bool,
|
||||
inReplyTo=None, inReplyToAtomUri=None, subject=None,
|
||||
schedulePost=False,
|
||||
eventDate=None, eventTime=None, location=None) -> {}:
|
||||
commentsEnabled = True
|
||||
blog = \
|
||||
createPublicPost(baseDir,
|
||||
nickname, domain, port, httpPrefix,
|
||||
|
|
@ -3532,6 +3531,7 @@ def rejectAnnounce(announceFilename: str):
|
|||
"""
|
||||
if not os.path.isfile(announceFilename + '.reject'):
|
||||
rejectAnnounceFile = open(announceFilename + '.reject', "w+")
|
||||
if rejectAnnounceFile:
|
||||
rejectAnnounceFile.write('\n')
|
||||
rejectAnnounceFile.close()
|
||||
|
||||
|
|
|
|||
6
tests.py
|
|
@ -288,7 +288,7 @@ def createServerAlice(path: str, domain: str, port: int,
|
|||
onionDomain = None
|
||||
i2pDomain = None
|
||||
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__,
|
||||
"instanceId", False, path, domain,
|
||||
onionDomain, i2pDomain, None, port, port,
|
||||
|
|
@ -351,7 +351,7 @@ def createServerBob(path: str, domain: str, port: int,
|
|||
onionDomain = None
|
||||
i2pDomain = None
|
||||
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__,
|
||||
"instanceId", False, path, domain,
|
||||
onionDomain, i2pDomain, None, port, port,
|
||||
|
|
@ -388,7 +388,7 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
|
|||
onionDomain = None
|
||||
i2pDomain = None
|
||||
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__,
|
||||
"instanceId", False, path, domain,
|
||||
onionDomain, i2pDomain, None, port, port,
|
||||
|
|
|
|||
66
theme.py
|
|
@ -293,6 +293,8 @@ def setThemeIndymedia(baseDir: str):
|
|||
"hashtag-vertical-spacing3": "100px",
|
||||
"hashtag-vertical-spacing4": "150px",
|
||||
"button-background-hover": "darkblue",
|
||||
"publish-button-background": "#ff9900",
|
||||
"publish-button-text": "#003366",
|
||||
"button-background": "#003366",
|
||||
"button-selected": "blue",
|
||||
"calendar-bg-color": "#0f0d10",
|
||||
|
|
@ -310,7 +312,9 @@ def setThemeIndymedia(baseDir: str):
|
|||
"column-left-width": "10vw",
|
||||
"column-center-width": "70vw",
|
||||
"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)
|
||||
|
||||
|
|
@ -320,6 +324,7 @@ def setThemeBlue(baseDir: str):
|
|||
removeTheme(baseDir)
|
||||
setThemeInConfig(baseDir, name)
|
||||
themeParams = {
|
||||
"newswire-date-color": "blue",
|
||||
"font-size-header": "22px",
|
||||
"font-size-header-mobile": "32px",
|
||||
"font-size": "45px",
|
||||
|
|
@ -373,17 +378,18 @@ def setThemeNight(baseDir: str):
|
|||
"link-bg-color": "#0f0d10",
|
||||
"main-link-color": "ff9900",
|
||||
"main-link-color-hover": "#d09338",
|
||||
"main-fg-color": "#a961ab",
|
||||
"column-left-fg-color": "#a961ab",
|
||||
"main-fg-color": "#0481f5",
|
||||
"column-left-fg-color": "#0481f5",
|
||||
"main-bg-color-dm": "#0b0a0a",
|
||||
"border-color": "#606984",
|
||||
"main-bg-color-reply": "#0f0d10",
|
||||
"main-bg-color-report": "#0f0d10",
|
||||
"hashtag-vertical-spacing3": "100px",
|
||||
"hashtag-vertical-spacing4": "150px",
|
||||
"button-background-hover": "#6961ab",
|
||||
"button-background": "#a961ab",
|
||||
"button-selected": "#86579d",
|
||||
"button-background-hover": "#0481f5",
|
||||
"publish-button-background": "#07447c",
|
||||
"button-background": "#07447c",
|
||||
"button-selected": "#0481f5",
|
||||
"calendar-bg-color": "#0f0d10",
|
||||
"lines-color": "#a961ab",
|
||||
"day-number": "#a961ab",
|
||||
|
|
@ -412,6 +418,8 @@ def setThemeStarlight(baseDir: str):
|
|||
removeTheme(baseDir)
|
||||
setThemeInConfig(baseDir, name)
|
||||
themeParams = {
|
||||
"column-left-image-width-mobile": "40vw",
|
||||
"line-spacing-newswire": "120%",
|
||||
"focus-color": "darkred",
|
||||
"font-size-button-mobile": "36px",
|
||||
"font-size": "32px",
|
||||
|
|
@ -437,6 +445,7 @@ def setThemeStarlight(baseDir: str):
|
|||
"hashtag-vertical-spacing3": "100px",
|
||||
"hashtag-vertical-spacing4": "150px",
|
||||
"button-background-hover": "#a9282c",
|
||||
"publish-button-background": "#69282c",
|
||||
"button-background": "#69282c",
|
||||
"button-small-background": "darkblue",
|
||||
"button-selected": "#a34046",
|
||||
|
|
@ -474,6 +483,8 @@ def setThemeHenge(baseDir: str):
|
|||
removeTheme(baseDir)
|
||||
setThemeInConfig(baseDir, name)
|
||||
themeParams = {
|
||||
"column-left-image-width-mobile": "40vw",
|
||||
"column-right-image-width-mobile": "40vw",
|
||||
"font-size-button-mobile": "36px",
|
||||
"font-size": "32px",
|
||||
"font-size2": "26px",
|
||||
|
|
@ -498,6 +509,7 @@ def setThemeHenge(baseDir: str):
|
|||
"hashtag-vertical-spacing3": "100px",
|
||||
"hashtag-vertical-spacing4": "150px",
|
||||
"button-background-hover": "#444",
|
||||
"publish-button-background": "#222",
|
||||
"button-background": "#222",
|
||||
"button-selected": "black",
|
||||
"dropdown-fg-color": "#dddddd",
|
||||
|
|
@ -545,6 +557,7 @@ def setThemeZen(baseDir: str):
|
|||
"title-color": "#dddddd",
|
||||
"main-visited-color": "#dddddd",
|
||||
"button-background-hover": "#a63b35",
|
||||
"publish-button-background": "#463b35",
|
||||
"button-background": "#463b35",
|
||||
"button-selected": "#26201d",
|
||||
"main-bg-color-dm": "#5c4a40",
|
||||
|
|
@ -591,8 +604,12 @@ def setThemeHighVis(baseDir: str):
|
|||
def setThemeLCD(baseDir: str):
|
||||
name = 'lcd'
|
||||
themeParams = {
|
||||
"newswire-date-color": "#cfb42b",
|
||||
"column-left-header-background": "#9fb42b",
|
||||
"column-left-header-color": "#33390d",
|
||||
"main-bg-color": "#9fb42b",
|
||||
"column-left-color": "#9fb42b",
|
||||
"column-left-color": "#33390d",
|
||||
"column-left-fg-color": "#9fb42b",
|
||||
"link-bg-color": "#33390d",
|
||||
"text-entry-foreground": "#33390d",
|
||||
"text-entry-background": "#9fb42b",
|
||||
|
|
@ -601,7 +618,6 @@ def setThemeLCD(baseDir: str):
|
|||
"main-bg-color-dm": "#5fb42b",
|
||||
"main-header-color-roles": "#9fb42b",
|
||||
"main-fg-color": "#33390d",
|
||||
"column-left-fg-color": "#33390d",
|
||||
"border-color": "#33390d",
|
||||
"border-width": "5px",
|
||||
"main-link-color": "#9fb42b",
|
||||
|
|
@ -611,9 +627,11 @@ def setThemeLCD(baseDir: str):
|
|||
"button-selected": "black",
|
||||
"button-highlighted": "green",
|
||||
"button-background-hover": "#a3390d",
|
||||
"publish-button-background": "#33390d",
|
||||
"button-background": "#33390d",
|
||||
"button-small-background": "#33390d",
|
||||
"button-text": "#9fb42b",
|
||||
"publish-button-text": "#9fb42b",
|
||||
"button-small-text": "#9fb42b",
|
||||
"color: #FFFFFE;": "color: #9fb42b;",
|
||||
"calendar-bg-color": "#eee",
|
||||
|
|
@ -684,9 +702,11 @@ def setThemePurple(baseDir: str):
|
|||
"main-visited-color": "#f93bb0",
|
||||
"button-selected": "#c042a0",
|
||||
"button-background-hover": "#af42a0",
|
||||
"publish-button-background": "#ff42a0",
|
||||
"button-background": "#ff42a0",
|
||||
"button-small-background": "#ff42a0",
|
||||
"button-text": "white",
|
||||
"publish-button-text": "white",
|
||||
"button-small-text": "white",
|
||||
"color: #FFFFFE;": "color: #1f152d;",
|
||||
"calendar-bg-color": "#eee",
|
||||
|
|
@ -735,9 +755,11 @@ def setThemeHacker(baseDir: str):
|
|||
"main-visited-color": "#3c8234",
|
||||
"button-selected": "#063200",
|
||||
"button-background-hover": "#a62200",
|
||||
"publish-button-background": "#062200",
|
||||
"button-background": "#062200",
|
||||
"button-small-background": "#062200",
|
||||
"button-text": "#00ff00",
|
||||
"publish-button-text": "#00ff00",
|
||||
"button-small-text": "#00ff00",
|
||||
"button-corner-radius": "4px",
|
||||
"timeline-border-radius": "4px",
|
||||
|
|
@ -830,6 +852,9 @@ def setThemeLight(baseDir: str):
|
|||
def setThemeSolidaric(baseDir: str):
|
||||
name = 'solidaric'
|
||||
themeParams = {
|
||||
"button-highlighted": "darkred",
|
||||
"button-selected-highlighted": "darkred",
|
||||
"newswire-date-color": "grey",
|
||||
"focus-color": "grey",
|
||||
"font-size-button-mobile": "36px",
|
||||
"font-size": "32px",
|
||||
|
|
@ -1006,6 +1031,31 @@ def setThemeImages(baseDir: str, name: str) -> None:
|
|||
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:
|
||||
result = False
|
||||
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "اقرأ أكثر...",
|
||||
"Edit News Post": "تحرير منشور الأخبار",
|
||||
"A list of editor nicknames. One per line.": "قائمة بأسماء المحرر. واحد في كل سطر.",
|
||||
"Site Editors": "محررو الموقع"
|
||||
"Site Editors": "محررو الموقع",
|
||||
"Allow news posts": "السماح بنشر الأخبار",
|
||||
"Publish": "ينشر",
|
||||
"Publish a news article": "انشر مقالة إخبارية"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Llegeix més...",
|
||||
"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.",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Darllen mwy...",
|
||||
"Edit News Post": "Golygu News News",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Weiterlesen...",
|
||||
"Edit News Post": "Nachrichtenbeitrag bearbeiten",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
"Site Editors": "Site Editors",
|
||||
"Allow news posts": "Allow news posts",
|
||||
"Publish": "Publish",
|
||||
"Publish a news article": "Publish a news article"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Lee mas...",
|
||||
"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.",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Lire la suite...",
|
||||
"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.",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Leigh Nios mo...",
|
||||
"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.",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "अधिक पढ़ें...",
|
||||
"Edit News Post": "समाचार पोस्ट संपादित करें",
|
||||
"A list of editor nicknames. One per line.": "संपादक उपनामों की एक सूची। प्रति पंक्ति एक।",
|
||||
"Site Editors": "साइट संपादकों"
|
||||
"Site Editors": "साइट संपादकों",
|
||||
"Allow news posts": "समाचार पोस्ट की अनुमति दें",
|
||||
"Publish": "प्रकाशित करना",
|
||||
"Publish a news article": "एक समाचार लेख प्रकाशित करें"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Leggi di più...",
|
||||
"Edit News Post": "Modifica post di notizie",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "続きを読む...",
|
||||
"Edit News Post": "ニュース投稿を編集する",
|
||||
"A list of editor nicknames. One per line.": "編集者のニックネームのリスト。 1行に1つ。",
|
||||
"Site Editors": "サイト編集者"
|
||||
"Site Editors": "サイト編集者",
|
||||
"Allow news posts": "ニュース投稿を許可する",
|
||||
"Publish": "公開する",
|
||||
"Publish a news article": "ニュース記事を公開する"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,5 +304,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"
|
||||
"Site Editors": "Site Editors",
|
||||
"Allow news posts": "Allow news posts",
|
||||
"Publish": "Publish",
|
||||
"Publish a news article": "Publish a news article"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Consulte Mais informação...",
|
||||
"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.",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "Подробнее...",
|
||||
"Edit News Post": "Редактировать новость",
|
||||
"A list of editor nicknames. One per line.": "Список ников редакторов. По одному на строку.",
|
||||
"Site Editors": "Редакторы сайта"
|
||||
"Site Editors": "Редакторы сайта",
|
||||
"Allow news posts": "Разрешить публикации новостей",
|
||||
"Publish": "Публиковать",
|
||||
"Publish a news article": "Опубликовать новостную статью"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,5 +308,8 @@
|
|||
"Read more...": "阅读更多...",
|
||||
"Edit News Post": "编辑新闻帖子",
|
||||
"A list of editor nicknames. One per line.": "编辑者昵称列表。 每行一个。",
|
||||
"Site Editors": "网站编辑"
|
||||
"Site Editors": "网站编辑",
|
||||
"Allow news posts": "允许新闻发布",
|
||||
"Publish": "发布",
|
||||
"Publish a news article": "发布新闻文章"
|
||||
}
|
||||
|
|
|
|||
23
utils.py
|
|
@ -19,6 +19,14 @@ from calendar import monthrange
|
|||
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:
|
||||
"""Creates a configuration file
|
||||
"""
|
||||
|
|
@ -49,7 +57,7 @@ def getConfigParam(baseDir: str, variableName: str):
|
|||
configFilename = baseDir + '/config.json'
|
||||
configJson = loadJson(configFilename)
|
||||
if configJson:
|
||||
if configJson.get(variableName):
|
||||
if variableName in configJson:
|
||||
return configJson[variableName]
|
||||
return None
|
||||
|
||||
|
|
@ -265,6 +273,19 @@ def isEvil(domain: str) -> bool:
|
|||
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,
|
||||
dirname: str) -> str:
|
||||
"""Create a directory for a person
|
||||
|
|
|
|||
210
webinterface.py
|
|
@ -25,6 +25,7 @@ from ssb import getSSBAddress
|
|||
from tox import getToxAddress
|
||||
from matrix import getMatrixAddress
|
||||
from donate import getDonationUrl
|
||||
from utils import isSystemAccount
|
||||
from utils import removeIdEnding
|
||||
from utils import getProtocolPrefixes
|
||||
from utils import searchBoxPosts
|
||||
|
|
@ -3232,7 +3233,7 @@ def htmlProfile(defaultTimeline: str,
|
|||
session, wfRequest: {}, personCache: {},
|
||||
YTReplacementDomain: str,
|
||||
showPublishedDateOnly: bool,
|
||||
extraJson=None,
|
||||
newswire: {}, extraJson=None,
|
||||
pageNumber=None, maxItemsPerPage=None) -> str:
|
||||
"""Show the profile page as html
|
||||
"""
|
||||
|
|
@ -3296,7 +3297,7 @@ def htmlProfile(defaultTimeline: str,
|
|||
PGPfingerprint or emailAddress:
|
||||
donateSection = '<div class="container">\n'
|
||||
donateSection += ' <center>\n'
|
||||
if donateUrl:
|
||||
if donateUrl and not isSystemAccount(nickname):
|
||||
donateSection += \
|
||||
' <p><a href="' + donateUrl + \
|
||||
'"><button class="donateButton">' + translate['Donate'] + \
|
||||
|
|
@ -3415,13 +3416,37 @@ def htmlProfile(defaultTimeline: str,
|
|||
avatarDescription = profileJson['summary'].replace('<br>', '\n')
|
||||
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 += \
|
||||
' <img loading="lazy" src="' + profileJson['icon']['url'] + \
|
||||
'" title="' + avatarDescription + '" alt="' + \
|
||||
avatarDescription + '" class="title">'
|
||||
profileHeaderStr += ' <h1>' + displayName + '</h1>'
|
||||
avatarDescription + '" class="title">\n'
|
||||
profileHeaderStr += ' <h1>' + displayName + '</h1>\n'
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
profileHeaderStr += \
|
||||
'<p><b>@' + nickname + '@' + domainFull + '</b><br>'
|
||||
|
|
@ -3429,17 +3454,19 @@ def htmlProfile(defaultTimeline: str,
|
|||
'<a href="/users/' + nickname + \
|
||||
'/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \
|
||||
translate['QR Code'] + '">' + \
|
||||
'<img class="qrcode" src="/' + iconsDir + '/qrcode.png" /></a></p>'
|
||||
profileHeaderStr += ' <p>' + profileDescriptionShort + '</p>'
|
||||
'<img class="qrcode" src="/' + iconsDir + \
|
||||
'/qrcode.png" /></a></p>\n'
|
||||
profileHeaderStr += ' <p>' + profileDescriptionShort + '</p>\n'
|
||||
profileHeaderStr += loginButton
|
||||
profileHeaderStr += ' </div>'
|
||||
profileHeaderStr += '</div>'
|
||||
profileHeaderStr += ' </div>\n'
|
||||
profileHeaderStr += '</div>\n'
|
||||
|
||||
profileStr = \
|
||||
linkToTimelineStart + profileHeaderStr + \
|
||||
linkToTimelineEnd + donateSection
|
||||
profileStr += '<div class="container" id="buttonheader">\n'
|
||||
profileStr += ' <center>'
|
||||
if not isSystemAccount(nickname):
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '#buttonheader"><button class="' + \
|
||||
postsButton + '"><span>' + translate['Posts'] + \
|
||||
|
|
@ -3454,7 +3481,8 @@ def htmlProfile(defaultTimeline: str,
|
|||
'"><span>' + translate['Followers'] + ' </span></button></a>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '/roles#buttonheader">' + \
|
||||
'<button class="' + rolesButton + '"><span>' + translate['Roles'] + \
|
||||
'<button class="' + rolesButton + '"><span>' + \
|
||||
translate['Roles'] + \
|
||||
' </span></button></a>'
|
||||
profileStr += \
|
||||
' <a href="' + usersPath + '/skills#buttonheader">' + \
|
||||
|
|
@ -3477,6 +3505,12 @@ def htmlProfile(defaultTimeline: str,
|
|||
profileStyle = \
|
||||
cssFile.read().replace('image.png',
|
||||
profileJson['image']['url'])
|
||||
if isSystemAccount(nickname):
|
||||
bannerFile, bannerFilename = \
|
||||
getBannerFile(baseDir, nickname, domain)
|
||||
profileStyle = \
|
||||
profileStyle.replace('banner.png',
|
||||
'/users/' + nickname + '/' + bannerFile)
|
||||
|
||||
licenseStr = \
|
||||
'<a href="https://gitlab.com/bashrc2/epicyon">' + \
|
||||
|
|
@ -3522,8 +3556,27 @@ def htmlProfile(defaultTimeline: str,
|
|||
htmlProfileShares(actor, translate,
|
||||
nickname, domainFull,
|
||||
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 = \
|
||||
htmlHeader(cssFilename, profileStyle) + profileStr + htmlFooter()
|
||||
htmlHeader(cssFilename, profileStyle) + \
|
||||
profileStr + profileFooterStr + htmlFooter()
|
||||
return profileStr
|
||||
|
||||
|
||||
|
|
@ -4426,13 +4479,21 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
if timeDiff > 100:
|
||||
print('TIMING INDIV ' + boxName + ' 7 = ' + str(timeDiff))
|
||||
|
||||
if '/users/news/' not in avatarUrl:
|
||||
avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
|
||||
avatarLink += \
|
||||
' <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 \
|
||||
fullDomain + '/users/' + nickname not in postActor:
|
||||
if '/users/news/' not in avatarUrl:
|
||||
avatarLink = \
|
||||
' <a class="imageAnchor" href="/users/' + \
|
||||
nickname + '?options=' + postActor + \
|
||||
|
|
@ -4441,6 +4502,12 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
' <img loading="lazy" title="' + \
|
||||
translate['Show options for this person'] + \
|
||||
'" 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 = \
|
||||
' <div class="timeline-avatar">' + avatarLink.strip() + '</div>\n'
|
||||
|
||||
|
|
@ -4929,13 +4996,17 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
|
||||
if announceAvatarUrl:
|
||||
idx = 'Show options for this person'
|
||||
if '/users/news/' not in announceAvatarUrl:
|
||||
replyAvatarImageInPost = \
|
||||
' ' \
|
||||
'<div class="timeline-avatar-reply">\n' \
|
||||
' <a class="imageAnchor" ' + \
|
||||
'<div class=' + \
|
||||
'"timeline-avatar-reply">\n' \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" ' + \
|
||||
'href="/users/' + nickname + \
|
||||
'?options=' + \
|
||||
announceActor + ';' + str(pageNumber) + \
|
||||
announceActor + ';' + \
|
||||
str(pageNumber) + \
|
||||
';' + announceAvatarUrl + \
|
||||
messageIdStr + '">' \
|
||||
'<img loading="lazy" src="' + \
|
||||
|
|
@ -5437,10 +5508,15 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
|||
iconsDir + '/edit.png" /></a>\n'
|
||||
|
||||
# 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 += \
|
||||
' <a href="' + \
|
||||
httpPrefix + '://' + domainFull + \
|
||||
'/blog/' + nickname + '/rss.xml">' + \
|
||||
' <a href="' + rssUrl + '">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['RSS feed for this site'] + \
|
||||
|
|
@ -5513,7 +5589,7 @@ def votesIndicator(totalVotes: int, positiveVoting: bool) -> str:
|
|||
return totalVotesStr
|
||||
|
||||
|
||||
def htmlNewswire(newswire: str, nickname: str, moderator: bool,
|
||||
def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
|
||||
translate: {}, positiveVoting: bool, iconsDir: str) -> str:
|
||||
"""Converts a newswire dict into html
|
||||
"""
|
||||
|
|
@ -5521,7 +5597,7 @@ def htmlNewswire(newswire: str, nickname: str, moderator: bool,
|
|||
for dateStr, item in newswire.items():
|
||||
publishedDate = \
|
||||
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 = dateStrLink.replace('Z', '')
|
||||
|
|
@ -5584,7 +5660,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
|
|||
httpPrefix: str, translate: {},
|
||||
iconsDir: str, moderator: bool, editor: bool,
|
||||
newswire: {}, positiveVoting: bool,
|
||||
showBackButton: bool, timelinePath: str) -> str:
|
||||
showBackButton: bool, timelinePath: str,
|
||||
showPublishButton: bool) -> str:
|
||||
"""Returns html content for the right column
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
|
@ -5627,6 +5704,14 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
|
|||
'<button class="cancelbtn">' + \
|
||||
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 os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'):
|
||||
# show the edit icon highlighted
|
||||
|
|
@ -5744,11 +5829,36 @@ def htmlNewswireMobile(baseDir: str, nickname: str,
|
|||
httpPrefix, translate,
|
||||
iconsDir, moderator, editor,
|
||||
newswire, positiveVoting,
|
||||
True, timelinePath)
|
||||
True, timelinePath, True)
|
||||
htmlStr += htmlFooter()
|
||||
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,
|
||||
recentPostsCache: {}, maxRecentPosts: int,
|
||||
translate: {}, pageNumber: int,
|
||||
|
|
@ -5824,23 +5934,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
# 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'
|
||||
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
|
||||
|
||||
# benchmark 1
|
||||
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
||||
|
|
@ -6136,7 +6230,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
# typically the blogs button
|
||||
# but may change if this is a blogging oriented instance
|
||||
if defaultTimeline != 'tlblogs':
|
||||
if not minimal:
|
||||
if not minimal or defaultTimeline == 'tlnews':
|
||||
tlStr += \
|
||||
' <a href="' + usersPath + \
|
||||
'/tlblogs"><button class="' + \
|
||||
|
|
@ -6422,7 +6516,7 @@ def htmlTimeline(defaultTimeline: str,
|
|||
httpPrefix, translate, iconsDir,
|
||||
moderator, editor,
|
||||
newswire, positiveVoting,
|
||||
False, None)
|
||||
False, None, True)
|
||||
tlStr += ' <td valign="top" class="col-right">' + \
|
||||
rightColumnStr + ' </td>\n'
|
||||
tlStr += ' </tr>\n'
|
||||
|
|
@ -7190,7 +7284,8 @@ def htmlUnfollowConfirm(translate: {}, baseDir: str,
|
|||
|
||||
|
||||
def htmlPersonOptions(translate: {}, baseDir: str,
|
||||
domain: str, originPathStr: str,
|
||||
domain: str, domainFull: str,
|
||||
originPathStr: str,
|
||||
optionsActor: str,
|
||||
optionsProfileUrl: str,
|
||||
optionsLink: str,
|
||||
|
|
@ -7328,24 +7423,37 @@ def htmlPersonOptions(translate: {}, baseDir: str,
|
|||
'name="submitPetname">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
|
||||
# checkbox for receiving calendar events
|
||||
if isFollowingActor(baseDir, nickname, domain, optionsActor):
|
||||
if receivingCalendarEvents(baseDir, nickname, domain,
|
||||
optionsNickname, optionsDomainFull):
|
||||
optionsStr += \
|
||||
checkboxStr = \
|
||||
' <input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="onCalendar" checked> ' + \
|
||||
translate['Receive calendar events from this account'] + \
|
||||
'\n <button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitOnCalendar">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
else:
|
||||
optionsStr += \
|
||||
if not receivingCalendarEvents(baseDir, nickname, domain,
|
||||
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" ' + \
|
||||
'class="profilecheckbox" name="onCalendar"> ' + \
|
||||
translate['Receive calendar events from this account'] + \
|
||||
'class="profilecheckbox" name="postsToNews" checked> ' + \
|
||||
translate['Allow news posts'] + \
|
||||
'\n <button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitOnCalendar">' + \
|
||||
'name="submitPostToNews">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
if os.path.isfile(newswireBlockedFilename):
|
||||
checkboxStr = checkboxStr.replace(' checked>', '>')
|
||||
optionsStr += checkboxStr
|
||||
|
||||
optionsStr += optionsLinkStr
|
||||
optionsStr += \
|
||||
|
|
|
|||