From cc2bfc114574bf8fbf54bf5bedf591f06b6933f1 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 09:45:18 +0000 Subject: [PATCH 01/21] Separate module for login screen --- daemon.py | 4 +- webapp.py | 156 -------------------------------------------- webapp_login.py | 170 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 158 deletions(-) create mode 100644 webapp_login.py diff --git a/daemon.py b/daemon.py index 77642227b..d52c6b29b 100644 --- a/daemon.py +++ b/daemon.py @@ -135,9 +135,9 @@ from webapp_timeline import htmlInboxNews from webapp_timeline import htmlOutbox from webapp_timeline import htmlModeration from webapp_create_post import htmlNewPost -from webapp import htmlLogin +from webapp_login import htmlLogin +from webapp_login import htmlGetLoginCredentials from webapp import htmlSuspended -from webapp import htmlGetLoginCredentials from webapp import htmlFollowConfirm from webapp import htmlUnfollowConfirm from webapp import htmlEditNewsPost diff --git a/webapp.py b/webapp.py index f51ff06ff..ee0f02002 100644 --- a/webapp.py +++ b/webapp.py @@ -6,14 +6,12 @@ __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" -import time import os from shutil import copyfile from utils import getCSS from utils import getNicknameFromActor from utils import getDomainFromActor from utils import locatePost -from utils import noOfAccounts from utils import loadJson from utils import getConfigParam from posts import isEditor @@ -195,160 +193,6 @@ def htmlEditNewsPost(cssCache: {}, translate: {}, baseDir: str, path: str, return editNewsPostForm -def htmlGetLoginCredentials(loginParams: str, - lastLoginTime: int) -> (str, str, bool): - """Receives login credentials via HTTPServer POST - """ - if not loginParams.startswith('username='): - return None, None, None - # minimum time between login attempts - currTime = int(time.time()) - if currTime < lastLoginTime+10: - return None, None, None - if '&' not in loginParams: - return None, None, None - loginArgs = loginParams.split('&') - nickname = None - password = None - register = False - for arg in loginArgs: - if '=' in arg: - if arg.split('=', 1)[0] == 'username': - nickname = arg.split('=', 1)[1] - elif arg.split('=', 1)[0] == 'password': - password = arg.split('=', 1)[1] - elif arg.split('=', 1)[0] == 'register': - register = True - return nickname, password, register - - -def htmlLogin(cssCache: {}, translate: {}, - baseDir: str, autocomplete=True) -> str: - """Shows the login screen - """ - accounts = noOfAccounts(baseDir) - - loginImage = 'login.png' - loginImageFilename = None - if os.path.isfile(baseDir + '/accounts/' + loginImage): - loginImageFilename = baseDir + '/accounts/' + loginImage - elif os.path.isfile(baseDir + '/accounts/login.jpg'): - loginImage = 'login.jpg' - loginImageFilename = baseDir + '/accounts/' + loginImage - elif os.path.isfile(baseDir + '/accounts/login.jpeg'): - loginImage = 'login.jpeg' - loginImageFilename = baseDir + '/accounts/' + loginImage - elif os.path.isfile(baseDir + '/accounts/login.gif'): - loginImage = 'login.gif' - loginImageFilename = baseDir + '/accounts/' + loginImage - elif os.path.isfile(baseDir + '/accounts/login.webp'): - loginImage = 'login.webp' - loginImageFilename = baseDir + '/accounts/' + loginImage - elif os.path.isfile(baseDir + '/accounts/login.avif'): - loginImage = 'login.avif' - loginImageFilename = baseDir + '/accounts/' + loginImage - - if not loginImageFilename: - loginImageFilename = baseDir + '/accounts/' + loginImage - copyfile(baseDir + '/img/login.png', loginImageFilename) - - if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'): - if not os.path.isfile(baseDir + '/accounts/login-background.jpg'): - copyfile(baseDir + '/accounts/login-background-custom.jpg', - baseDir + '/accounts/login-background.jpg') - - if accounts > 0: - loginText = \ - '

' + \ - translate['Welcome. Please enter your login details below.'] + \ - '

' - else: - loginText = \ - '

' + \ - translate['Please enter some credentials'] + '

' - loginText += \ - '

' + \ - translate['You will become the admin of this site.'] + \ - '

' - if os.path.isfile(baseDir + '/accounts/login.txt'): - # custom login message - with open(baseDir + '/accounts/login.txt', 'r') as file: - loginText = '

' + file.read() + '

' - - cssFilename = baseDir + '/epicyon-login.css' - if os.path.isfile(baseDir + '/login.css'): - cssFilename = baseDir + '/login.css' - - loginCSS = getCSS(baseDir, cssFilename, cssCache) - if not loginCSS: - print('ERROR: login css file missing ' + cssFilename) - return None - - # show the register button - registerButtonStr = '' - if getConfigParam(baseDir, 'registration') == 'open': - if int(getConfigParam(baseDir, 'registrationsRemaining')) > 0: - if accounts > 0: - idx = 'Welcome. Please login or register a new account.' - loginText = \ - '

' + \ - translate[idx] + \ - '

' - registerButtonStr = \ - '' - - TOSstr = \ - '

' + \ - translate['Terms of Service'] + '

' - TOSstr += \ - '

' + \ - translate['About this Instance'] + '

' - - loginButtonStr = '' - if accounts > 0: - loginButtonStr = \ - '' - - autocompleteStr = '' - if not autocomplete: - autocompleteStr = 'autocomplete="off" value=""' - - loginForm = htmlHeader(cssFilename, loginCSS) - loginForm += '
\n' - loginForm += '
\n' - loginForm += '
\n' - loginForm += \ - ' login image\n' - loginForm += loginText + TOSstr + '\n' - loginForm += '
\n' - loginForm += '\n' - loginForm += '
\n' - loginForm += ' \n' - loginForm += \ - ' \n' - loginForm += '\n' - loginForm += ' \n' - loginForm += \ - ' \n' - loginForm += loginButtonStr + registerButtonStr + '\n' - loginForm += '
\n' - loginForm += '
\n' - loginForm += \ - '' + \ - '' + \
-        translate['Get the source code'] + '\n' - loginForm += htmlFooter() - return loginForm - - def htmlTermsOfService(cssCache: {}, baseDir: str, httpPrefix: str, domainFull: str) -> str: """Show the terms of service screen diff --git a/webapp_login.py b/webapp_login.py new file mode 100644 index 000000000..6ee41fc5f --- /dev/null +++ b/webapp_login.py @@ -0,0 +1,170 @@ +__filename__ = "webapp_login.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +import time +from shutil import copyfile +from utils import getConfigParam +from utils import noOfAccounts +from utils import getCSS +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter + + +def htmlGetLoginCredentials(loginParams: str, + lastLoginTime: int) -> (str, str, bool): + """Receives login credentials via HTTPServer POST + """ + if not loginParams.startswith('username='): + return None, None, None + # minimum time between login attempts + currTime = int(time.time()) + if currTime < lastLoginTime+10: + return None, None, None + if '&' not in loginParams: + return None, None, None + loginArgs = loginParams.split('&') + nickname = None + password = None + register = False + for arg in loginArgs: + if '=' in arg: + if arg.split('=', 1)[0] == 'username': + nickname = arg.split('=', 1)[1] + elif arg.split('=', 1)[0] == 'password': + password = arg.split('=', 1)[1] + elif arg.split('=', 1)[0] == 'register': + register = True + return nickname, password, register + + +def htmlLogin(cssCache: {}, translate: {}, + baseDir: str, autocomplete=True) -> str: + """Shows the login screen + """ + accounts = noOfAccounts(baseDir) + + loginImage = 'login.png' + loginImageFilename = None + if os.path.isfile(baseDir + '/accounts/' + loginImage): + loginImageFilename = baseDir + '/accounts/' + loginImage + elif os.path.isfile(baseDir + '/accounts/login.jpg'): + loginImage = 'login.jpg' + loginImageFilename = baseDir + '/accounts/' + loginImage + elif os.path.isfile(baseDir + '/accounts/login.jpeg'): + loginImage = 'login.jpeg' + loginImageFilename = baseDir + '/accounts/' + loginImage + elif os.path.isfile(baseDir + '/accounts/login.gif'): + loginImage = 'login.gif' + loginImageFilename = baseDir + '/accounts/' + loginImage + elif os.path.isfile(baseDir + '/accounts/login.webp'): + loginImage = 'login.webp' + loginImageFilename = baseDir + '/accounts/' + loginImage + elif os.path.isfile(baseDir + '/accounts/login.avif'): + loginImage = 'login.avif' + loginImageFilename = baseDir + '/accounts/' + loginImage + + if not loginImageFilename: + loginImageFilename = baseDir + '/accounts/' + loginImage + copyfile(baseDir + '/img/login.png', loginImageFilename) + + if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'): + if not os.path.isfile(baseDir + '/accounts/login-background.jpg'): + copyfile(baseDir + '/accounts/login-background-custom.jpg', + baseDir + '/accounts/login-background.jpg') + + if accounts > 0: + loginText = \ + '

' + \ + translate['Welcome. Please enter your login details below.'] + \ + '

' + else: + loginText = \ + '

' + \ + translate['Please enter some credentials'] + '

' + loginText += \ + '

' + \ + translate['You will become the admin of this site.'] + \ + '

' + if os.path.isfile(baseDir + '/accounts/login.txt'): + # custom login message + with open(baseDir + '/accounts/login.txt', 'r') as file: + loginText = '

' + file.read() + '

' + + cssFilename = baseDir + '/epicyon-login.css' + if os.path.isfile(baseDir + '/login.css'): + cssFilename = baseDir + '/login.css' + + loginCSS = getCSS(baseDir, cssFilename, cssCache) + if not loginCSS: + print('ERROR: login css file missing ' + cssFilename) + return None + + # show the register button + registerButtonStr = '' + if getConfigParam(baseDir, 'registration') == 'open': + if int(getConfigParam(baseDir, 'registrationsRemaining')) > 0: + if accounts > 0: + idx = 'Welcome. Please login or register a new account.' + loginText = \ + '

' + \ + translate[idx] + \ + '

' + registerButtonStr = \ + '' + + TOSstr = \ + '

' + \ + translate['Terms of Service'] + '

' + TOSstr += \ + '

' + \ + translate['About this Instance'] + '

' + + loginButtonStr = '' + if accounts > 0: + loginButtonStr = \ + '' + + autocompleteStr = '' + if not autocomplete: + autocompleteStr = 'autocomplete="off" value=""' + + loginForm = htmlHeader(cssFilename, loginCSS) + loginForm += '
\n' + loginForm += '
\n' + loginForm += '
\n' + loginForm += \ + ' login image\n' + loginForm += loginText + TOSstr + '\n' + loginForm += '
\n' + loginForm += '\n' + loginForm += '
\n' + loginForm += ' \n' + loginForm += \ + ' \n' + loginForm += '\n' + loginForm += ' \n' + loginForm += \ + ' \n' + loginForm += loginButtonStr + registerButtonStr + '\n' + loginForm += '
\n' + loginForm += '
\n' + loginForm += \ + '' + \ + '' + \
+        translate['Get the source code'] + '\n' + loginForm += htmlFooter() + return loginForm From 36621052592332431db167ec591b4cbcd23dd70a Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 09:55:06 +0000 Subject: [PATCH 02/21] Move news function to right column module --- daemon.py | 2 +- webapp.py | 79 ----------------------------------------- webapp_column_right.py | 80 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/daemon.py b/daemon.py index d52c6b29b..8525442b4 100644 --- a/daemon.py +++ b/daemon.py @@ -140,7 +140,6 @@ from webapp_login import htmlGetLoginCredentials from webapp import htmlSuspended from webapp import htmlFollowConfirm from webapp import htmlUnfollowConfirm -from webapp import htmlEditNewsPost from webapp import htmlTermsOfService from webapp import htmlModerationInfo from webapp import htmlHashtagBlocked @@ -154,6 +153,7 @@ from webapp_column_left import htmlEditLinks from webapp_column_right import htmlNewswireMobile from webapp_column_right import htmlEditNewswire from webapp_column_right import htmlCitations +from webapp_column_right import htmlEditNewsPost from webapp_search import htmlSkillsSearch from webapp_search import htmlHistorySearch from webapp_search import htmlHashtagSearch diff --git a/webapp.py b/webapp.py index ee0f02002..b395b0be6 100644 --- a/webapp.py +++ b/webapp.py @@ -14,7 +14,6 @@ from utils import getDomainFromActor from utils import locatePost from utils import loadJson from utils import getConfigParam -from posts import isEditor from shares import getValidSharedItemID from webapp_utils import getAltPath from webapp_utils import getIconsDir @@ -115,84 +114,6 @@ def htmlModerationInfo(cssCache: {}, translate: {}, return infoForm -def htmlEditNewsPost(cssCache: {}, translate: {}, baseDir: str, path: str, - domain: str, port: int, - httpPrefix: str, postUrl: str) -> str: - """Edits a news post - """ - if '/users/' not in path: - return '' - pathOriginal = path - - nickname = getNicknameFromActor(path) - if not nickname: - return '' - - # is the user an editor? - if not isEditor(baseDir, nickname): - return '' - - postUrl = postUrl.replace('/', '#') - postFilename = locatePost(baseDir, nickname, domain, postUrl) - if not postFilename: - return '' - postJsonObject = loadJson(postFilename) - if not postJsonObject: - return '' - - cssFilename = baseDir + '/epicyon-links.css' - if os.path.isfile(baseDir + '/links.css'): - cssFilename = baseDir + '/links.css' - - editCSS = getCSS(baseDir, cssFilename, cssCache) - if editCSS: - if httpPrefix != 'https': - editCSS = \ - editCSS.replace('https://', httpPrefix + '://') - - editNewsPostForm = htmlHeader(cssFilename, editCSS) - editNewsPostForm += \ - '
\n' - editNewsPostForm += \ - '
\n' - editNewsPostForm += \ - '

' + translate['Edit News Post'] + '

' - editNewsPostForm += \ - '
\n' - editNewsPostForm += \ - ' ' + \ - '\n' - editNewsPostForm += \ - ' \n' - editNewsPostForm += \ - '
\n' - - editNewsPostForm += \ - '
' - - editNewsPostForm += \ - ' \n' - - newsPostTitle = postJsonObject['object']['summary'] - editNewsPostForm += \ - '
\n' - - newsPostContent = postJsonObject['object']['content'] - editNewsPostForm += \ - ' ' - - editNewsPostForm += \ - '
' - - editNewsPostForm += htmlFooter() - return editNewsPostForm - - def htmlTermsOfService(cssCache: {}, baseDir: str, httpPrefix: str, domainFull: str) -> str: """Show the terms of service screen diff --git a/webapp_column_right.py b/webapp_column_right.py index c8b72a7b4..0e3cff85a 100644 --- a/webapp_column_right.py +++ b/webapp_column_right.py @@ -10,6 +10,8 @@ import os from datetime import datetime from shutil import copyfile from content import removeLongWords +from utils import locatePost +from utils import loadJson from utils import getCSS from utils import getConfigParam from utils import votesOnNewswireItem @@ -565,3 +567,81 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str, editNewswireForm += htmlFooter() return editNewswireForm + + +def htmlEditNewsPost(cssCache: {}, translate: {}, baseDir: str, path: str, + domain: str, port: int, + httpPrefix: str, postUrl: str) -> str: + """Edits a news post on the news/features timeline + """ + if '/users/' not in path: + return '' + pathOriginal = path + + nickname = getNicknameFromActor(path) + if not nickname: + return '' + + # is the user an editor? + if not isEditor(baseDir, nickname): + return '' + + postUrl = postUrl.replace('/', '#') + postFilename = locatePost(baseDir, nickname, domain, postUrl) + if not postFilename: + return '' + postJsonObject = loadJson(postFilename) + if not postJsonObject: + return '' + + cssFilename = baseDir + '/epicyon-links.css' + if os.path.isfile(baseDir + '/links.css'): + cssFilename = baseDir + '/links.css' + + editCSS = getCSS(baseDir, cssFilename, cssCache) + if editCSS: + if httpPrefix != 'https': + editCSS = \ + editCSS.replace('https://', httpPrefix + '://') + + editNewsPostForm = htmlHeader(cssFilename, editCSS) + editNewsPostForm += \ + '\n' + editNewsPostForm += \ + '
\n' + editNewsPostForm += \ + '

