mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
commit
b98155a6d6
|
@ -4,7 +4,7 @@ Add issues on https://gitlab.com/bashrc2/epicyon/-/issues
|
|||
|
||||
<blockquote><b>Epicyon</b>, meaning <i>"more than a dog"</i>. Largest of the <i>Borophaginae</i> which lived in North America 20-5 million years ago.</blockquote>
|
||||
|
||||
<img src="https://epicyon.net/img/screenshot_indymedia.jpg" width="80%"/>
|
||||
<img src="https://epicyon.net/img/screenshot_starlight.jpg" width="80%"/>
|
||||
|
||||
<img src="https://epicyon.net/img/mobile.jpg" width="30%"/>
|
||||
|
||||
|
|
54
daemon.py
54
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,16 +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 import htmlLogin
|
||||
from webapp import htmlSuspended
|
||||
from webapp import htmlGetLoginCredentials
|
||||
from webapp_login import htmlLogin
|
||||
from webapp_login import htmlGetLoginCredentials
|
||||
from webapp_suspended import htmlSuspended
|
||||
from webapp_tos import htmlTermsOfService
|
||||
from webapp import htmlFollowConfirm
|
||||
from webapp import htmlUnfollowConfirm
|
||||
from webapp import htmlEditNewsPost
|
||||
from webapp import htmlTermsOfService
|
||||
from webapp import htmlModerationInfo
|
||||
from webapp import htmlHashtagBlocked
|
||||
from webapp_post import htmlPostReplies
|
||||
from webapp_post import htmlIndividualPost
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -191,6 +192,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
|
||||
|
@ -2891,10 +2893,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 +2910,31 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if os.path.isfile(linksFilename):
|
||||
os.remove(linksFilename)
|
||||
|
||||
adminNickname = \
|
||||
getConfigParam(baseDir, 'admin')
|
||||
if nickname == adminNickname:
|
||||
if fields.get('editedAbout'):
|
||||
aboutStr = fields['editedAbout']
|
||||
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']
|
||||
if not dangerousMarkup(TOSStr):
|
||||
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:
|
||||
|
@ -4678,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,
|
||||
|
@ -8250,8 +8281,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)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<p>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.</p>
|
||||
|
||||
<h3>Use of Data for Research Purposes</h3>
|
||||
<h3>Use of User Generated Content for Research</h3>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
--follow-text-size2: 40px;
|
||||
--follow-text-entry-width: 90%;
|
||||
--focus-color: white;
|
||||
--petname-width-chars: 16ch;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
@ -139,6 +140,7 @@ a:focus {
|
|||
input[type=text] {
|
||||
width: var(--follow-text-entry-width);
|
||||
clear: both;
|
||||
min-width: var(--petname-width-chars);
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
color: var(--text-entry-foreground);
|
||||
|
@ -216,6 +218,7 @@ a:focus {
|
|||
clear: both;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
min-width: var(--petname-width-chars);
|
||||
color: var(--text-entry-foreground);
|
||||
background-color: var(--text-entry-background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
412
webapp.py
412
webapp.py
|
@ -6,17 +6,13 @@ __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
|
||||
from shares import getValidSharedItemID
|
||||
from webapp_utils import getAltPath
|
||||
from webapp_utils import getIconsDir
|
||||
|
@ -51,395 +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 += \
|
||||
'<center><h1>' + \
|
||||
translate['Moderation Information'] + \
|
||||
'</h1></center>'
|
||||
|
||||
infoShown = False
|
||||
suspendedFilename = baseDir + '/accounts/suspended.txt'
|
||||
if os.path.isfile(suspendedFilename):
|
||||
with open(suspendedFilename, "r") as f:
|
||||
suspendedStr = f.read()
|
||||
infoForm += '<div class="container">'
|
||||
infoForm += ' <br><b>' + \
|
||||
translate['Suspended accounts'] + '</b>'
|
||||
infoForm += ' <br>' + \
|
||||
translate['These are currently suspended']
|
||||
infoForm += \
|
||||
' <textarea id="message" ' + \
|
||||
'name="suspended" style="height:200px">' + \
|
||||
suspendedStr + '</textarea>'
|
||||
infoForm += '</div>'
|
||||
infoShown = True
|
||||
|
||||
blockingFilename = baseDir + '/accounts/blocking.txt'
|
||||
if os.path.isfile(blockingFilename):
|
||||
with open(blockingFilename, "r") as f:
|
||||
blockedStr = f.read()
|
||||
infoForm += '<div class="container">'
|
||||
infoForm += \
|
||||
' <br><b>' + \
|
||||
translate['Blocked accounts and hashtags'] + '</b>'
|
||||
infoForm += \
|
||||
' <br>' + \
|
||||
translate[msgStr1]
|
||||
infoForm += \
|
||||
' <textarea id="message" ' + \
|
||||
'name="blocked" style="height:700px">' + \
|
||||
blockedStr + '</textarea>'
|
||||
infoForm += '</div>'
|
||||
infoShown = True
|
||||
if not infoShown:
|
||||
infoForm += \
|
||||
'<center><p>' + \
|
||||
translate[msgStr2] + \
|
||||
'</p></center>'
|
||||
infoForm += htmlFooter()
|
||||
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 += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/newseditdata">\n'
|
||||
editNewsPostForm += \
|
||||
' <div class="vertical-center">\n'
|
||||
editNewsPostForm += \
|
||||
' <p class="new-post-text">' + translate['Edit News Post'] + '</p>'
|
||||
editNewsPostForm += \
|
||||
' <div class="container">\n'
|
||||
editNewsPostForm += \
|
||||
' <a href="' + pathOriginal + '/tlnews">' + \
|
||||
'<button class="cancelbtn">' + translate['Go Back'] + '</button></a>\n'
|
||||
editNewsPostForm += \
|
||||
' <input type="submit" name="submitEditedNewsPost" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
editNewsPostForm += \
|
||||
' </div>\n'
|
||||
|
||||
editNewsPostForm += \
|
||||
'<div class="container">'
|
||||
|
||||
editNewsPostForm += \
|
||||
' <input type="hidden" name="newsPostUrl" value="' + \
|
||||
postUrl + '">\n'
|
||||
|
||||
newsPostTitle = postJsonObject['object']['summary']
|
||||
editNewsPostForm += \
|
||||
' <input type="text" name="newsPostTitle" value="' + \
|
||||
newsPostTitle + '"><br>\n'
|
||||
|
||||
newsPostContent = postJsonObject['object']['content']
|
||||
editNewsPostForm += \
|
||||
' <textarea id="message" name="editedNewsPost" ' + \
|
||||
'style="height:600px">' + newsPostContent + '</textarea>'
|
||||
|
||||
editNewsPostForm += \
|
||||
'</div>'
|
||||
|
||||
editNewsPostForm += htmlFooter()
|
||||
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 = \
|
||||
'<p class="login-text">' + \
|
||||
translate['Welcome. Please enter your login details below.'] + \
|
||||
'</p>'
|
||||
else:
|
||||
loginText = \
|
||||
'<p class="login-text">' + \
|
||||
translate['Please enter some credentials'] + '</p>'
|
||||
loginText += \
|
||||
'<p class="login-text">' + \
|
||||
translate['You will become the admin of this site.'] + \
|
||||
'</p>'
|
||||
if os.path.isfile(baseDir + '/accounts/login.txt'):
|
||||
# custom login message
|
||||
with open(baseDir + '/accounts/login.txt', 'r') as file:
|
||||
loginText = '<p class="login-text">' + file.read() + '</p>'
|
||||
|
||||
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 = \
|
||||
'<p class="login-text">' + \
|
||||
translate[idx] + \
|
||||
'</p>'
|
||||
registerButtonStr = \
|
||||
'<button type="submit" name="register">Register</button>'
|
||||
|
||||
TOSstr = \
|
||||
'<p class="login-text"><a href="/terms">' + \
|
||||
translate['Terms of Service'] + '</a></p>'
|
||||
TOSstr += \
|
||||
'<p class="login-text"><a href="/about">' + \
|
||||
translate['About this Instance'] + '</a></p>'
|
||||
|
||||
loginButtonStr = ''
|
||||
if accounts > 0:
|
||||
loginButtonStr = \
|
||||
'<button type="submit" name="submit">' + \
|
||||
translate['Login'] + '</button>'
|
||||
|
||||
autocompleteStr = ''
|
||||
if not autocomplete:
|
||||
autocompleteStr = 'autocomplete="off" value=""'
|
||||
|
||||
loginForm = htmlHeader(cssFilename, loginCSS)
|
||||
loginForm += '<br>\n'
|
||||
loginForm += '<form method="POST" action="/login">\n'
|
||||
loginForm += ' <div class="imgcontainer">\n'
|
||||
loginForm += \
|
||||
' <img loading="lazy" src="' + loginImage + \
|
||||
'" alt="login image" class="loginimage">\n'
|
||||
loginForm += loginText + TOSstr + '\n'
|
||||
loginForm += ' </div>\n'
|
||||
loginForm += '\n'
|
||||
loginForm += ' <div class="container">\n'
|
||||
loginForm += ' <label for="nickname"><b>' + \
|
||||
translate['Nickname'] + '</b></label>\n'
|
||||
loginForm += \
|
||||
' <input type="text" ' + autocompleteStr + ' placeholder="' + \
|
||||
translate['Enter Nickname'] + '" name="username" required autofocus>\n'
|
||||
loginForm += '\n'
|
||||
loginForm += ' <label for="password"><b>' + \
|
||||
translate['Password'] + '</b></label>\n'
|
||||
loginForm += \
|
||||
' <input type="password" ' + autocompleteStr + \
|
||||
' placeholder="' + translate['Enter Password'] + \
|
||||
'" name="password" required>\n'
|
||||
loginForm += loginButtonStr + registerButtonStr + '\n'
|
||||
loginForm += ' </div>\n'
|
||||
loginForm += '</form>\n'
|
||||
loginForm += \
|
||||
'<a href="https://gitlab.com/bashrc2/epicyon">' + \
|
||||
'<img loading="lazy" class="license" title="' + \
|
||||
translate['Get the source code'] + '" alt="' + \
|
||||
translate['Get the source code'] + '" src="/icons/agpl.png" /></a>\n'
|
||||
loginForm += htmlFooter()
|
||||
return loginForm
|
||||
|
||||
|
||||
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 += '<div class="container">' + TOSText + '</div>\n'
|
||||
if adminNickname:
|
||||
adminActor = httpPrefix + '://' + domainFull + \
|
||||
'/users/' + adminNickname
|
||||
TOSForm += \
|
||||
'<div class="container"><center>\n' + \
|
||||
'<p class="administeredby">Administered by <a href="' + \
|
||||
adminActor + '">' + adminNickname + '</a></p>\n' + \
|
||||
'</center></div>\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 += '<div class="container">' + aboutText + '</div>'
|
||||
if onionDomain:
|
||||
aboutForm += \
|
||||
'<div class="container"><center>\n' + \
|
||||
'<p class="administeredby">' + \
|
||||
'http://' + onionDomain + '</p>\n</center></div>\n'
|
||||
if adminNickname:
|
||||
adminActor = '/users/' + adminNickname
|
||||
aboutForm += \
|
||||
'<div class="container"><center>\n' + \
|
||||
'<p class="administeredby">Administered by <a href="' + \
|
||||
adminActor + '">' + adminNickname + '</a></p>\n' + \
|
||||
'</center></div>\n'
|
||||
aboutForm += htmlFooter()
|
||||
return aboutForm
|
||||
|
||||
|
||||
def htmlHashtagBlocked(cssCache: {}, baseDir: str, translate: {}) -> str:
|
||||
"""Show the screen for a blocked hashtag
|
||||
"""
|
||||
|
@ -463,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 += '<div><center>\n'
|
||||
suspendedForm += ' <p class="screentitle">Account Suspended</p>\n'
|
||||
suspendedForm += ' <p>See <a href="/terms">Terms of Service</a></p>\n'
|
||||
suspendedForm += '</center></div>\n'
|
||||
suspendedForm += htmlFooter()
|
||||
return suspendedForm
|
||||
|
||||
|
||||
def htmlRemoveSharedItem(cssCache: {}, translate: {}, baseDir: str,
|
||||
actor: str, shareName: str,
|
||||
callingDomain: str) -> str:
|
||||
|
|
|
@ -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 += '<div class="container">' + aboutText + '</div>'
|
||||
if onionDomain:
|
||||
aboutForm += \
|
||||
'<div class="container"><center>\n' + \
|
||||
'<p class="administeredby">' + \
|
||||
'http://' + onionDomain + '</p>\n</center></div>\n'
|
||||
if adminNickname:
|
||||
adminActor = '/users/' + adminNickname
|
||||
aboutForm += \
|
||||
'<div class="container"><center>\n' + \
|
||||
'<p class="administeredby">Administered by <a href="' + \
|
||||
adminActor + '">' + adminNickname + '</a></p>\n' + \
|
||||
'</center></div>\n'
|
||||
aboutForm += htmlFooter()
|
||||
return aboutForm
|
|
@ -17,7 +17,7 @@ from webapp_utils import getLeftImageFile
|
|||
from webapp_utils import getImageFile
|
||||
from webapp_utils import headerButtonsFrontScreen
|
||||
from webapp_utils import getIconsDir
|
||||
from webapp_utils import htmlHeader
|
||||
from webapp_utils import htmlHeaderWithExternalStyle
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import getBannerFile
|
||||
|
||||
|
@ -125,6 +125,13 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
|||
# if showHeaderImage:
|
||||
# htmlStr += '<br>'
|
||||
|
||||
htmlStr += \
|
||||
'<p class="login-text"><a href="/about">' + \
|
||||
translate['About this Instance'] + '</a></p>'
|
||||
htmlStr += \
|
||||
'<p class="login-text"><a href="/terms">' + \
|
||||
translate['Terms of Service'] + '</a></p>'
|
||||
|
||||
linksFilename = baseDir + '/accounts/links.txt'
|
||||
linksFileContainsEntries = False
|
||||
if os.path.isfile(linksFilename):
|
||||
|
@ -213,7 +220,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
|
|||
if ':' in domain:
|
||||
domain = domain.split(':')[0]
|
||||
|
||||
htmlStr = htmlHeader(cssFilename, profileStyle)
|
||||
htmlStr = htmlHeaderWithExternalStyle(cssFilename, profileStyle)
|
||||
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
|
||||
htmlStr += \
|
||||
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
|
||||
|
@ -265,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 += \
|
||||
|
@ -284,9 +291,6 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
|
|||
' <p class="new-post-text">' + translate['Edit Links'] + '</p>'
|
||||
editLinksForm += \
|
||||
' <div class="container">\n'
|
||||
# editLinksForm += \
|
||||
# ' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
|
||||
# translate['Go Back'] + '</button></a>\n'
|
||||
editLinksForm += \
|
||||
' <center>\n' + \
|
||||
' <input type="submit" name="submitLinks" value="' + \
|
||||
|
@ -313,5 +317,45 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
|
|||
editLinksForm += \
|
||||
'</div>'
|
||||
|
||||
# the admin can edit terms of service and about text
|
||||
adminNickname = getConfigParam(baseDir, 'admin')
|
||||
if adminNickname:
|
||||
if nickname == adminNickname:
|
||||
aboutFilename = baseDir + '/accounts/about.txt'
|
||||
aboutStr = ''
|
||||
if os.path.isfile(aboutFilename):
|
||||
with open(aboutFilename, 'r') as fp:
|
||||
aboutStr = fp.read()
|
||||
|
||||
editLinksForm += \
|
||||
'<div class="container">'
|
||||
editLinksForm += \
|
||||
' ' + \
|
||||
translate['About this Instance'] + \
|
||||
'<br>'
|
||||
editLinksForm += \
|
||||
' <textarea id="message" name="editedAbout" ' + \
|
||||
'style="height:100vh">' + aboutStr + '</textarea>'
|
||||
editLinksForm += \
|
||||
'</div>'
|
||||
|
||||
TOSFilename = baseDir + '/accounts/tos.txt'
|
||||
TOSStr = ''
|
||||
if os.path.isfile(TOSFilename):
|
||||
with open(TOSFilename, 'r') as fp:
|
||||
TOSStr = fp.read()
|
||||
|
||||
editLinksForm += \
|
||||
'<div class="container">'
|
||||
editLinksForm += \
|
||||
' ' + \
|
||||
translate['Terms of Service'] + \
|
||||
'<br>'
|
||||
editLinksForm += \
|
||||
' <textarea id="message" name="editedTOS" ' + \
|
||||
'style="height:100vh">' + TOSStr + '</textarea>'
|
||||
editLinksForm += \
|
||||
'</div>'
|
||||
|
||||
editLinksForm += htmlFooter()
|
||||
return editLinksForm
|
||||
|
|
|
@ -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
|
||||
|
@ -18,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
|
||||
|
@ -312,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)
|
||||
|
@ -422,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 += \
|
||||
|
@ -477,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 += \
|
||||
|
@ -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 = htmlHeaderWithExternalStyle(cssFilename, editCSS)
|
||||
editNewsPostForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/newseditdata">\n'
|
||||
editNewsPostForm += \
|
||||
' <div class="vertical-center">\n'
|
||||
editNewsPostForm += \
|
||||
' <p class="new-post-text">' + translate['Edit News Post'] + '</p>'
|
||||
editNewsPostForm += \
|
||||
' <div class="container">\n'
|
||||
editNewsPostForm += \
|
||||
' <a href="' + pathOriginal + '/tlnews">' + \
|
||||
'<button class="cancelbtn">' + translate['Go Back'] + '</button></a>\n'
|
||||
editNewsPostForm += \
|
||||
' <input type="submit" name="submitEditedNewsPost" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
editNewsPostForm += \
|
||||
' </div>\n'
|
||||
|
||||
editNewsPostForm += \
|
||||
'<div class="container">'
|
||||
|
||||
editNewsPostForm += \
|
||||
' <input type="hidden" name="newsPostUrl" value="' + \
|
||||
postUrl + '">\n'
|
||||
|
||||
newsPostTitle = postJsonObject['object']['summary']
|
||||
editNewsPostForm += \
|
||||
' <input type="text" name="newsPostTitle" value="' + \
|
||||
newsPostTitle + '"><br>\n'
|
||||
|
||||
newsPostContent = postJsonObject['object']['content']
|
||||
editNewsPostForm += \
|
||||
' <textarea id="message" name="editedNewsPost" ' + \
|
||||
'style="height:600px">' + newsPostContent + '</textarea>'
|
||||
|
||||
editNewsPostForm += \
|
||||
'</div>'
|
||||
|
||||
editNewsPostForm += htmlFooter()
|
||||
return editNewsPostForm
|
||||
|
|
|
@ -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 += '<input type="text" name="category">\n'
|
||||
dateAndLocation += '</div>\n'
|
||||
|
||||
newPostForm = htmlHeader(cssFilename, newPostCSS)
|
||||
newPostForm = htmlHeaderWithExternalStyle(cssFilename, newPostCSS)
|
||||
|
||||
newPostForm += \
|
||||
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
|
||||
|
|
|
@ -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 = \
|
||||
'<p class="login-text">' + \
|
||||
translate['Welcome. Please enter your login details below.'] + \
|
||||
'</p>'
|
||||
else:
|
||||
loginText = \
|
||||
'<p class="login-text">' + \
|
||||
translate['Please enter some credentials'] + '</p>'
|
||||
loginText += \
|
||||
'<p class="login-text">' + \
|
||||
translate['You will become the admin of this site.'] + \
|
||||
'</p>'
|
||||
if os.path.isfile(baseDir + '/accounts/login.txt'):
|
||||
# custom login message
|
||||
with open(baseDir + '/accounts/login.txt', 'r') as file:
|
||||
loginText = '<p class="login-text">' + file.read() + '</p>'
|
||||
|
||||
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 = \
|
||||
'<p class="login-text">' + \
|
||||
translate[idx] + \
|
||||
'</p>'
|
||||
registerButtonStr = \
|
||||
'<button type="submit" name="register">Register</button>'
|
||||
|
||||
TOSstr = \
|
||||
'<p class="login-text"><a href="/about">' + \
|
||||
translate['About this Instance'] + '</a></p>'
|
||||
TOSstr += \
|
||||
'<p class="login-text"><a href="/terms">' + \
|
||||
translate['Terms of Service'] + '</a></p>'
|
||||
|
||||
loginButtonStr = ''
|
||||
if accounts > 0:
|
||||
loginButtonStr = \
|
||||
'<button type="submit" name="submit">' + \
|
||||
translate['Login'] + '</button>'
|
||||
|
||||
autocompleteStr = ''
|
||||
if not autocomplete:
|
||||
autocompleteStr = 'autocomplete="off" value=""'
|
||||
|
||||
loginForm = htmlHeader(cssFilename, loginCSS)
|
||||
loginForm += '<br>\n'
|
||||
loginForm += '<form method="POST" action="/login">\n'
|
||||
loginForm += ' <div class="imgcontainer">\n'
|
||||
loginForm += \
|
||||
' <img loading="lazy" src="' + loginImage + \
|
||||
'" alt="login image" class="loginimage">\n'
|
||||
loginForm += loginText + TOSstr + '\n'
|
||||
loginForm += ' </div>\n'
|
||||
loginForm += '\n'
|
||||
loginForm += ' <div class="container">\n'
|
||||
loginForm += ' <label for="nickname"><b>' + \
|
||||
translate['Nickname'] + '</b></label>\n'
|
||||
loginForm += \
|
||||
' <input type="text" ' + autocompleteStr + ' placeholder="' + \
|
||||
translate['Enter Nickname'] + '" name="username" required autofocus>\n'
|
||||
loginForm += '\n'
|
||||
loginForm += ' <label for="password"><b>' + \
|
||||
translate['Password'] + '</b></label>\n'
|
||||
loginForm += \
|
||||
' <input type="password" ' + autocompleteStr + \
|
||||
' placeholder="' + translate['Enter Password'] + \
|
||||
'" name="password" required>\n'
|
||||
loginForm += loginButtonStr + registerButtonStr + '\n'
|
||||
loginForm += ' </div>\n'
|
||||
loginForm += '</form>\n'
|
||||
loginForm += \
|
||||
'<a href="https://gitlab.com/bashrc2/epicyon">' + \
|
||||
'<img loading="lazy" class="license" title="' + \
|
||||
translate['Get the source code'] + '" alt="' + \
|
||||
translate['Get the source code'] + '" src="/icons/agpl.png" /></a>\n'
|
||||
loginForm += htmlFooter()
|
||||
return loginForm
|
|
@ -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 += \
|
||||
'<center><h1>' + \
|
||||
translate['Moderation Information'] + \
|
||||
'</h1></center>'
|
||||
|
||||
infoShown = False
|
||||
suspendedFilename = baseDir + '/accounts/suspended.txt'
|
||||
if os.path.isfile(suspendedFilename):
|
||||
with open(suspendedFilename, "r") as f:
|
||||
suspendedStr = f.read()
|
||||
infoForm += '<div class="container">'
|
||||
infoForm += ' <br><b>' + \
|
||||
translate['Suspended accounts'] + '</b>'
|
||||
infoForm += ' <br>' + \
|
||||
translate['These are currently suspended']
|
||||
infoForm += \
|
||||
' <textarea id="message" ' + \
|
||||
'name="suspended" style="height:200px">' + \
|
||||
suspendedStr + '</textarea>'
|
||||
infoForm += '</div>'
|
||||
infoShown = True
|
||||
|
||||
blockingFilename = baseDir + '/accounts/blocking.txt'
|
||||
if os.path.isfile(blockingFilename):
|
||||
with open(blockingFilename, "r") as f:
|
||||
blockedStr = f.read()
|
||||
infoForm += '<div class="container">'
|
||||
infoForm += \
|
||||
' <br><b>' + \
|
||||
translate['Blocked accounts and hashtags'] + '</b>'
|
||||
infoForm += \
|
||||
' <br>' + \
|
||||
translate[msgStr1]
|
||||
infoForm += \
|
||||
' <textarea id="message" ' + \
|
||||
'name="blocked" style="height:700px">' + \
|
||||
blockedStr + '</textarea>'
|
||||
infoForm += '</div>'
|
||||
infoShown = True
|
||||
if not infoShown:
|
||||
infoForm += \
|
||||
'<center><p>' + \
|
||||
translate[msgStr2] + \
|
||||
'</p></center>'
|
||||
infoForm += htmlFooter()
|
||||
return infoForm
|
|
@ -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,
|
||||
|
@ -202,9 +203,13 @@ def htmlPersonOptions(cssCache: {}, translate: {}, baseDir: str,
|
|||
optionsStr += checkboxStr
|
||||
|
||||
optionsStr += optionsLinkStr
|
||||
backPath = '/'
|
||||
if nickname:
|
||||
backPath = '/users/' + nickname + '/' + defaultTimeline
|
||||
optionsStr += \
|
||||
' <a href="/"><button type="button" class="buttonIcon" ' + \
|
||||
'name="submitBack">' + translate['Go Back'] + '</button></a>'
|
||||
' <a href="' + backPath + '"><button type="button" ' + \
|
||||
'class="buttonIcon" name="submitBack">' + translate['Go Back'] + \
|
||||
'</button></a>'
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submitView">' + \
|
||||
translate['View'] + '</button>'
|
||||
|
|
|
@ -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 += '<div><center>\n'
|
||||
suspendedForm += ' <p class="screentitle">Account Suspended</p>\n'
|
||||
suspendedForm += ' <p>See <a href="/terms">Terms of Service</a></p>\n'
|
||||
suspendedForm += '</center></div>\n'
|
||||
suspendedForm += htmlFooter()
|
||||
return suspendedForm
|
1221
webapp_timeline.py
1221
webapp_timeline.py
File diff suppressed because it is too large
Load Diff
|
@ -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 += '<div class="container">' + TOSText + '</div>\n'
|
||||
if adminNickname:
|
||||
adminActor = httpPrefix + '://' + domainFull + \
|
||||
'/users/' + adminNickname
|
||||
TOSForm += \
|
||||
'<div class="container"><center>\n' + \
|
||||
'<p class="administeredby">Administered by <a href="' + \
|
||||
adminActor + '">' + adminNickname + '</a></p>\n' + \
|
||||
'</center></div>\n'
|
||||
TOSForm += htmlFooter()
|
||||
return TOSForm
|
|
@ -437,6 +437,25 @@ def htmlHeader(cssFilename: str, css: str, lang='en') -> str:
|
|||
return htmlStr
|
||||
|
||||
|
||||
def htmlHeaderWithExternalStyle(cssFilename: str, css: str, lang='en') -> str:
|
||||
htmlStr = '<!DOCTYPE html>\n'
|
||||
htmlStr += '<html lang="' + lang + '">\n'
|
||||
htmlStr += ' <head>\n'
|
||||
htmlStr += ' <meta charset="utf-8">\n'
|
||||
fontName, fontFormat = getFontFromCss(css)
|
||||
if fontName:
|
||||
htmlStr += ' <link rel="preload" as="font" type="' + \
|
||||
fontFormat + '" href="' + fontName + '" crossorigin>\n'
|
||||
cssFile = cssFilename.split('/')[-1]
|
||||
htmlStr += ' <link rel="stylesheet" href="' + cssFile + '">\n'
|
||||
htmlStr += ' <link rel="manifest" href="/manifest.json">\n'
|
||||
htmlStr += ' <meta name="theme-color" content="grey">\n'
|
||||
htmlStr += ' <title>Epicyon</title>\n'
|
||||
htmlStr += ' </head>\n'
|
||||
htmlStr += ' <body>\n'
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlFooter() -> str:
|
||||
htmlStr = ' </body>\n'
|
||||
htmlStr += '</html>\n'
|
||||
|
|
|
@ -1168,7 +1168,7 @@
|
|||
<th><a href="img/screenshot_hacker.jpg"><img width="90%" src="img/screenshot_hacker.jpg" alt="hacker theme profile page" /></a></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><a href="img/screenshot_indymedia.jpg"><img width="120%" src="img/screenshots.jpg" alt="various screenshots" /></a></th>
|
||||
<th><a href="img/screenshot_starlight.jpg"><img width="120%" src="img/screenshot_starlight.jpg" alt="various screenshots" /></a></th>
|
||||
<th><img width="50%" src="img/mobile.jpg" alt="mobile screenshot" /></th>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1199,9 +1199,8 @@
|
|||
|
||||
<p class="siteheader">Federated Blogging</p>
|
||||
<img width="10%" src="img/icons/rss.png" alt="RSS 2.0"/>
|
||||
<img width="10%" src="img/icons/rss3.png" alt="RSS 3.0"/>
|
||||
<p class="intro">
|
||||
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 <a href="http://r3r.sourceforge.net/rss3-spec.xhtml">3.0</a> 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.
|
||||
</p>
|
||||
|
||||
<p class="siteheader">International and Customizable</p>
|
||||
|
|
Loading…
Reference in New Issue