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

main
Bob Mottram 2020-11-03 17:51:34 +00:00
commit f38cbd22b8
7 changed files with 137 additions and 63 deletions

View File

@ -53,7 +53,7 @@ sudo apt install -y \
In the most common case you'll be using systemd to set up a daemon to run the server. In the most common case you'll be using systemd to set up a daemon to run the server.
The following instructions install Epicyon to the **/etc** directory. It's not essential that it be installed there, and it could be in any other preferred directory. The following instructions install Epicyon to the **/opt** directory. It's not essential that it be installed there, and it could be in any other preferred directory.
Add a dedicated user so that we don't have to run as root. Add a dedicated user so that we don't have to run as root.

View File

@ -12222,7 +12222,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
tokensLookup[token] = nickname tokensLookup[token] = nickname
def runDaemon(publishButtonAtTop: bool, def runDaemon(maxFeedItemSizeKb: int,
publishButtonAtTop: bool,
rssIconAtTop: bool, rssIconAtTop: bool,
iconsAsButtons: bool, iconsAsButtons: bool,
fullWidthTimelineButtonHeader: bool, fullWidthTimelineButtonHeader: bool,
@ -12400,6 +12401,9 @@ def runDaemon(publishButtonAtTop: bool,
# above the header image # above the header image
httpd.publishButtonAtTop = publishButtonAtTop httpd.publishButtonAtTop = publishButtonAtTop
# maximum size of individual RSS feed items, in K
httpd.maxFeedItemSizeKb = maxFeedItemSizeKb
if registration == 'open': if registration == 'open':
httpd.registration = True httpd.registration = True
else: else:

View File

@ -118,8 +118,13 @@ parser.add_argument('--postsPerSource',
help='Maximum newswire posts per feed or account') help='Maximum newswire posts per feed or account')
parser.add_argument('--maxFeedSize', parser.add_argument('--maxFeedSize',
dest='maxNewswireFeedSizeKb', type=int, dest='maxNewswireFeedSizeKb', type=int,
default=2048, default=10240,
help='Maximum newswire rss/atom feed size in K') help='Maximum newswire rss/atom feed size in K')
parser.add_argument('--maxFeedItemSizeKb',
dest='maxFeedItemSizeKb', type=int,
default=2048,
help='Maximum size of an individual rss/atom ' +
'feed item in K')
parser.add_argument('--maxMirroredArticles', parser.add_argument('--maxMirroredArticles',
dest='maxMirroredArticles', type=int, dest='maxMirroredArticles', type=int,
default=100, default=100,
@ -2010,6 +2015,11 @@ maxFollowers = \
if maxFollowers is not None: if maxFollowers is not None:
args.maxFollowers = int(maxFollowers) args.maxFollowers = int(maxFollowers)
maxFeedItemSizeKb = \
getConfigParam(baseDir, 'maxFeedItemSizeKb')
if maxFeedItemSizeKb is not None:
args.maxFeedItemSizeKb = int(maxFeedItemSizeKb)
allowNewsFollowers = \ allowNewsFollowers = \
getConfigParam(baseDir, 'allowNewsFollowers') getConfigParam(baseDir, 'allowNewsFollowers')
if allowNewsFollowers is not None: if allowNewsFollowers is not None:
@ -2053,7 +2063,8 @@ if setTheme(baseDir, themeName, domain):
print('Theme set to ' + themeName) print('Theme set to ' + themeName)
if __name__ == "__main__": if __name__ == "__main__":
runDaemon(args.publishButtonAtTop, runDaemon(args.maxFeedItemSizeKb,
args.publishButtonAtTop,
args.rssIconAtTop, args.rssIconAtTop,
args.iconsAsButtons, args.iconsAsButtons,
args.fullWidthTimelineButtonHeader, args.fullWidthTimelineButtonHeader,

View File

@ -31,6 +31,7 @@ from utils import saveJson
from utils import getStatusNumber from utils import getStatusNumber
from utils import clearFromPostCaches from utils import clearFromPostCaches
from inbox import storeHashTags from inbox import storeHashTags
from session import createSession
def updateFeedsOutboxIndex(baseDir: str, domain: str, postId: str) -> None: def updateFeedsOutboxIndex(baseDir: str, domain: str, postId: str) -> None:
@ -471,6 +472,9 @@ def convertRSStoActivityPub(baseDir: str, httpPrefix: str,
maxMirroredArticles: int) -> None: maxMirroredArticles: int) -> None:
"""Converts rss items in a newswire into posts """Converts rss items in a newswire into posts
""" """
if not newswire:
return
basePath = baseDir + '/accounts/news@' + domain + '/outbox' basePath = baseDir + '/accounts/news@' + domain + '/outbox'
if not os.path.isdir(basePath): if not os.path.isdir(basePath):
os.mkdir(basePath) os.mkdir(basePath)
@ -669,6 +673,9 @@ def mergeWithPreviousNewswire(oldNewswire: {}, newNewswire: {}) -> None:
"""Preserve any votes or generated activitypub post filename """Preserve any votes or generated activitypub post filename
as rss feeds are updated as rss feeds are updated
""" """
if not oldNewswire:
return
for published, fields in oldNewswire.items(): for published, fields in oldNewswire.items():
if not newNewswire.get(published): if not newNewswire.get(published):
continue continue
@ -689,8 +696,13 @@ def runNewswireDaemon(baseDir: str, httpd,
# has the session been created yet? # has the session been created yet?
if not httpd.session: if not httpd.session:
print('Newswire daemon waiting for session') print('Newswire daemon waiting for session')
time.sleep(60) httpd.session = createSession(httpd.proxyType)
continue if not httpd.session:
print('Newswire daemon has no session')
time.sleep(60)
continue
else:
print('Newswire daemon session established')
# try to update the feeds # try to update the feeds
newNewswire = None newNewswire = None
@ -699,7 +711,8 @@ def runNewswireDaemon(baseDir: str, httpd,
getDictFromNewswire(httpd.session, baseDir, domain, getDictFromNewswire(httpd.session, baseDir, domain,
httpd.maxNewswirePostsPerSource, httpd.maxNewswirePostsPerSource,
httpd.maxNewswireFeedSizeKb, httpd.maxNewswireFeedSizeKb,
httpd.maxTags) httpd.maxTags,
httpd.maxFeedItemSizeKb)
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

@ -126,7 +126,8 @@ def addNewswireDictEntry(baseDir: str, domain: str,
def xml2StrToDict(baseDir: str, domain: str, xmlStr: str, def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
moderated: bool, mirrored: bool, moderated: bool, mirrored: bool,
maxPostsPerSource: int) -> {}: maxPostsPerSource: int,
maxFeedItemSizeKb: 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:
@ -134,7 +135,11 @@ def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
result = {} result = {}
rssItems = xmlStr.split('<item>') rssItems = xmlStr.split('<item>')
postCtr = 0 postCtr = 0
maxBytes = maxFeedItemSizeKb * 1024
for rssItem in rssItems: for rssItem in rssItems:
if len(rssItem) > maxBytes:
print('WARN: rss feed item is too big')
continue
if '<title>' not in rssItem: if '<title>' not in rssItem:
continue continue
if '</title>' not in rssItem: if '</title>' not in rssItem:
@ -205,7 +210,8 @@ def xml2StrToDict(baseDir: str, domain: str, xmlStr: str,
def atomFeedToDict(baseDir: str, domain: str, xmlStr: str, def atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
moderated: bool, mirrored: bool, moderated: bool, mirrored: bool,
maxPostsPerSource: int) -> {}: maxPostsPerSource: int,
maxFeedItemSizeKb: 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:
@ -213,7 +219,11 @@ def atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
result = {} result = {}
rssItems = xmlStr.split('<entry>') rssItems = xmlStr.split('<entry>')
postCtr = 0 postCtr = 0
maxBytes = maxFeedItemSizeKb * 1024
for rssItem in rssItems: for rssItem in rssItems:
if len(rssItem) > maxBytes:
print('WARN: atom feed item is too big')
continue
if '<title>' not in rssItem: if '<title>' not in rssItem:
continue continue
if '</title>' not in rssItem: if '</title>' not in rssItem:
@ -283,21 +293,25 @@ def atomFeedToDict(baseDir: str, domain: str, xmlStr: str,
def xmlStrToDict(baseDir: str, domain: str, xmlStr: str, def xmlStrToDict(baseDir: str, domain: str, xmlStr: str,
moderated: bool, mirrored: bool, moderated: bool, mirrored: bool,
maxPostsPerSource: int) -> {}: maxPostsPerSource: int,
maxFeedItemSizeKb: 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(baseDir, domain, return xml2StrToDict(baseDir, domain,
xmlStr, moderated, mirrored, maxPostsPerSource) xmlStr, moderated, mirrored,
maxPostsPerSource, maxFeedItemSizeKb)
elif 'xmlns="http://www.w3.org/2005/Atom"' in xmlStr: elif 'xmlns="http://www.w3.org/2005/Atom"' in xmlStr:
return atomFeedToDict(baseDir, domain, return atomFeedToDict(baseDir, domain,
xmlStr, moderated, mirrored, maxPostsPerSource) xmlStr, moderated, mirrored,
maxPostsPerSource, maxFeedItemSizeKb)
return {} return {}
def getRSS(baseDir: str, domain: str, session, url: str, def getRSS(baseDir: str, domain: str, session, url: str,
moderated: bool, mirrored: bool, moderated: bool, mirrored: bool,
maxPostsPerSource: int, maxFeedSizeKb: int) -> {}: maxPostsPerSource: int, maxFeedSizeKb: int,
maxFeedItemSizeKb: 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):
@ -325,7 +339,8 @@ def getRSS(baseDir: str, domain: str, session, url: str,
not containsInvalidChars(result.text): not containsInvalidChars(result.text):
return xmlStrToDict(baseDir, domain, result.text, return xmlStrToDict(baseDir, domain, result.text,
moderated, mirrored, moderated, mirrored,
maxPostsPerSource) maxPostsPerSource,
maxFeedItemSizeKb)
else: else:
print('WARN: feed is too large: ' + url) print('WARN: feed is too large: ' + url)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
@ -354,6 +369,8 @@ def getRSSfromDict(baseDir: str, newswire: {},
rssStr = rss2Header(httpPrefix, rssStr = rss2Header(httpPrefix,
None, domainFull, None, domainFull,
'Newswire', translate) 'Newswire', translate)
if not newswire:
return ''
for published, fields in newswire.items(): for published, fields in newswire.items():
if '+00:00' in published: if '+00:00' in published:
published = published.replace('+00:00', 'Z').strip() published = published.replace('+00:00', 'Z').strip()
@ -547,7 +564,7 @@ def addBlogsToNewswire(baseDir: str, domain: str, newswire: {},
def getDictFromNewswire(session, baseDir: str, domain: str, def getDictFromNewswire(session, baseDir: str, domain: str,
maxPostsPerSource: int, maxFeedSizeKb: int, maxPostsPerSource: int, maxFeedSizeKb: int,
maxTags: int) -> {}: maxTags: int, maxFeedItemSizeKb: 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'
@ -586,9 +603,11 @@ def getDictFromNewswire(session, baseDir: str, domain: str,
itemsList = getRSS(baseDir, domain, session, url, itemsList = getRSS(baseDir, domain, session, url,
moderated, mirrored, moderated, mirrored,
maxPostsPerSource, maxFeedSizeKb) maxPostsPerSource, maxFeedSizeKb,
for dateStr, item in itemsList.items(): maxFeedItemSizeKb)
result[dateStr] = item if itemsList:
for dateStr, item in itemsList.items():
result[dateStr] = item
# add blogs from each user account # add blogs from each user account
addBlogsToNewswire(baseDir, domain, result, addBlogsToNewswire(baseDir, domain, result,

View File

@ -291,7 +291,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, True, False, False, True, 10, False, runDaemon(2048, False, True, False, False, True, 10, False,
0, 100, 1024, 5, False, 0, 100, 1024, 5, False,
0, False, 1, False, False, False, 0, False, 1, False, False, False,
5, True, True, 'en', __version__, 5, True, True, 'en', __version__,
@ -356,7 +356,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, True, False, False, True, 10, False, runDaemon(2048, False, True, False, False, True, 10, False,
0, 100, 1024, 5, False, 0, 0, 100, 1024, 5, False, 0,
False, 1, False, False, False, False, 1, False, False, False,
5, True, True, 'en', __version__, 5, True, True, 'en', __version__,
@ -395,7 +395,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, True, False, False, True, 10, False, runDaemon(2048, False, True, False, False, True, 10, False,
0, 100, 1024, 5, False, 0, 0, 100, 1024, 5, False, 0,
False, 1, False, False, False, False, 1, False, False, False,
5, True, True, 'en', __version__, 5, True, True, 'en', __version__,

View File

@ -3557,9 +3557,10 @@ def htmlProfile(rssIconAtTop: bool,
# If this is the news account then show a different banner # If this is the news account then show a different banner
if isSystemAccount(nickname): if isSystemAccount(nickname):
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
profileHeaderStr = \ profileHeaderStr = \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
'src="/users/news/banner.png" />\n' 'src="/users/' + nickname + '/' + bannerFile + '" />\n'
if loginButton: if loginButton:
profileHeaderStr += '<center>' + loginButton + '</center>\n' profileHeaderStr += '<center>' + loginButton + '</center>\n'
@ -5604,20 +5605,24 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
editImageClass = '' editImageClass = ''
if showHeaderImage: if showHeaderImage:
leftColumnImageFilename = \ leftImageFile, leftColumnImageFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + \ getLeftImageFile(baseDir, nickname, domain)
'/left_col_image.png'
if not os.path.isfile(leftColumnImageFilename): if not os.path.isfile(leftColumnImageFilename):
theme = getConfigParam(baseDir, 'theme').lower() theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default': if theme == 'default':
theme = '' theme = ''
else: else:
theme = '_' + theme theme = '_' + theme
themeLeftColumnImageFilename = \ themeLeftImageFile, themeLeftColumnImageFilename = \
baseDir + '/img/left_col_image' + theme + '.png' getImageFile(baseDir, 'left_col_image', baseDir + '/img',
nickname, domain)
if os.path.isfile(themeLeftColumnImageFilename): if os.path.isfile(themeLeftColumnImageFilename):
leftColumnImageFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + themeLeftImageFile
copyfile(themeLeftColumnImageFilename, copyfile(themeLeftColumnImageFilename,
leftColumnImageFilename) leftColumnImageFilename)
leftImageFile = themeLeftImageFile
# show the image at the top of the column # show the image at the top of the column
editImageClass = 'leftColEdit' editImageClass = 'leftColEdit'
@ -5627,7 +5632,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
'\n <center>\n' + \ '\n <center>\n' + \
' <img class="leftColImg" ' + \ ' <img class="leftColImg" ' + \
'loading="lazy" src="/users/' + \ 'loading="lazy" src="/users/' + \
nickname + '/left_col_image.png" />\n' + \ nickname + '/' + leftImageFile + '" />\n' + \
' </center>\n' ' </center>\n'
if showBackButton: if showBackButton:
@ -5858,20 +5863,24 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
# show a column header image, eg. title of the theme or newswire banner # show a column header image, eg. title of the theme or newswire banner
editImageClass = '' editImageClass = ''
if showHeaderImage: if showHeaderImage:
rightColumnImageFilename = \ rightImageFile, rightColumnImageFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + \ getRightImageFile(baseDir, nickname, domain)
'/right_col_image.png'
if not os.path.isfile(rightColumnImageFilename): if not os.path.isfile(rightColumnImageFilename):
theme = getConfigParam(baseDir, 'theme').lower() theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default': if theme == 'default':
theme = '' theme = ''
else: else:
theme = '_' + theme theme = '_' + theme
themeRightColumnImageFilename = \ themeRightImageFile, themeRightColumnImageFilename = \
baseDir + '/img/right_col_image' + theme + '.png' getImageFile(baseDir, 'right_col_image', baseDir + '/img',
nickname, domain)
if os.path.isfile(themeRightColumnImageFilename): if os.path.isfile(themeRightColumnImageFilename):
rightColumnImageFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + themeRightImageFile
copyfile(themeRightColumnImageFilename, copyfile(themeRightColumnImageFilename,
rightColumnImageFilename) rightColumnImageFilename)
rightImageFile = themeRightImageFile
# show the image at the top of the column # show the image at the top of the column
editImageClass = 'rightColEdit' editImageClass = 'rightColEdit'
@ -5881,7 +5890,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
'\n <center>\n' + \ '\n <center>\n' + \
' <img class="rightColImg" ' + \ ' <img class="rightColImg" ' + \
'loading="lazy" src="/users/' + \ 'loading="lazy" src="/users/' + \
nickname + '/right_col_image.png" />\n' + \ nickname + '/' + rightImageFile + '" />\n' + \
' </center>\n' ' </center>\n'
if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage: if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage:
@ -6000,11 +6009,16 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
else: else:
editor = isEditor(baseDir, nickname) editor = isEditor(baseDir, nickname)
domain = domainFull
if ':' in domain:
domain = domain.split(':')[0]
htmlStr = htmlHeader(cssFilename, profileStyle) htmlStr = htmlHeader(cssFilename, profileStyle)
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
htmlStr += \ htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
'src="/users/news/banner.png" /></a>\n' 'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
htmlStr += '<center>' + \ htmlStr += '<center>' + \
headerButtonsFrontScreen(translate, nickname, headerButtonsFrontScreen(translate, nickname,
@ -6063,10 +6077,12 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
showPublishButton = editor showPublishButton = editor
htmlStr = htmlHeader(cssFilename, profileStyle) htmlStr = htmlHeader(cssFilename, profileStyle)
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
htmlStr += \ htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
'src="/users/news/banner.png" /></a>\n' 'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
htmlStr += '<center>' + \ htmlStr += '<center>' + \
headerButtonsFrontScreen(translate, nickname, headerButtonsFrontScreen(translate, nickname,
@ -6084,37 +6100,48 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
return htmlStr return htmlStr
def getBannerFile(baseDir: str, nickname: str, domain: str) -> (str, str): def getImageFile(baseDir: str, name: str, directory: str,
nickname: str, domain: str) -> (str, str):
""" """
returns the banner filename returns the filenames for an image with the given name
""" """
bannerExtensions = ('png', 'jpg', 'jpeg', 'gif', 'avif', 'webp') bannerExtensions = ('png', 'jpg', 'jpeg', 'gif', 'avif', 'webp')
bannerFile = '' bannerFile = ''
bannerFilename = '' bannerFilename = ''
for ext in bannerExtensions: for ext in bannerExtensions:
bannerFile = 'banner.' + ext bannerFile = name + '.' + ext
bannerFilename = baseDir + '/accounts/' + \ bannerFilename = directory + '/' + bannerFile
nickname + '@' + domain + '/' + bannerFile
if os.path.isfile(bannerFilename): if os.path.isfile(bannerFilename):
break break
return bannerFile, bannerFilename return bannerFile, bannerFilename
def getBannerFile(baseDir: str,
nickname: str, domain: str) -> (str, str):
return getImageFile(baseDir, 'banner',
baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain)
def getSearchBannerFile(baseDir: str, def getSearchBannerFile(baseDir: str,
nickname: str, domain: str) -> (str, str): nickname: str, domain: str) -> (str, str):
""" return getImageFile(baseDir, 'search_banner',
returns the search banner filename baseDir + '/accounts/' + nickname + '@' + domain,
""" nickname, domain)
bannerExtensions = ('png', 'jpg', 'jpeg', 'gif', 'avif', 'webp')
bannerFile = ''
bannerFilename = '' def getLeftImageFile(baseDir: str,
for ext in bannerExtensions: nickname: str, domain: str) -> (str, str):
bannerFile = 'search_banner.' + ext return getImageFile(baseDir, 'left_col_image',
bannerFilename = baseDir + '/accounts/' + \ baseDir + '/accounts/' + nickname + '@' + domain,
nickname + '@' + domain + '/' + bannerFile nickname, domain)
if os.path.isfile(bannerFilename):
break
return bannerFile, bannerFilename def getRightImageFile(baseDir: str,
nickname: str, domain: str) -> (str, str):
return getImageFile(baseDir, 'right_col_image',
baseDir + '/accounts/' + nickname + '@' + domain,
nickname, domain)
def headerButtonsFrontScreen(translate: {}, def headerButtonsFrontScreen(translate: {},
@ -8698,17 +8725,17 @@ def htmlSearch(cssCache: {}, translate: {},
theme = '' theme = ''
else: else:
theme = '_' + theme theme = '_' + theme
bannerExtensions = ('png', 'jpg', 'jpeg', 'gif', 'avif', 'webp') themeSearchImageFile, themeSearchBannerFilename = \
for ext in bannerExtensions: getImageFile(baseDir, 'search_banner', baseDir + '/img',
searchBannerFile = 'search_banner.' + ext searchNickname, domain)
if os.path.isfile(themeSearchBannerFilename):
searchBannerFilename = \ searchBannerFilename = \
baseDir + '/accounts/' + \ baseDir + '/accounts/' + \
searchNickname + '@' + domain + '/' + searchBannerFile searchNickname + '@' + domain + '/' + themeSearchImageFile
themeSearchBannerFilename = \ copyfile(themeSearchBannerFilename,
baseDir + '/img/search_banner' + theme + '.' + ext searchBannerFilename)
if os.path.isfile(themeSearchBannerFilename): searchBannerFile = themeSearchImageFile
copyfile(themeSearchBannerFilename, searchBannerFilename)
break
if os.path.isfile(searchBannerFilename): if os.path.isfile(searchBannerFilename):
usersPath = '/users/' + searchNickname usersPath = '/users/' + searchNickname
followStr += \ followStr += \