' + translate['Edit News Post'] + '

' + editNewsPostForm += \ + '
\n' + editNewsPostForm += \ + ' ' + \ + '\n' + editNewsPostForm += \ + ' \n' + editNewsPostForm += \ + '
\n' + + editNewsPostForm += \ + '
' + + editNewsPostForm += \ + ' \n' + + newsPostTitle = postJsonObject['object']['summary'] + editNewsPostForm += \ + '
\n' + + newsPostContent = postJsonObject['object']['content'] + editNewsPostForm += \ + ' ' + + editNewsPostForm += \ + '
' + + editNewsPostForm += htmlFooter() + return editNewsPostForm From 087b7b65c6a55abfe1414acc1a0ffdbf5e301a95 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 10:25:21 +0000 Subject: [PATCH 03/21] More webapp modules --- daemon.py | 10 +-- webapp.py | 177 ------------------------------------------- webapp_about.py | 62 +++++++++++++++ webapp_moderation.py | 111 +++++++++++++++++++++++++++ webapp_suspended.py | 31 ++++++++ webapp_timeline.py | 31 -------- webapp_tos.py | 57 ++++++++++++++ 7 files changed, 266 insertions(+), 213 deletions(-) create mode 100644 webapp_about.py create mode 100644 webapp_moderation.py create mode 100644 webapp_suspended.py create mode 100644 webapp_tos.py diff --git a/daemon.py b/daemon.py index 8525442b4..9aaf85cde 100644 --- a/daemon.py +++ b/daemon.py @@ -117,9 +117,9 @@ from webapp_utils import setBlogAddress from webapp_utils import getBlogAddress from webapp_calendar import htmlCalendarDeleteConfirm from webapp_calendar import htmlCalendar +from webapp_about import htmlAbout from webapp import htmlFollowingList from webapp import htmlDeletePost -from webapp import htmlAbout from webapp import htmlRemoveSharedItem from webapp import htmlUnblockConfirm from webapp_person_options import htmlPersonOptions @@ -133,15 +133,15 @@ from webapp_timeline import htmlInboxMedia from webapp_timeline import htmlInboxBlogs from webapp_timeline import htmlInboxNews from webapp_timeline import htmlOutbox -from webapp_timeline import htmlModeration +from webapp_moderation import htmlModeration +from webapp_moderation import htmlModerationInfo from webapp_create_post import htmlNewPost from webapp_login import htmlLogin from webapp_login import htmlGetLoginCredentials -from webapp import htmlSuspended +from webapp_suspended import htmlSuspended +from webapp_tos import htmlTermsOfService from webapp import htmlFollowConfirm from webapp import htmlUnfollowConfirm -from webapp import htmlTermsOfService -from webapp import htmlModerationInfo from webapp import htmlHashtagBlocked from webapp_post import htmlPostReplies from webapp_post import htmlIndividualPost diff --git a/webapp.py b/webapp.py index b395b0be6..02061882b 100644 --- a/webapp.py +++ b/webapp.py @@ -13,7 +13,6 @@ from utils import getNicknameFromActor from utils import getDomainFromActor from utils import locatePost from utils import loadJson -from utils import getConfigParam from shares import getValidSharedItemID from webapp_utils import getAltPath from webapp_utils import getIconsDir @@ -48,163 +47,6 @@ def htmlFollowingList(cssCache: {}, baseDir: str, return '' -def htmlModerationInfo(cssCache: {}, translate: {}, - baseDir: str, httpPrefix: str) -> str: - msgStr1 = \ - 'These are globally blocked for all accounts on this instance' - msgStr2 = \ - 'Any blocks or suspensions made by moderators will be shown here.' - infoForm = '' - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - infoCSS = getCSS(baseDir, cssFilename, cssCache) - if infoCSS: - if httpPrefix != 'https': - infoCSS = infoCSS.replace('https://', - httpPrefix + '://') - infoForm = htmlHeader(cssFilename, infoCSS) - - infoForm += \ - '

' + \ - translate['Moderation Information'] + \ - '

' - - infoShown = False - suspendedFilename = baseDir + '/accounts/suspended.txt' - if os.path.isfile(suspendedFilename): - with open(suspendedFilename, "r") as f: - suspendedStr = f.read() - infoForm += '
' - infoForm += '
' + \ - translate['Suspended accounts'] + '' - infoForm += '
' + \ - translate['These are currently suspended'] - infoForm += \ - ' ' - infoForm += '
' - infoShown = True - - blockingFilename = baseDir + '/accounts/blocking.txt' - if os.path.isfile(blockingFilename): - with open(blockingFilename, "r") as f: - blockedStr = f.read() - infoForm += '
' - infoForm += \ - '
' + \ - translate['Blocked accounts and hashtags'] + '' - infoForm += \ - '
' + \ - translate[msgStr1] - infoForm += \ - ' ' - infoForm += '
' - infoShown = True - if not infoShown: - infoForm += \ - '

' + \ - translate[msgStr2] + \ - '

' - infoForm += htmlFooter() - return infoForm - - -def htmlTermsOfService(cssCache: {}, baseDir: str, - httpPrefix: str, domainFull: str) -> str: - """Show the terms of service screen - """ - adminNickname = getConfigParam(baseDir, 'admin') - if not os.path.isfile(baseDir + '/accounts/tos.txt'): - copyfile(baseDir + '/default_tos.txt', - baseDir + '/accounts/tos.txt') - - if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'): - if not os.path.isfile(baseDir + '/accounts/login-background.jpg'): - copyfile(baseDir + '/accounts/login-background-custom.jpg', - baseDir + '/accounts/login-background.jpg') - - TOSText = 'Terms of Service go here.' - if os.path.isfile(baseDir + '/accounts/tos.txt'): - with open(baseDir + '/accounts/tos.txt', 'r') as file: - TOSText = file.read() - - TOSForm = '' - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - termsCSS = getCSS(baseDir, cssFilename, cssCache) - if termsCSS: - if httpPrefix != 'https': - termsCSS = termsCSS.replace('https://', httpPrefix+'://') - - TOSForm = htmlHeader(cssFilename, termsCSS) - TOSForm += '
' + TOSText + '
\n' - if adminNickname: - adminActor = httpPrefix + '://' + domainFull + \ - '/users/' + adminNickname - TOSForm += \ - '
\n' + \ - '

Administered by ' + adminNickname + '

\n' + \ - '
\n' - TOSForm += htmlFooter() - return TOSForm - - -def htmlAbout(cssCache: {}, baseDir: str, httpPrefix: str, - domainFull: str, onionDomain: str) -> str: - """Show the about screen - """ - adminNickname = getConfigParam(baseDir, 'admin') - if not os.path.isfile(baseDir + '/accounts/about.txt'): - copyfile(baseDir + '/default_about.txt', - baseDir + '/accounts/about.txt') - - if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'): - if not os.path.isfile(baseDir + '/accounts/login-background.jpg'): - copyfile(baseDir + '/accounts/login-background-custom.jpg', - baseDir + '/accounts/login-background.jpg') - - aboutText = 'Information about this instance goes here.' - if os.path.isfile(baseDir + '/accounts/about.txt'): - with open(baseDir + '/accounts/about.txt', 'r') as aboutFile: - aboutText = aboutFile.read() - - aboutForm = '' - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - aboutCSS = getCSS(baseDir, cssFilename, cssCache) - if aboutCSS: - if httpPrefix != 'http': - aboutCSS = aboutCSS.replace('https://', - httpPrefix + '://') - - aboutForm = htmlHeader(cssFilename, aboutCSS) - aboutForm += '
' + aboutText + '
' - if onionDomain: - aboutForm += \ - '
\n' + \ - '

' + \ - 'http://' + onionDomain + '

\n
\n' - if adminNickname: - adminActor = '/users/' + adminNickname - aboutForm += \ - '
\n' + \ - '

Administered by ' + adminNickname + '

\n' + \ - '
\n' - aboutForm += htmlFooter() - return aboutForm - - def htmlHashtagBlocked(cssCache: {}, baseDir: str, translate: {}) -> str: """Show the screen for a blocked hashtag """ @@ -228,25 +70,6 @@ def htmlHashtagBlocked(cssCache: {}, baseDir: str, translate: {}) -> str: return blockedHashtagForm -def htmlSuspended(cssCache: {}, baseDir: str) -> str: - """Show the screen for suspended accounts - """ - suspendedForm = '' - cssFilename = baseDir + '/epicyon-suspended.css' - if os.path.isfile(baseDir + '/suspended.css'): - cssFilename = baseDir + '/suspended.css' - - suspendedCSS = getCSS(baseDir, cssFilename, cssCache) - if suspendedCSS: - suspendedForm = htmlHeader(cssFilename, suspendedCSS) - suspendedForm += '
\n' - suspendedForm += '

Account Suspended

\n' - suspendedForm += '

See Terms of Service

\n' - suspendedForm += '
\n' - suspendedForm += htmlFooter() - return suspendedForm - - def htmlRemoveSharedItem(cssCache: {}, translate: {}, baseDir: str, actor: str, shareName: str, callingDomain: str) -> str: diff --git a/webapp_about.py b/webapp_about.py new file mode 100644 index 000000000..72b522398 --- /dev/null +++ b/webapp_about.py @@ -0,0 +1,62 @@ +__filename__ = "webapp_about.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from shutil import copyfile +from utils import getCSS +from utils import getConfigParam +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter + + +def htmlAbout(cssCache: {}, baseDir: str, httpPrefix: str, + domainFull: str, onionDomain: str) -> str: + """Show the about screen + """ + adminNickname = getConfigParam(baseDir, 'admin') + if not os.path.isfile(baseDir + '/accounts/about.txt'): + copyfile(baseDir + '/default_about.txt', + baseDir + '/accounts/about.txt') + + if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'): + if not os.path.isfile(baseDir + '/accounts/login-background.jpg'): + copyfile(baseDir + '/accounts/login-background-custom.jpg', + baseDir + '/accounts/login-background.jpg') + + aboutText = 'Information about this instance goes here.' + if os.path.isfile(baseDir + '/accounts/about.txt'): + with open(baseDir + '/accounts/about.txt', 'r') as aboutFile: + aboutText = aboutFile.read() + + aboutForm = '' + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + aboutCSS = getCSS(baseDir, cssFilename, cssCache) + if aboutCSS: + if httpPrefix != 'http': + aboutCSS = aboutCSS.replace('https://', + httpPrefix + '://') + + aboutForm = htmlHeader(cssFilename, aboutCSS) + aboutForm += '
' + aboutText + '
' + if onionDomain: + aboutForm += \ + '
\n' + \ + '

' + \ + 'http://' + onionDomain + '

\n
\n' + if adminNickname: + adminActor = '/users/' + adminNickname + aboutForm += \ + '
\n' + \ + '

Administered by ' + adminNickname + '

\n' + \ + '
\n' + aboutForm += htmlFooter() + return aboutForm diff --git a/webapp_moderation.py b/webapp_moderation.py new file mode 100644 index 000000000..a3ec54292 --- /dev/null +++ b/webapp_moderation.py @@ -0,0 +1,111 @@ +__filename__ = "webapp_moderation.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from utils import getCSS +from webapp_timeline import htmlTimeline +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter + + +def htmlModeration(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the moderation feed as html + This is what you see when selecting the "mod" timeline + """ + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, 'moderation', + allowDeletion, httpPrefix, projectVersion, True, False, + YTReplacementDomain, showPublishedDateOnly, + newswire, False, False, positiveVoting, + showPublishAsIcon, fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlModerationInfo(cssCache: {}, translate: {}, + baseDir: str, httpPrefix: str) -> str: + msgStr1 = \ + 'These are globally blocked for all accounts on this instance' + msgStr2 = \ + 'Any blocks or suspensions made by moderators will be shown here.' + infoForm = '' + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + infoCSS = getCSS(baseDir, cssFilename, cssCache) + if infoCSS: + if httpPrefix != 'https': + infoCSS = infoCSS.replace('https://', + httpPrefix + '://') + infoForm = htmlHeader(cssFilename, infoCSS) + + infoForm += \ + '

' + \ + translate['Moderation Information'] + \ + '

' + + infoShown = False + suspendedFilename = baseDir + '/accounts/suspended.txt' + if os.path.isfile(suspendedFilename): + with open(suspendedFilename, "r") as f: + suspendedStr = f.read() + infoForm += '
' + infoForm += '
' + \ + translate['Suspended accounts'] + '' + infoForm += '
' + \ + translate['These are currently suspended'] + infoForm += \ + ' ' + infoForm += '
' + infoShown = True + + blockingFilename = baseDir + '/accounts/blocking.txt' + if os.path.isfile(blockingFilename): + with open(blockingFilename, "r") as f: + blockedStr = f.read() + infoForm += '
' + infoForm += \ + '
' + \ + translate['Blocked accounts and hashtags'] + '' + infoForm += \ + '
' + \ + translate[msgStr1] + infoForm += \ + ' ' + infoForm += '
' + infoShown = True + if not infoShown: + infoForm += \ + '

' + \ + translate[msgStr2] + \ + '

' + infoForm += htmlFooter() + return infoForm diff --git a/webapp_suspended.py b/webapp_suspended.py new file mode 100644 index 000000000..cbcc1a23e --- /dev/null +++ b/webapp_suspended.py @@ -0,0 +1,31 @@ +__filename__ = "webapp_suspended.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from utils import getCSS +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter + + +def htmlSuspended(cssCache: {}, baseDir: str) -> str: + """Show the screen for suspended accounts + """ + suspendedForm = '' + cssFilename = baseDir + '/epicyon-suspended.css' + if os.path.isfile(baseDir + '/suspended.css'): + cssFilename = baseDir + '/suspended.css' + + suspendedCSS = getCSS(baseDir, cssFilename, cssCache) + if suspendedCSS: + suspendedForm = htmlHeader(cssFilename, suspendedCSS) + suspendedForm += '
\n' + suspendedForm += '

Account Suspended

\n' + suspendedForm += '

See Terms of Service

\n' + suspendedForm += '
\n' + suspendedForm += htmlFooter() + return suspendedForm diff --git a/webapp_timeline.py b/webapp_timeline.py index 9711814ce..80f2f17dd 100644 --- a/webapp_timeline.py +++ b/webapp_timeline.py @@ -1346,37 +1346,6 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str, authorized) -def htmlModeration(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the moderation feed as html - """ - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, 'moderation', - allowDeletion, httpPrefix, projectVersion, True, False, - YTReplacementDomain, showPublishedDateOnly, - newswire, False, False, positiveVoting, - showPublishAsIcon, fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - def htmlOutbox(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, diff --git a/webapp_tos.py b/webapp_tos.py new file mode 100644 index 000000000..707a29d42 --- /dev/null +++ b/webapp_tos.py @@ -0,0 +1,57 @@ +__filename__ = "webapp_tos.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from shutil import copyfile +from utils import getCSS +from utils import getConfigParam +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter + + +def htmlTermsOfService(cssCache: {}, baseDir: str, + httpPrefix: str, domainFull: str) -> str: + """Show the terms of service screen + """ + adminNickname = getConfigParam(baseDir, 'admin') + if not os.path.isfile(baseDir + '/accounts/tos.txt'): + copyfile(baseDir + '/default_tos.txt', + baseDir + '/accounts/tos.txt') + + if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'): + if not os.path.isfile(baseDir + '/accounts/login-background.jpg'): + copyfile(baseDir + '/accounts/login-background-custom.jpg', + baseDir + '/accounts/login-background.jpg') + + TOSText = 'Terms of Service go here.' + if os.path.isfile(baseDir + '/accounts/tos.txt'): + with open(baseDir + '/accounts/tos.txt', 'r') as file: + TOSText = file.read() + + TOSForm = '' + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + termsCSS = getCSS(baseDir, cssFilename, cssCache) + if termsCSS: + if httpPrefix != 'https': + termsCSS = termsCSS.replace('https://', httpPrefix+'://') + + TOSForm = htmlHeader(cssFilename, termsCSS) + TOSForm += '
' + TOSText + '
\n' + if adminNickname: + adminActor = httpPrefix + '://' + domainFull + \ + '/users/' + adminNickname + TOSForm += \ + '
\n' + \ + '

Administered by ' + adminNickname + '

\n' + \ + '
\n' + TOSForm += htmlFooter() + return TOSForm From c5a27206c49184c408d6fc759b5511118d45f7c3 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 11:24:57 +0000 Subject: [PATCH 04/21] Change title --- default_tos.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default_tos.txt b/default_tos.txt index 1004961e6..176240893 100644 --- a/default_tos.txt +++ b/default_tos.txt @@ -26,7 +26,7 @@

This system will not federate with instances whose moderation policy is incompatible with the content policy described above. If an instance lacks a moderation policy, or refuses to enforce one, it will be assumed to be incompatible.

-

Use of Data for Research Purposes

+

Use of User Generated Content for Research

Data may not be "scraped" or otherwise obtained from this instance and used for academic research or cited within research publications without the prior written permission of the administrator. Financial remedy will be sought through the courts from any researcher publishing data obtained from this instance without consent.

From f56bc33465d93536d50b55376c5bc81fa8d4b90e Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 11:38:27 +0000 Subject: [PATCH 05/21] Add about and ToS to links --- webapp_column_left.py | 7 +++++++ webapp_login.py | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/webapp_column_left.py b/webapp_column_left.py index 736bfb1bb..1d26b8ec0 100644 --- a/webapp_column_left.py +++ b/webapp_column_left.py @@ -125,6 +125,13 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str, # if showHeaderImage: # htmlStr += '
' + htmlStr += \ + '' + htmlStr += \ + '' + linksFilename = baseDir + '/accounts/links.txt' linksFileContainsEntries = False if os.path.isfile(linksFilename): diff --git a/webapp_login.py b/webapp_login.py index 6ee41fc5f..ebcff8b93 100644 --- a/webapp_login.py +++ b/webapp_login.py @@ -119,11 +119,11 @@ def htmlLogin(cssCache: {}, translate: {}, '' TOSstr = \ - '' - TOSstr += \ '' + TOSstr += \ + '' loginButtonStr = '' if accounts > 0: From c1e2e3ae5df5256d6d5d4e4f289ac3582e931e12 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 12:20:46 +0000 Subject: [PATCH 06/21] Admin can edit about text and ToS from left column --- webapp_column_left.py | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/webapp_column_left.py b/webapp_column_left.py index 1d26b8ec0..f01e71f4a 100644 --- a/webapp_column_left.py +++ b/webapp_column_left.py @@ -291,9 +291,6 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str, '

' + translate['Edit Links'] + '

' editLinksForm += \ '
\n' - # editLinksForm += \ - # ' \n' editLinksForm += \ '
\n' + \ ' ' + editLinksForm += \ + ' ' + \ + translate['About this Instance'] + \ + '
' + editLinksForm += \ + ' ' + editLinksForm += \ + '
' + + TOSFilename = baseDir + '/accounts/tos.txt' + TOSStr = '' + if os.path.isfile(TOSFilename): + with open(TOSFilename, 'r') as fp: + TOSStr = fp.read() + + editLinksForm += \ + '
' + editLinksForm += \ + ' ' + \ + translate['Terms of Service'] + \ + '
' + editLinksForm += \ + ' ' + editLinksForm += \ + '
' + editLinksForm += htmlFooter() return editLinksForm From d78139a2588a94319c6da65d18c37b5f28603792 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 12:26:32 +0000 Subject: [PATCH 07/21] Update edited about and tos --- daemon.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/daemon.py b/daemon.py index 9aaf85cde..a5c067a61 100644 --- a/daemon.py +++ b/daemon.py @@ -2891,10 +2891,13 @@ class PubServer(BaseHTTPRequestHandler): return linksFilename = baseDir + '/accounts/links.txt' + aboutFilename = baseDir + '/accounts/about.txt' + TOSFilename = baseDir + '/accounts/tos.txt' # extract all of the text fields into a dict fields = \ extractTextFieldsInPOST(postBytes, boundary, debug) + if fields.get('editedLinks'): linksStr = fields['editedLinks'] linksFile = open(linksFilename, "w+") @@ -2905,6 +2908,26 @@ class PubServer(BaseHTTPRequestHandler): if os.path.isfile(linksFilename): os.remove(linksFilename) + if fields.get('editedAbout'): + aboutStr = fields['editedAbout'] + aboutFile = open(aboutFilename, "w+") + if aboutFile: + aboutFile.write(aboutStr) + aboutFile.close() + else: + if os.path.isfile(aboutFilename): + os.remove(aboutFilename) + + if fields.get('editedTOS'): + TOSStr = fields['editedTOS'] + TOSFile = open(TOSFilename, "w+") + if TOSFile: + TOSFile.write(TOSStr) + TOSFile.close() + else: + if os.path.isfile(TOSFilename): + os.remove(TOSFilename) + # redirect back to the default timeline if callingDomain.endswith('.onion') and \ onionDomain: From 67ea67988798b91526b13577ed7689811116d48d Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 12:28:54 +0000 Subject: [PATCH 08/21] Check that the editor is the admin --- daemon.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/daemon.py b/daemon.py index a5c067a61..a23f9d8d3 100644 --- a/daemon.py +++ b/daemon.py @@ -2908,25 +2908,28 @@ class PubServer(BaseHTTPRequestHandler): if os.path.isfile(linksFilename): os.remove(linksFilename) - if fields.get('editedAbout'): - aboutStr = fields['editedAbout'] - aboutFile = open(aboutFilename, "w+") - if aboutFile: - aboutFile.write(aboutStr) - aboutFile.close() - else: - if os.path.isfile(aboutFilename): - os.remove(aboutFilename) + adminNickname = \ + getConfigParam(baseDir, 'admin') + if nickname == adminNickname: + if fields.get('editedAbout'): + aboutStr = fields['editedAbout'] + aboutFile = open(aboutFilename, "w+") + if aboutFile: + aboutFile.write(aboutStr) + aboutFile.close() + else: + if os.path.isfile(aboutFilename): + os.remove(aboutFilename) - if fields.get('editedTOS'): - TOSStr = fields['editedTOS'] - TOSFile = open(TOSFilename, "w+") - if TOSFile: - TOSFile.write(TOSStr) - TOSFile.close() - else: - if os.path.isfile(TOSFilename): - os.remove(TOSFilename) + if fields.get('editedTOS'): + TOSStr = fields['editedTOS'] + TOSFile = open(TOSFilename, "w+") + if TOSFile: + TOSFile.write(TOSStr) + TOSFile.close() + else: + if os.path.isfile(TOSFilename): + os.remove(TOSFilename) # redirect back to the default timeline if callingDomain.endswith('.onion') and \ From 3019846433e4af62094ff140bca3723f519deb25 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 12:36:36 +0000 Subject: [PATCH 09/21] Check for dangerous markup in about text or ToS --- daemon.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/daemon.py b/daemon.py index a23f9d8d3..01ac5c901 100644 --- a/daemon.py +++ b/daemon.py @@ -191,6 +191,7 @@ from utils import isSuspended from manualapprove import manualDenyFollowRequest from manualapprove import manualApproveFollowRequest from announce import createAnnounce +from content import dangerousMarkup from content import replaceEmojiFromTags from content import addHtmlTags from content import extractMediaInFormPOST @@ -2913,20 +2914,22 @@ class PubServer(BaseHTTPRequestHandler): if nickname == adminNickname: if fields.get('editedAbout'): aboutStr = fields['editedAbout'] - aboutFile = open(aboutFilename, "w+") - if aboutFile: - aboutFile.write(aboutStr) - aboutFile.close() + if not dangerousMarkup(aboutStr): + aboutFile = open(aboutFilename, "w+") + if aboutFile: + aboutFile.write(aboutStr) + aboutFile.close() else: if os.path.isfile(aboutFilename): os.remove(aboutFilename) if fields.get('editedTOS'): TOSStr = fields['editedTOS'] - TOSFile = open(TOSFilename, "w+") - if TOSFile: - TOSFile.write(TOSStr) - TOSFile.close() + if not dangerousMarkup(TOSStr): + TOSFile = open(TOSFilename, "w+") + if TOSFile: + TOSFile.write(TOSStr) + TOSFile.close() else: if os.path.isfile(TOSFilename): os.remove(TOSFilename) From d2e660dd63159536c84f2f96e032e2744612262d Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 13:18:41 +0000 Subject: [PATCH 10/21] Starlight screenshot --- README.md | 2 +- img/screenshot_starlight.jpg | Bin 0 -> 105603 bytes website/EN/index.html | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 img/screenshot_starlight.jpg diff --git a/README.md b/README.md index 9f7a28c46..50d3a9970 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
Epicyon, meaning "more than a dog". Largest of the Borophaginae which lived in North America 20-5 million years ago.
- + diff --git a/img/screenshot_starlight.jpg b/img/screenshot_starlight.jpg new file mode 100644 index 0000000000000000000000000000000000000000..516662b2ce7784a734c6c4153642e0a1fc6e5d42 GIT binary patch literal 105603 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 theme profile page - various screenshots + various screenshots mobile screenshot From e83fedb6535d80abb1476b7643611fe17281d995 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 13:21:42 +0000 Subject: [PATCH 11/21] Starlight screenshot --- website/EN/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/EN/index.html b/website/EN/index.html index 7b6672791..804c026a2 100644 --- a/website/EN/index.html +++ b/website/EN/index.html @@ -1168,7 +1168,7 @@ hacker theme profile page - various screenshots + various screenshots mobile screenshot From 7760816ec1da0a6c3699edba0bd549466535cbda Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 13:27:50 +0000 Subject: [PATCH 12/21] Blogging description --- website/EN/index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/EN/index.html b/website/EN/index.html index 804c026a2..e4b6be5cc 100644 --- a/website/EN/index.html +++ b/website/EN/index.html @@ -1199,9 +1199,8 @@ RSS 2.0 - RSS 3.0

- You don't need a separate blog system. Blog posts can be added and edited, and are federated as ActivityPub articles. They also have RSS version 2.0 and 3.0 feeds. People can comment on blog posts, but unlike other systems the moderation settings apply just the same as they do for any other fediverse post arriving at your server. This makes blog spam much easier to keep control over. + You don't need a separate blog system. Blog posts can be added and edited, and are federated as ActivityPub articles. They also have RSS version 2.0 feeds. People can comment on blog posts, but unlike other systems the moderation settings apply just the same as they do for any other fediverse post arriving at your server. This makes blog spam much easier to keep control over. You can subscribe to other people's blogs and they will appear in the right hand newswire column.

From 6939c82302a343f0132abec2d6b173110615d199 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 15:12:07 +0000 Subject: [PATCH 13/21] External css on timeline --- webapp_timeline.py | 1190 ++++++++++++++++++++++---------------------- webapp_utils.py | 20 + 2 files changed, 615 insertions(+), 595 deletions(-) diff --git a/webapp_timeline.py b/webapp_timeline.py index 80f2f17dd..9e65b3d64 100644 --- a/webapp_timeline.py +++ b/webapp_timeline.py @@ -18,7 +18,7 @@ from happening import thisWeeksEventsCheck from webapp_utils import getIconsDir from webapp_utils import htmlPostSeparator from webapp_utils import getBannerFile -from webapp_utils import htmlHeader +from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter from webapp_utils import sharesTimelineJson from webapp_post import preparePostFromHtmlCache @@ -29,6 +29,600 @@ from posts import isModerator from posts import isEditor +def htmlTimeline(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, + itemsPerPage: int, session, baseDir: str, + wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, timelineJson: {}, + boxName: str, allowDeletion: bool, + httpPrefix: str, projectVersion: str, + manuallyApproveFollowers: bool, + minimal: bool, + YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, moderator: bool, + editor: bool, + positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the timeline as html + """ + timelineStartTime = time.time() + + accountDir = baseDir + '/accounts/' + nickname + '@' + domain + + # should the calendar icon be highlighted? + newCalendarEvent = False + calendarImage = 'calendar.png' + calendarPath = '/calendar' + calendarFile = accountDir + '/.newCalendar' + if os.path.isfile(calendarFile): + newCalendarEvent = True + calendarImage = 'calendar_notify.png' + with open(calendarFile, 'r') as calfile: + calendarPath = calfile.read().replace('##sent##', '') + calendarPath = calendarPath.replace('\n', '').replace('\r', '') + + # should the DM button be highlighted? + newDM = False + dmFile = accountDir + '/.newDM' + if os.path.isfile(dmFile): + newDM = True + if boxName == 'dm': + os.remove(dmFile) + + # should the Replies button be highlighted? + newReply = False + replyFile = accountDir + '/.newReply' + if os.path.isfile(replyFile): + newReply = True + if boxName == 'tlreplies': + os.remove(replyFile) + + # should the Shares button be highlighted? + newShare = False + newShareFile = accountDir + '/.newShare' + if os.path.isfile(newShareFile): + newShare = True + if boxName == 'tlshares': + os.remove(newShareFile) + + # should the Moderation/reports button be highlighted? + newReport = False + newReportFile = accountDir + '/.newReport' + if os.path.isfile(newReportFile): + newReport = True + if boxName == 'moderation': + os.remove(newReportFile) + + # directory where icons are found + # This changes depending upon theme + iconsDir = getIconsDir(baseDir) + + separatorStr = '' + if boxName != 'tlmedia': + separatorStr = htmlPostSeparator(baseDir, None) + + # the css filename + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + # filename of the banner shown at the top + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + + # benchmark 1 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 1 = ' + str(timeDiff)) + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if not profileStyle: + print('ERROR: css file not found ' + cssFilename) + return None + + # replace any https within the css with whatever prefix is needed + if httpPrefix != 'https': + profileStyle = \ + profileStyle.replace('https://', + httpPrefix + '://') + + # is the user a moderator? + if not moderator: + moderator = isModerator(baseDir, nickname) + + # is the user a site editor? + if not editor: + editor = isEditor(baseDir, nickname) + + # benchmark 2 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 2 = ' + str(timeDiff)) + + # the appearance of buttons - highlighted or not + inboxButton = 'button' + blogsButton = 'button' + newsButton = 'button' + dmButton = 'button' + if newDM: + dmButton = 'buttonhighlighted' + repliesButton = 'button' + if newReply: + repliesButton = 'buttonhighlighted' + mediaButton = 'button' + bookmarksButton = 'button' + eventsButton = 'button' + sentButton = 'button' + sharesButton = 'button' + if newShare: + sharesButton = 'buttonhighlighted' + moderationButton = 'button' + if newReport: + moderationButton = 'buttonhighlighted' + if boxName == 'inbox': + inboxButton = 'buttonselected' + elif boxName == 'tlblogs': + blogsButton = 'buttonselected' + elif boxName == 'tlnews': + newsButton = 'buttonselected' + elif boxName == 'dm': + dmButton = 'buttonselected' + if newDM: + dmButton = 'buttonselectedhighlighted' + elif boxName == 'tlreplies': + repliesButton = 'buttonselected' + if newReply: + repliesButton = 'buttonselectedhighlighted' + elif boxName == 'tlmedia': + mediaButton = 'buttonselected' + elif boxName == 'outbox': + sentButton = 'buttonselected' + elif boxName == 'moderation': + moderationButton = 'buttonselected' + if newReport: + moderationButton = 'buttonselectedhighlighted' + elif boxName == 'tlshares': + sharesButton = 'buttonselected' + if newShare: + sharesButton = 'buttonselectedhighlighted' + elif boxName == 'tlbookmarks' or boxName == 'bookmarks': + bookmarksButton = 'buttonselected' + elif boxName == 'tlevents': + eventsButton = 'buttonselected' + + # get the full domain, including any port number + fullDomain = domain + if port != 80 and port != 443: + if ':' not in domain: + fullDomain = domain + ':' + str(port) + + usersPath = '/users/' + nickname + actor = httpPrefix + '://' + fullDomain + usersPath + + showIndividualPostIcons = True + + # show an icon for new follow approvals + followApprovals = '' + followRequestsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/followrequests.txt' + if os.path.isfile(followRequestsFilename): + with open(followRequestsFilename, 'r') as f: + for line in f: + if len(line) > 0: + # show follow approvals icon + followApprovals = \ + '' + \ + '' + \
+                        translate['Approve follow requests'] + \
+                        '\n' + break + + # benchmark 3 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff)) + + # moderation / reports button + moderationButtonStr = '' + if moderator and not minimal: + moderationButtonStr = \ + '' + + # shares, bookmarks and events buttons + sharesButtonStr = '' + bookmarksButtonStr = '' + eventsButtonStr = '' + if not minimal: + sharesButtonStr = \ + '' + + bookmarksButtonStr = \ + '' + + eventsButtonStr = \ + '' + + tlStr = htmlHeaderWithExternalStyle(cssFilename, profileStyle) + + # benchmark 4 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff)) + + # if this is a news instance and we are viewing the news timeline + newsHeader = False + if defaultTimeline == 'tlnews' and boxName == 'tlnews': + newsHeader = True + + newPostButtonStr = '' + # start of headericons div + if not newsHeader: + if not iconsAsButtons: + newPostButtonStr += '
' + + # what screen to go to when a new post is created + if boxName == 'dm': + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + translate['Create a new DM'] + \
+                '\n' + else: + newPostButtonStr += \ + '' + \ + '' + elif boxName == 'tlblogs' or boxName == 'tlnews': + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + \
+                translate['Create a new post'] + \
+                '\n' + else: + newPostButtonStr += \ + '' + \ + '' + elif boxName == 'tlevents': + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + \
+                translate['Create a new event'] + \
+                '\n' + else: + newPostButtonStr += \ + '' + \ + '' + else: + if not manuallyApproveFollowers: + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + \
+                    translate['Create a new post'] + \
+                    '\n' + else: + newPostButtonStr += \ + '' + \ + '' + else: + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + translate['Create a new post'] + \
+                    '\n' + else: + newPostButtonStr += \ + '' + \ + '' + + # This creates a link to the profile page when viewed + # in lynx, but should be invisible in a graphical web browser + tlStr += \ + '\n' + + # banner and row of buttons + tlStr += \ + '\n' + tlStr += '\n' + + if fullWidthTimelineButtonHeader: + tlStr += \ + headerButtonsTimeline(defaultTimeline, boxName, pageNumber, + translate, usersPath, mediaButton, + blogsButton, newsButton, inboxButton, + dmButton, newDM, repliesButton, + newReply, minimal, sentButton, + sharesButtonStr, bookmarksButtonStr, + eventsButtonStr, moderationButtonStr, + newPostButtonStr, baseDir, nickname, + domain, iconsDir, timelineStartTime, + newCalendarEvent, calendarPath, + calendarImage, followApprovals, + iconsAsButtons) + + # start the timeline + tlStr += '\n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + + domainFull = domain + if port: + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + + # left column + leftColumnStr = \ + getLeftColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, iconsDir, + editor, False, None, rssIconAtTop, + True, False) + tlStr += ' \n' + # center column containing posts + tlStr += ' \n' + + # right column + rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, iconsDir, + moderator, editor, + newswire, positiveVoting, + False, None, True, + showPublishAsIcon, + rssIconAtTop, publishButtonAtTop, + authorized, True) + tlStr += ' \n' + tlStr += ' \n' + + # benchmark 9 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 9 = ' + str(timeDiff)) + + tlStr += ' \n' + tlStr += '
' + \ + leftColumnStr + ' \n' + + if not fullWidthTimelineButtonHeader: + tlStr += \ + headerButtonsTimeline(defaultTimeline, boxName, pageNumber, + translate, usersPath, mediaButton, + blogsButton, newsButton, inboxButton, + dmButton, newDM, repliesButton, + newReply, minimal, sentButton, + sharesButtonStr, bookmarksButtonStr, + eventsButtonStr, moderationButtonStr, + newPostButtonStr, baseDir, nickname, + domain, iconsDir, timelineStartTime, + newCalendarEvent, calendarPath, + calendarImage, followApprovals, + iconsAsButtons) + + # second row of buttons for moderator actions + if moderator and boxName == 'moderation': + tlStr += \ + '' + tlStr += '
\n' + idx = 'Nickname or URL. Block using *@domain or nickname@domain' + tlStr += \ + ' ' + translate[idx] + '
\n' + tlStr += '
\n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += '
\n\n' + + # benchmark 6 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 6 = ' + str(timeDiff)) + + if boxName == 'tlshares': + maxSharesPerAccount = itemsPerPage + return (tlStr + + htmlSharesTimeline(translate, pageNumber, itemsPerPage, + baseDir, actor, nickname, domain, port, + maxSharesPerAccount, httpPrefix) + + htmlFooter()) + + # benchmark 7 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff)) + + # benchmark 8 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 8 = ' + str(timeDiff)) + + # page up arrow + if pageNumber > 1: + tlStr += \ + '
\n' + \ + ' ' + \
+            translate['Page up'] + '\n' + \ + '
\n' + + # show the posts + itemCtr = 0 + if timelineJson: + # if this is the media timeline then add an extra gallery container + if boxName == 'tlmedia': + if pageNumber > 1: + tlStr += '
' + tlStr += '
\n' + + # show each post in the timeline + for item in timelineJson['orderedItems']: + timelinePostStartTime = time.time() + + if item['type'] == 'Create' or \ + item['type'] == 'Announce' or \ + item['type'] == 'Update': + # is the actor who sent this post snoozed? + if isPersonSnoozed(baseDir, nickname, domain, item['actor']): + continue + + # is the post in the memory cache of recent ones? + currTlStr = None + if boxName != 'tlmedia' and \ + recentPostsCache.get('index'): + postId = \ + removeIdEnding(item['id']).replace('/', '#') + if postId in recentPostsCache['index']: + if not item.get('muted'): + if recentPostsCache['html'].get(postId): + currTlStr = recentPostsCache['html'][postId] + currTlStr = \ + preparePostFromHtmlCache(currTlStr, + boxName, + pageNumber) + # benchmark cache post + timeDiff = \ + int((time.time() - + timelinePostStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE POST CACHE TIMING ' + + boxName + ' = ' + str(timeDiff)) + + if not currTlStr: + # benchmark cache post + timeDiff = \ + int((time.time() - + timelinePostStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE POST DISK TIMING START ' + + boxName + ' = ' + str(timeDiff)) + + # read the post from disk + currTlStr = \ + individualPostAsHtml(False, recentPostsCache, + maxRecentPosts, + iconsDir, translate, pageNumber, + baseDir, session, wfRequest, + personCache, + nickname, domain, port, + item, None, True, + allowDeletion, + httpPrefix, projectVersion, + boxName, + YTReplacementDomain, + showPublishedDateOnly, + boxName != 'dm', + showIndividualPostIcons, + manuallyApproveFollowers, + False, True) + # benchmark cache post + timeDiff = \ + int((time.time() - + timelinePostStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE POST DISK TIMING ' + + boxName + ' = ' + str(timeDiff)) + + if currTlStr: + itemCtr += 1 + if separatorStr: + tlStr += separatorStr + tlStr += currTlStr + if boxName == 'tlmedia': + tlStr += '
\n' + + # page down arrow + if itemCtr > 2: + tlStr += \ + '
\n' + \ + ' ' + \
+            translate['Page down'] + '\n' + \ + '
\n' + + # end of column-center + tlStr += '
' + \ + rightColumnStr + '
\n' + tlStr += htmlFooter() + return tlStr + + def htmlIndividualShare(actor: str, item: {}, translate: {}, showContact: bool, removeButton: bool) -> str: """Returns an individual shared item as html @@ -440,600 +1034,6 @@ def headerButtonsTimeline(defaultTimeline: str, return tlStr -def htmlTimeline(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, - itemsPerPage: int, session, baseDir: str, - wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, timelineJson: {}, - boxName: str, allowDeletion: bool, - httpPrefix: str, projectVersion: str, - manuallyApproveFollowers: bool, - minimal: bool, - YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, moderator: bool, - editor: bool, - positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the timeline as html - """ - timelineStartTime = time.time() - - accountDir = baseDir + '/accounts/' + nickname + '@' + domain - - # should the calendar icon be highlighted? - newCalendarEvent = False - calendarImage = 'calendar.png' - calendarPath = '/calendar' - calendarFile = accountDir + '/.newCalendar' - if os.path.isfile(calendarFile): - newCalendarEvent = True - calendarImage = 'calendar_notify.png' - with open(calendarFile, 'r') as calfile: - calendarPath = calfile.read().replace('##sent##', '') - calendarPath = calendarPath.replace('\n', '').replace('\r', '') - - # should the DM button be highlighted? - newDM = False - dmFile = accountDir + '/.newDM' - if os.path.isfile(dmFile): - newDM = True - if boxName == 'dm': - os.remove(dmFile) - - # should the Replies button be highlighted? - newReply = False - replyFile = accountDir + '/.newReply' - if os.path.isfile(replyFile): - newReply = True - if boxName == 'tlreplies': - os.remove(replyFile) - - # should the Shares button be highlighted? - newShare = False - newShareFile = accountDir + '/.newShare' - if os.path.isfile(newShareFile): - newShare = True - if boxName == 'tlshares': - os.remove(newShareFile) - - # should the Moderation/reports button be highlighted? - newReport = False - newReportFile = accountDir + '/.newReport' - if os.path.isfile(newReportFile): - newReport = True - if boxName == 'moderation': - os.remove(newReportFile) - - # directory where icons are found - # This changes depending upon theme - iconsDir = getIconsDir(baseDir) - - separatorStr = '' - if boxName != 'tlmedia': - separatorStr = htmlPostSeparator(baseDir, None) - - # the css filename - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - # filename of the banner shown at the top - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - - # benchmark 1 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 1 = ' + str(timeDiff)) - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if not profileStyle: - print('ERROR: css file not found ' + cssFilename) - return None - - # replace any https within the css with whatever prefix is needed - if httpPrefix != 'https': - profileStyle = \ - profileStyle.replace('https://', - httpPrefix + '://') - - # is the user a moderator? - if not moderator: - moderator = isModerator(baseDir, nickname) - - # is the user a site editor? - if not editor: - editor = isEditor(baseDir, nickname) - - # benchmark 2 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 2 = ' + str(timeDiff)) - - # the appearance of buttons - highlighted or not - inboxButton = 'button' - blogsButton = 'button' - newsButton = 'button' - dmButton = 'button' - if newDM: - dmButton = 'buttonhighlighted' - repliesButton = 'button' - if newReply: - repliesButton = 'buttonhighlighted' - mediaButton = 'button' - bookmarksButton = 'button' - eventsButton = 'button' - sentButton = 'button' - sharesButton = 'button' - if newShare: - sharesButton = 'buttonhighlighted' - moderationButton = 'button' - if newReport: - moderationButton = 'buttonhighlighted' - if boxName == 'inbox': - inboxButton = 'buttonselected' - elif boxName == 'tlblogs': - blogsButton = 'buttonselected' - elif boxName == 'tlnews': - newsButton = 'buttonselected' - elif boxName == 'dm': - dmButton = 'buttonselected' - if newDM: - dmButton = 'buttonselectedhighlighted' - elif boxName == 'tlreplies': - repliesButton = 'buttonselected' - if newReply: - repliesButton = 'buttonselectedhighlighted' - elif boxName == 'tlmedia': - mediaButton = 'buttonselected' - elif boxName == 'outbox': - sentButton = 'buttonselected' - elif boxName == 'moderation': - moderationButton = 'buttonselected' - if newReport: - moderationButton = 'buttonselectedhighlighted' - elif boxName == 'tlshares': - sharesButton = 'buttonselected' - if newShare: - sharesButton = 'buttonselectedhighlighted' - elif boxName == 'tlbookmarks' or boxName == 'bookmarks': - bookmarksButton = 'buttonselected' - elif boxName == 'tlevents': - eventsButton = 'buttonselected' - - # get the full domain, including any port number - fullDomain = domain - if port != 80 and port != 443: - if ':' not in domain: - fullDomain = domain + ':' + str(port) - - usersPath = '/users/' + nickname - actor = httpPrefix + '://' + fullDomain + usersPath - - showIndividualPostIcons = True - - # show an icon for new follow approvals - followApprovals = '' - followRequestsFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/followrequests.txt' - if os.path.isfile(followRequestsFilename): - with open(followRequestsFilename, 'r') as f: - for line in f: - if len(line) > 0: - # show follow approvals icon - followApprovals = \ - '' + \ - '' + \
-                        translate['Approve follow requests'] + \
-                        '\n' - break - - # benchmark 3 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff)) - - # moderation / reports button - moderationButtonStr = '' - if moderator and not minimal: - moderationButtonStr = \ - '' - - # shares, bookmarks and events buttons - sharesButtonStr = '' - bookmarksButtonStr = '' - eventsButtonStr = '' - if not minimal: - sharesButtonStr = \ - '' - - bookmarksButtonStr = \ - '' - - eventsButtonStr = \ - '' - - tlStr = htmlHeader(cssFilename, profileStyle) - - # benchmark 4 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff)) - - # if this is a news instance and we are viewing the news timeline - newsHeader = False - if defaultTimeline == 'tlnews' and boxName == 'tlnews': - newsHeader = True - - newPostButtonStr = '' - # start of headericons div - if not newsHeader: - if not iconsAsButtons: - newPostButtonStr += '
' - - # what screen to go to when a new post is created - if boxName == 'dm': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + translate['Create a new DM'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - elif boxName == 'tlblogs' or boxName == 'tlnews': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                translate['Create a new post'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - elif boxName == 'tlevents': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                translate['Create a new event'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - else: - if not manuallyApproveFollowers: - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                    translate['Create a new post'] + \
-                    '\n' - else: - newPostButtonStr += \ - '' + \ - '' - else: - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + translate['Create a new post'] + \
-                    '\n' - else: - newPostButtonStr += \ - '' + \ - '' - - # This creates a link to the profile page when viewed - # in lynx, but should be invisible in a graphical web browser - tlStr += \ - '\n' - - # banner and row of buttons - tlStr += \ - '\n' - tlStr += '\n' - - if fullWidthTimelineButtonHeader: - tlStr += \ - headerButtonsTimeline(defaultTimeline, boxName, pageNumber, - translate, usersPath, mediaButton, - blogsButton, newsButton, inboxButton, - dmButton, newDM, repliesButton, - newReply, minimal, sentButton, - sharesButtonStr, bookmarksButtonStr, - eventsButtonStr, moderationButtonStr, - newPostButtonStr, baseDir, nickname, - domain, iconsDir, timelineStartTime, - newCalendarEvent, calendarPath, - calendarImage, followApprovals, - iconsAsButtons) - - # start the timeline - tlStr += '\n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - - domainFull = domain - if port: - if port != 80 and port != 443: - domainFull = domain + ':' + str(port) - - # left column - leftColumnStr = \ - getLeftColumnContent(baseDir, nickname, domainFull, - httpPrefix, translate, iconsDir, - editor, False, None, rssIconAtTop, - True, False) - tlStr += ' \n' - # center column containing posts - tlStr += ' \n' - - # right column - rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull, - httpPrefix, translate, iconsDir, - moderator, editor, - newswire, positiveVoting, - False, None, True, - showPublishAsIcon, - rssIconAtTop, publishButtonAtTop, - authorized, True) - tlStr += ' \n' - tlStr += ' \n' - - # benchmark 9 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 9 = ' + str(timeDiff)) - - tlStr += ' \n' - tlStr += '
' + \ - leftColumnStr + ' \n' - - if not fullWidthTimelineButtonHeader: - tlStr += \ - headerButtonsTimeline(defaultTimeline, boxName, pageNumber, - translate, usersPath, mediaButton, - blogsButton, newsButton, inboxButton, - dmButton, newDM, repliesButton, - newReply, minimal, sentButton, - sharesButtonStr, bookmarksButtonStr, - eventsButtonStr, moderationButtonStr, - newPostButtonStr, baseDir, nickname, - domain, iconsDir, timelineStartTime, - newCalendarEvent, calendarPath, - calendarImage, followApprovals, - iconsAsButtons) - - # second row of buttons for moderator actions - if moderator and boxName == 'moderation': - tlStr += \ - '
' - tlStr += '
\n' - idx = 'Nickname or URL. Block using *@domain or nickname@domain' - tlStr += \ - ' ' + translate[idx] + '
\n' - tlStr += '
\n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += '
\n
\n' - - # benchmark 6 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 6 = ' + str(timeDiff)) - - if boxName == 'tlshares': - maxSharesPerAccount = itemsPerPage - return (tlStr + - htmlSharesTimeline(translate, pageNumber, itemsPerPage, - baseDir, actor, nickname, domain, port, - maxSharesPerAccount, httpPrefix) + - htmlFooter()) - - # benchmark 7 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff)) - - # benchmark 8 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 8 = ' + str(timeDiff)) - - # page up arrow - if pageNumber > 1: - tlStr += \ - '
\n' + \ - ' ' + \
-            translate['Page up'] + '\n' + \ - '
\n' - - # show the posts - itemCtr = 0 - if timelineJson: - # if this is the media timeline then add an extra gallery container - if boxName == 'tlmedia': - if pageNumber > 1: - tlStr += '
' - tlStr += '
\n' - - # show each post in the timeline - for item in timelineJson['orderedItems']: - timelinePostStartTime = time.time() - - if item['type'] == 'Create' or \ - item['type'] == 'Announce' or \ - item['type'] == 'Update': - # is the actor who sent this post snoozed? - if isPersonSnoozed(baseDir, nickname, domain, item['actor']): - continue - - # is the post in the memory cache of recent ones? - currTlStr = None - if boxName != 'tlmedia' and \ - recentPostsCache.get('index'): - postId = \ - removeIdEnding(item['id']).replace('/', '#') - if postId in recentPostsCache['index']: - if not item.get('muted'): - if recentPostsCache['html'].get(postId): - currTlStr = recentPostsCache['html'][postId] - currTlStr = \ - preparePostFromHtmlCache(currTlStr, - boxName, - pageNumber) - # benchmark cache post - timeDiff = \ - int((time.time() - - timelinePostStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE POST CACHE TIMING ' + - boxName + ' = ' + str(timeDiff)) - - if not currTlStr: - # benchmark cache post - timeDiff = \ - int((time.time() - - timelinePostStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE POST DISK TIMING START ' + - boxName + ' = ' + str(timeDiff)) - - # read the post from disk - currTlStr = \ - individualPostAsHtml(False, recentPostsCache, - maxRecentPosts, - iconsDir, translate, pageNumber, - baseDir, session, wfRequest, - personCache, - nickname, domain, port, - item, None, True, - allowDeletion, - httpPrefix, projectVersion, - boxName, - YTReplacementDomain, - showPublishedDateOnly, - boxName != 'dm', - showIndividualPostIcons, - manuallyApproveFollowers, - False, True) - # benchmark cache post - timeDiff = \ - int((time.time() - - timelinePostStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE POST DISK TIMING ' + - boxName + ' = ' + str(timeDiff)) - - if currTlStr: - itemCtr += 1 - if separatorStr: - tlStr += separatorStr - tlStr += currTlStr - if boxName == 'tlmedia': - tlStr += '
\n' - - # page down arrow - if itemCtr > 2: - tlStr += \ - '
\n' + \ - ' ' + \
-            translate['Page down'] + '\n' + \ - '
\n' - - # end of column-center - tlStr += '
' + \ - rightColumnStr + '
\n' - tlStr += htmlFooter() - return tlStr - - def htmlShares(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, diff --git a/webapp_utils.py b/webapp_utils.py index 2dcf172c0..712ec5e79 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -437,6 +437,26 @@ def htmlHeader(cssFilename: str, css: str, lang='en') -> str: return htmlStr +def htmlHeaderWithExternalStyle(cssFilename: str, css: str, lang='en') -> str: + htmlStr = '\n' + htmlStr += '\n' + htmlStr += ' \n' + htmlStr += ' \n' + fontName, fontFormat = getFontFromCss(css) + if fontName: + htmlStr += ' \n' + htmlStr += ' \n' + cssFile = cssFilename.split('/')[-1] + htmlStr += ' ' + htmlStr += ' \n' + htmlStr += ' \n' + htmlStr += ' Epicyon\n' + htmlStr += ' \n' + htmlStr += ' \n' + return htmlStr + + def htmlFooter() -> str: htmlStr = ' \n' htmlStr += '\n' From 2aab0da7360a2c92567306cdd2f0c26f98446744 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 15:18:11 +0000 Subject: [PATCH 14/21] Has no style --- webapp_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp_utils.py b/webapp_utils.py index 712ec5e79..5c35b35ce 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -446,7 +446,6 @@ def htmlHeaderWithExternalStyle(cssFilename: str, css: str, lang='en') -> str: if fontName: htmlStr += ' \n' - htmlStr += ' \n' cssFile = cssFilename.split('/')[-1] htmlStr += ' ' htmlStr += ' \n' From 8bddaaf136a1f2a1993e53190a9c459c7a3c5c51 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 15:19:39 +0000 Subject: [PATCH 15/21] Newline --- webapp_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp_utils.py b/webapp_utils.py index 5c35b35ce..8e5894fe1 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -447,7 +447,7 @@ def htmlHeaderWithExternalStyle(cssFilename: str, css: str, lang='en') -> str: htmlStr += ' \n' cssFile = cssFilename.split('/')[-1] - htmlStr += ' ' + htmlStr += ' \n' htmlStr += ' \n' htmlStr += ' \n' htmlStr += ' Epicyon\n' From a99063bb64743c5d8e9263c2a5c26bd004898477 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 15:23:30 +0000 Subject: [PATCH 16/21] Get css from cache --- daemon.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daemon.py b/daemon.py index 01ac5c901..6e8765c30 100644 --- a/daemon.py +++ b/daemon.py @@ -166,6 +166,7 @@ from shares import getSharesFeedForPerson from shares import addShare from shares import removeShare from shares import expireShares +from utils import getCSS from utils import firstParagraphFromString from utils import clearFromPostCaches from utils import containsInvalidChars @@ -8279,8 +8280,9 @@ class PubServer(BaseHTTPRequestHandler): tries = 0 while tries < 5: try: - with open(path, 'r') as cssfile: - css = cssfile.read() + css = getCSS(self.server.baseDir, path, + self.server.cssCache) + if css: break except Exception as e: print(e) From 5b7bfc55ddca68e8b18a84c1aa653e74c2e1d836 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 16:10:47 +0000 Subject: [PATCH 17/21] External css on new post --- webapp_create_post.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp_create_post.py b/webapp_create_post.py index b69edc7ee..49f7e2cf8 100644 --- a/webapp_create_post.py +++ b/webapp_create_post.py @@ -13,7 +13,7 @@ from utils import getNicknameFromActor from utils import getDomainFromActor from webapp_utils import getIconsDir from webapp_utils import getBannerFile -from webapp_utils import htmlHeader +from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter @@ -558,7 +558,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {}, dateAndLocation += '\n' dateAndLocation += '
\n' - newPostForm = htmlHeader(cssFilename, newPostCSS) + newPostForm = htmlHeaderWithExternalStyle(cssFilename, newPostCSS) newPostForm += \ '' + \ @@ -272,7 +272,7 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str, # filename of the banner shown at the top bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - editLinksForm = htmlHeader(cssFilename, editCSS) + editLinksForm = htmlHeaderWithExternalStyle(cssFilename, editCSS) # top banner editLinksForm += \ diff --git a/webapp_column_right.py b/webapp_column_right.py index 0e3cff85a..46529a8a7 100644 --- a/webapp_column_right.py +++ b/webapp_column_right.py @@ -20,7 +20,7 @@ from posts import isEditor from posts import isModerator from webapp_utils import getRightImageFile from webapp_utils import getImageFile -from webapp_utils import htmlHeader +from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter from webapp_utils import getBannerFile from webapp_utils import htmlPostSeparator @@ -314,7 +314,7 @@ def htmlCitations(baseDir: str, nickname: str, domain: str, # iconsDir = getIconsDir(baseDir) - htmlStr = htmlHeader(cssFilename, profileStyle) + htmlStr = htmlHeaderWithExternalStyle(cssFilename, profileStyle) # top banner bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) @@ -424,7 +424,7 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str, showPublishButton = editor - htmlStr = htmlHeader(cssFilename, profileStyle) + htmlStr = htmlHeaderWithExternalStyle(cssFilename, profileStyle) bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) htmlStr += \ @@ -479,7 +479,7 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str, # filename of the banner shown at the top bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - editNewswireForm = htmlHeader(cssFilename, editCSS) + editNewswireForm = htmlHeaderWithExternalStyle(cssFilename, editCSS) # top banner editNewswireForm += \ @@ -604,7 +604,7 @@ def htmlEditNewsPost(cssCache: {}, translate: {}, baseDir: str, path: str, editCSS = \ editCSS.replace('https://', httpPrefix + '://') - editNewsPostForm = htmlHeader(cssFilename, editCSS) + editNewsPostForm = htmlHeaderWithExternalStyle(cssFilename, editCSS) editNewsPostForm += \ '
\n' From 0f2f21359eadbc5986aa7a5c72d9f6389a17ca29 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 20:40:19 +0000 Subject: [PATCH 19/21] Back button path --- webapp_person_options.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/webapp_person_options.py b/webapp_person_options.py index 9c37e00de..80bae0771 100644 --- a/webapp_person_options.py +++ b/webapp_person_options.py @@ -202,9 +202,13 @@ def htmlPersonOptions(cssCache: {}, translate: {}, baseDir: str, optionsStr += checkboxStr optionsStr += optionsLinkStr + backPath = '/' + if nickname: + backPath = '/users/' + nickname optionsStr += \ - ' ' + ' ' optionsStr += \ ' ' From f040bdbc385fa5234dc157a0c410e1ee374d2376 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 10 Nov 2020 20:44:38 +0000 Subject: [PATCH 20/21] Back to default timeline --- daemon.py | 3 ++- webapp_person_options.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/daemon.py b/daemon.py index 6e8765c30..f3ffefb95 100644 --- a/daemon.py +++ b/daemon.py @@ -4708,7 +4708,8 @@ class PubServer(BaseHTTPRequestHandler): emailAddress = getEmailAddress(actorJson) PGPpubKey = getPGPpubKey(actorJson) PGPfingerprint = getPGPfingerprint(actorJson) - msg = htmlPersonOptions(self.server.cssCache, + msg = htmlPersonOptions(self.server.defaultTimeline, + self.server.cssCache, self.server.translate, baseDir, domain, domainFull, diff --git a/webapp_person_options.py b/webapp_person_options.py index 80bae0771..c6cb8df0e 100644 --- a/webapp_person_options.py +++ b/webapp_person_options.py @@ -21,7 +21,8 @@ from webapp_utils import htmlHeader from webapp_utils import htmlFooter -def htmlPersonOptions(cssCache: {}, translate: {}, baseDir: str, +def htmlPersonOptions(defaultTimeline: str, + cssCache: {}, translate: {}, baseDir: str, domain: str, domainFull: str, originPathStr: str, optionsActor: str, @@ -204,7 +205,7 @@ def htmlPersonOptions(cssCache: {}, translate: {}, baseDir: str, optionsStr += optionsLinkStr backPath = '/' if nickname: - backPath = '/users/' + nickname + backPath = '/users/' + nickname + '/' + defaultTimeline optionsStr += \ '