BDServer/modules/Render.py
2024-12-06 16:28:00 +02:00

3082 lines
87 KiB
Python

# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import json
import time
import email
import random
import hashlib
import urllib.parse
from datetime import datetime
from modules import Set
from modules import API
from modules import markdown
from modules.Common import *
KnownCookies = []
ProbablyHumanCookies = []
RecentArticles = {}
RefferedArticles = {}
ProblematicRefreshes = []
def guess_type(path):
if "/json/" in path or ".json" in path:
return "application/json"
if "/css" in path or ".css" in path:
return "text/css"
if "/rss" in path or ".rss" in path:
return "application/rss+xml"
if "/icon" in path or path.endswith(".png"):
return "image/png"
if path.endswith("jpg"):
return "image/jpg"
return "text/html"
def headers(server, code):
# Basic cookie for logins to work
server.send_response(code)
server.send_header("Content-type", guess_type(server.path))
if not server.cookie:
cookie = RandString(200)
KnownCookies.append(cookie)
server.send_header("Set-Cookie", "temp_id="+cookie)
server.end_headers()
def head(title="", description="", image="", config={}, author=""):
if image.startswith("/"): image = config.get("url","")+image
favicon = config.get("favicon", "/icon/internet")
html = """
<head>
<!-- The head. Part of the HTML code where metadata about the page is storred.
Including the metadata readable by social media websites, when generating link
previwews, which are called mata-tags in HTML. -->
<meta charset="utf-8">
<title>"""+title+"""</title> <!-- Title in the browser tab -->
<link media="all" href="/css" type="text/css" rel="stylesheet" /> <!-- CSS theme link -->
<link rel="icon" href=\""""+favicon+""""> <!-- Tiny image in the tab -->
<!-- Now meta tags for social media -->
<meta property="og:site_name" content=\""""+config.get("title", "My Website")+"""">
<meta property="og:title" content=\""""+Safe(title)+"""">
<meta property="og:description" content=\""""+Safe(description)+"""">
<meta property="og:image" content=\""""+image+"""">
"""
# Author tags.
if author:
account = accounts().get(author, {})
name = account.get("title", account)
mastodon = account.get("mastodon", "")
try:
if "/" in mastodon:
mastodon = mastodon.replace("https://", "").replace("http://", "")
mastodon = mastodon.split("/")[1]+"@"+mastodon.split("/")[0]
except:
pass
if mastodon:
html = html + """
<meta name=article:author content=\""""+name+"""">
<meta name=fediverse:creator content=\""""+mastodon+"""">
"""
# Tor tags.
tor = config.get("tor", "")
if tor:
if not tor.startswith("http://"): tor = "http://"+tor
html = html + '<meta http-equiv="onion-location" content="'+tor+'" />'
html = html + """
<!-- This meta tag is needed for pretty rendering on phones -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
"""
return html
def send(server, html, code):
# Add headers
headers(server, code)
server.wfile.write(html.encode("utf-8"))
def tabs():
folder = Set.Folder()+"/tabs"
tabs = {}
for tab in sorted(list(os.walk(folder))[0][1]):
try:
with open(folder+"/"+tab+"/config.json") as o:
data = json.load(o)
tabs[tab] = data
except Exception as e:
print(e)
pass
return tabs
def accounts():
folder = Set.Folder()+"/accounts"
accounts = {}
for account in sorted(list(os.walk(folder))[0][2]):
try:
with open(folder+"/"+account) as o:
data = json.load(o)
data["username"] = account.replace(".json","")
accounts[account.replace(".json","")] = data
except Exception as e:
print(e)
pass
return accounts
def validate(cookie):
Accounts = accounts()
for account in Accounts:
if cookie in Accounts[account].get("sessions", []):
return Accounts[account]
return {}
def moderates(moderator, user):
Accounts = accounts()
if moderator not in Accounts:
return False
if user not in Accounts:
return True
if moderator == user:
return True
if rank(moderator, Accounts) < rank(user, Accounts):
return True
def rank(account, Accounts=None):
if not Accounts:
Accounts = accounts()
if account not in Accounts:
return 1000000
if not Accounts[account].get("invited_by") or Accounts[account].get("invited_by") == account:
return 0
return 1 + rank(Accounts[account].get("invited_by"), Accounts)
def editsIn(account, tab):
# Determents whether the user
# can edit an article.
Accounts = accounts()
# If the user is not registered
# per cannot edit anything.
if account not in Accounts:
return False
# If the user is the owner of the
# site, per can edit everything.
if rank(account, Accounts) == 0:
return True
# Not all users can edit in all
# tabs.
user = Accounts[account]
if tab in user.get("editsIn", []):
return True
return False
def articles(tab):
folder = Set.Folder()+"/tabs/"+tab
articles = {}
for article in list(os.walk(folder))[0][1]:
try:
with open(folder+"/"+article+"/metadata.json") as o:
data = json.load(o)
data["tab"] = tab
data["url"] = "/"+tab+"/"+article
articles[article] = data
except Exception as e:
print(e)
pass
# Sorting articles based on timestamp
articles = {k:articles[k] for k in sorted(articles, key=lambda y: articles[y]["timestamp"], reverse=True)}
return articles
def allArticles():
articles = {}
f = Set.Folder()
for tab in list(os.walk(f+"/tabs/"))[0][1]:
folder = f+"/tabs/"+tab
for article in list(os.walk(folder))[0][1]:
try:
with open(folder+"/"+article+"/metadata.json") as o:
data = json.load(o)
data["tab"] = tab
data["url"] = "/"+tab+"/"+article
articles[data["url"]] = data
except Exception as e:
print(e)
pass
# Sorting articles based on timestamp
articles = {k:articles[k] for k in sorted(articles, key=lambda y: articles[y]["timestamp"], reverse=True)}
return articles
def randomArticles():
articles = {}
f = Set.Folder()
for tab in list(os.walk(f+"/tabs/"))[0][1]:
folder = f+"/tabs/"+tab
for article in list(os.walk(folder))[0][1]:
try:
with open(folder+"/"+article+"/metadata.json") as o:
data = json.load(o)
data["tab"] = tab
data["url"] = "/"+tab+"/"+article
articles[article] = data
except Exception as e:
print(e)
pass
# Randomizing Articles.
newarticles = {}
while articles:
article = random.choice(list(articles.keys()))
newarticles[article] = articles.pop(article)
return newarticles
def suggestedArticles(cookie, random=False):
if not random:
articles = allArticles()
else:
articles = randomArticles()
# Suggesting unread articles.
newarticles = {}
move = []
for article in articles:
if cookie not in articles[article].get("views", {}).get("viewers", []):
move.append(article)
for article in move:
newarticles[article] = articles[article]
for article in articles:
if article not in move:
newarticles[article] = articles[article]
return newarticles
def previewsToSize(text):
# Calculates roughly how many previews to fit any
# given article.
# A thousand character article is about 4 articles.
return len(text)/2200
###
def MainPage(server):
# Reading config
config = Set.Load()
# Generating <head>
html = head(title = config.get("title", "Website"),
description = config.get("description", "Description"),
config = config
)
html = html + LoginButton(server)
html = html + """
<br>
<br>
<center>
<img src=\""""+config.get("favicon", "icon/internet")+"""" alt="[LOGO]" style="height:150px;vertical-align: middle">
<br>
<br>
<h2>"""+config.get("title", "My Website")+"""</h2>
"""+config.get("tagline", "")+"""
<br>
<br>
<form action="/search">
<input name="text" class="button" title="Search text" placeholder="Search..." >
<button title="Start the search" class="button" type="submit">
<img alt="[icon search]" style="vertical-align: middle" src="/icon/search">
Search
</button>
</form>
<br>
"""
Tabs = tabs()
for tab in Tabs:
html = html + Button(Tabs[tab].get("title", tab),
"/"+tab,
Tabs[tab].get("icon", "folder"))
html = html + "</center>"
# Trending articles
html = html + '<div class="flexity">'
trends = suggestedArticles(server.cookie)
Buffer = 20
for n, article in enumerate(trends):
if n >= Buffer: break
article = trends[article]
html = html + ArticlePreview(article, Tabs, server.cookie)
html = html + '</div>'
html = html + Footer()
html = html + LoginButton(server)
send(server, html, 200)
def ListPage(server, tab):
user = validate(server.cookie)
Tabs = tabs()
Articles = articles(tab)
config = Set.Load()
try: page = int(server.parsed.get("page", ["0"])[0])
except Exception as e:
print(e)
page = 0
Buffer = 16
From = Buffer*page
To = From+Buffer
# Generating <head>
html = head(title = Tabs.get(tab, {}).get("title", tab),
description = "",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
if editsIn(user.get("username", ""), tab):
html = html + Button("New Post", "/editor?tab="+tab, icon="new")
# Scroll thingie
if len(Articles) > Buffer:
if page > 0:
html = html + Button(str(page-1), tab+"?page="+str(page-1), "left")
html = html + '<div class="button">'+str(page)+'</div>'
if To < len(Articles)-1:
html = html + Button(str(page+1), tab+"?page="+str(page+1), "right")
html = html + """
<br>
<br>
<!-- Article previews are neatly positioned into a grid here -->
<div class="flexity">
"""
rendered = 0
for n, article in enumerate(Articles):
if n < From: continue
if n >= To: break
html = html + ArticlePreview(Articles[article], Tabs, server.cookie)
rendered += 1
html = html + '</div><br>'
# Bottom pannel for large dialogs
if rendered >= Buffer/2:
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
# Scroll thingie
if len(Articles) > Buffer:
if page > 0:
html = html + Button(str(page-1), tab+"?page="+str(page-1), "left")
html = html + '<div class="button">'+str(page)+'</div>'
if To < len(Articles)-1:
html = html + Button(str(page+1), tab+"?page="+str(page+1), "right")
html = html + LoginButton(server)
send(server, html, 200)
def ArticlePage(server, url):
user = validate(server.cookie)
referrer = server.headers.get("referer", "")
if url.endswith(".md"):
url = url.replace(".md", "")
# Recording when was the last time
# the article loaded.
RecentArticles["/"+url] = time.time()
RefferedArticles["/"+url] = referrer
config = Set.Load()
tab, article, *rest = url.split("/")
Tabs = tabs()
Articles = articles(tab)
f = Set.Folder()
# Generating <head>
html = head(title = Articles.get(article, {}).get("title", article),
description = Articles.get(article, {}).get("description", ""),
image = Articles.get(article, {}).get("thumbnail", ""),
config = config,
author = Articles.get(article, {}).get("author")
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + Button(Tabs.get(tab, {}).get("title", tab), "/"+tab, Tabs.get(tab, {}).get("icon", "folder"))
# The article itself
html = html + '<div class="middle_section_article">'
html = html + '<div class="dark_box">'
# Edit button
if editsIn(user.get("username"), tab) and moderates(user.get("username"), Articles.get(article, {}).get("author", "")):
html = html + '<div class="login_fixed">'
html = html + Button("Edit", "/editor?tab="+tab+"&name="+article, icon="edit")
html = html + '</div><br><br><br>'
html = html +"<center><h1>"+Articles.get(article, {}).get("title", article)+"</h1></center>"
# Page author
author = Articles.get(article, {}).get("author", "")
if author:
html = html + '<center>'+User( author )+'</center>'
timestamp = Articles.get(article, {}).get("timestamp", "")
arttime = str(datetime.fromtimestamp(timestamp).strftime("%B %d, %Y"))
html = html + "<br><small><center>"+str(arttime)+"</center></small>"
# Page views
# Views are calculated using an iframe which only loads when the page is
# rendered in a browser. It is also looking at whether the same cookie renders
# the same page, to avoid double counting from the same person.
# The iframe itself shows a graph of views.
views = str(Articles.get(article, {}).get("views", {}).get("amount", 0))
html = html + '<br><center><details><summary class="button">👁 '+views+'</summary>'
html = html + """
<iframe width="100%" height="500px" style="border:none; border-radius:25px;" src="/graph"""+server.path+""""></iframe>
<br><br>"""
referrers = Articles.get(article, {}).get("views", {}).get("referrers", {})
if referrers:
for referrer in referrers:
# Filtering out probably bad links.
if ( "https://" in referrer and ".onion" not in referrer ) or rank(user.get("username", "")) == 0:
html = html + Safe(referrer) + " : 👁 "+str(referrers[referrer]) + "<br>\n"
html = html + """
</details>
"""
html = html + '</div>'
# Petition
petition = Articles.get(article, {}).get("petition", "")
if petition:
petition_error = False
if petition.get("api"):
try:
API.Petition(Articles[article])
except:
petition_error = True
html = html + '<div class="dark_box"> <center>'
html = html + '<h2>'
html = html + 'Petition</h2></center>'
try:
frac = petition.get("signed", 0) / int(petition.get("goal", 1))
except:
frac = 0
html = html + ProgressBar(frac)
html = html + "<br><center>"+str(petition.get("signed", 0))+" / "+Safe(str(petition.get("goal", 1)))+" Signatures"
# Last update
if petition.get("api"):
lastUpdate = petition.get("api", {}).get("timestamp", {})
nowTime = time.time()
html = html + '<br><br><small>Last updated: '+TimeDifference(lastUpdate, nowTime)+'</small><br><br>'
if not petition.get("api"):
html = html + """
<details>
<summary class="button">
<img alt="[icon petition]" style="vertical-align: middle" src="/icon/petition">
Sign
</summary>
<form action="/sign_petition">
<input type="hidden" name="article" value='/"""+tab+"/"+article+"""'>
<img style="vertical-align: middle" src="/icon/frase">
<input class="button" style="width:90%" required maxlength="200" name="email" placeholder="Email">
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/ok">
Submit
</button>
</form>
</details>
"""
else:
html = html + """
<details>
<summary class="button">
<img alt="[icon petition]" style="vertical-align: middle" src="/icon/petition">
Sign
</summary>
<center>
<br>
This petition is signed by increasing the number of<br>
<i>"""+petition.get("api", {}).get("title", "")+"""</i>
<br><br>
"""+Button("Continue", petition.get("api", {}).get("link", ""), "ok")+"""
</center>
"""
html = html + '</div>'
# License
License = Articles.get(article, {}).get("license", "")
if License:
html = html + '<div class="dark_box"> <center>'
html = html + 'License:<br><img style="vertical-align: middle" src="/icon/'+License+'"> '
html = html + '<a href="'+Licenses.get(License, {}).get("link")+'">'
html = html + Licenses.get(License, {}).get("name")+'</a>'
html = html + '</center></div>'
# Audio recording of the article
recording = Articles.get(article, {}).get("recording", "")
if recording:
html = html + '<div class="dark_box"> <center>'
html = html + '<b>If you are going to skim, better listen to it instead.</b><br><br>'
html = html + '<audio controls="controls" style="min-width:100%;" src="'+recording+'"></audio>'
html = html + '<br><br></center></div>'
html = html + '<div class="dark_box">'
html = html + markdown.convert(f+"/tabs/"+tab+"/"+article+"/text.md")
html = html + '</div>'
# RSS
rsslink = "https://"+config.get("domain", "")+"/rss"
authorrsslink = rsslink+"?author="+Safe(Articles.get(article, {}).get("author",""))
html = html + """
<div class="dark_box">
<center>
<details>
<summary class="button">
<img style="vertical-align: middle" src="/icon/rss">
Subscribe RSS
</summary>
<br>
<img style="vertical-align: middle" src="/icon/user">
<input class="button" style="width:50%" value='"""+authorrsslink+"""'>
"""+Button("Author", authorrsslink, "link")+"""
<br>
<img style="vertical-align: middle" src="/icon/internet">
<input class="button" style="width:50%" value='"""+rsslink+"""'>
"""+Button("Website", rsslink, "link")+"""
</details>
</center>
</div>
"""
# Comments
html = html + CommentInput(server, url)
comments = Articles.get(article, {}).get("comments", {}).get("comments", [])
commentsTextLength = 0
comment_edit = server.parsed.get("comment_edit", [""])[0]
if comments:
for n, comment in enumerate(comments):
if str(n) == comment_edit and moderates(user.get("username"), comment.get("username")):
html = html + CommentEditInput(server, comment, url, n, user)
else:
html = html + Comment(comment, url, n, user)
# Needed to extend the suggestion for pages with many comments
commentsTextLength += previewsToSize(comment.get("text", ""))
# Requests
requests = Articles.get(article, {}).get("comments", {}).get("requests", [])
if requests:
for n, comment in enumerate(requests):
if comment.get("cookie") == server.cookie:
html = html + Comment(comment, url, n, user, request=True)
elif moderates(user.get("username"), comment.get("username")):
html = html + CommentEditInput(server, comment, url, n, user, request=str(n))
html = html + '</div>'
# Thumbnail and suggestions
html = html + '<div class="checklist_section_article">'
thumbnail = Articles.get(article, {}).get("thumbnail")
if thumbnail:
html = html + '<div class="article_box"><br>'
html = html + '<img style="min-width:100%; width:100%" src="'+thumbnail+'">'
html = html + '<br><br></div>'
suggestions = suggestedArticles(server.cookie, random=True)
toomuch = previewsToSize(open(f+"/tabs/"+tab+"/"+article+"/text.md").read())
toomuch += commentsTextLength
for n, title in enumerate(suggestions):
if server.path in suggestions[title].get("url") :
continue
if n > toomuch:
break
article = suggestions[title]
html = html + ArticlePreview(article, Tabs, server.cookie)
html = html + ""
html = html + LoginButton(server)
send(server, html, 200)
def AccountPage(server, account):
user = validate(server.cookie)
config = Set.Load()
Accounts = accounts()
Tabs = tabs()
Articles = allArticles()
f = Set.Folder()
# Generating <head>
html = head(title = Safe(Accounts.get(account, {}).get("title", account)),
description = Safe(Accounts.get(account, {}).get("bio" , "")),
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
# Name and bio
html = html + '<div class="middle_section_article">'
html = html + '<div class="dark_box">'
html = html +"<center><h1>"+Accounts.get(account, {}).get("title", account)+"</h1>"
# Rank
Rank = rank(account)
html = html + Button("Rank "+str(Rank), "", icon="analytics")
html = html + '</center>'
# Protecting emails and stuff from scrubbers
if server.cookie in ProbablyHumanCookies and server.cookie in KnownCookies:
# Website
website = Safe(Accounts.get(account, {}).get("website" , ""))
if website:
webtitle = website.replace("https://", "").replace("http://", "")
if not website.startswith("http"): website = "http://"+website
html = html + '<center>'
html = html + '<img style="vertical-align: middle" src="/icon/internet">'
html = html + '<a href="'+website+'"> '+webtitle+'</a>'
html = html + '</center>'
# Email
email = Safe(Accounts.get(account, {}).get("email" , ""))
if email:
html = html + '<center>'
html = html + '<img style="vertical-align: middle" src="/icon/frase">'
html = html + '<a href="mailto:'+email+'"> '+email+'</a>'
if Accounts.get(account, {}).get("email_verified"):
html = html + '<img title="Email Verified!" style="vertical-align: middle" src="/icon/ok">'
html = html + '</center>'
# Mastodon
mastodon = Safe(Accounts.get(account, {}).get("mastodon" , ""))
if mastodon:
# It could be mastodon url and not handle.
try:
if "/" in mastodon:
mastodon = mastodon.replace("https://", "").replace("http://", "")
mastodon = mastodon.split("/")[1]+"@"+mastodon.split("/")[0]
if not mastodon.startswith("@"): mastodon = "@"+mastodon
mastolink = "https://"+mastodon[1:].split("@")[1]+"/@"+mastodon[1:].split("@")[0]
html = html + '<center>'
html = html + '<img style="vertical-align: middle" src="/icon/mastodon">'
html = html + '<a href="'+mastolink+'"> '+mastodon+'</a>'
html = html + '</center>'
except:
pass
# Matrix
matrix = Safe(Accounts.get(account, {}).get("matrix" , ""))
if matrix:
# Matrix could be the matrix.to link
if "/" in matrix:
matrix = matrix[matrix.rfind("/")+1:]
matrixlink = "https://matrix.to/#/"+matrix
html = html + '<center>'
html = html + '<img style="vertical-align: middle" src="/icon/element">'
html = html + '<a href="'+matrixlink+'"> '+matrix+'</a>'
html = html + '</center>'
if any((website, email, mastodon, matrix)):
html = html + '<br>'
html = html + '</div>'
invited_by = Accounts.get(account, {}).get("invited_by", "")
if invited_by:
html = html + '<div class="dark_box">'
html = html +"<center>Invited by:<br>"+User(invited_by)+"</center>"
html = html + '</div>'
bio = Safe(Accounts.get(account, {}).get("bio" , ""))
if bio:
html = html + '<div class="dark_box">'
html = html + markdown.convert(bio, False)+'<br>'
html = html + '</div>'
# Validating this account
validates = []
if user.get("username", "") != account and moderates(user.get("username", ""), account):
for tab in Tabs:
if editsIn(user.get("username", ""), tab):
validates.append(tab)
if validates:
html = html + '<div class="dark_box"><center>'
html = html + "You can grant publication rights to this account.<br>"
html = html + '</center><form action="/grant_publication_rights">'
html = html + '<input type="hidden" name="account" value="'+account+'">'
for tab in validates:
checked = ""
if editsIn(account, tab):
checked = 'checked=""'
html = html + '<div class="button">'
html = html + '<input type="checkbox" '+checked+' name="'+tab+'">' + Tabs[tab].get("title", tab)
html = html + '</div>'
html = html + """
<br>
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/ok">
Apply
</button>
<br>
"""
html = html + '</div>'
# Posts by this account
html = html + '<div class="flexity">'
for article in Articles:
if Articles[article].get("author") != account:
continue
html = html + ArticlePreview(Articles[article], Tabs, server.cookie)
html = html + '</div>'
html = html + '</div>'
# Thumbnail and suggestions
html = html + '<div class="checklist_section_article">'
avatar = Safe(Accounts.get(account, {}).get("avatar" , ""))
if avatar:
html = html + '<div class="article_box"><br>'
html = html + '<img style="min-width:100%; width:100%" src="'+avatar+'">'
html = html + '<br><br></div>'
# Invited
invited = Accounts.get(account, {}).get("invited", [])
if invited:
html = html + '<center><div class="button">Invited:</div></center>'
for username in invited:
if username in Accounts:
html = html + '<div class="dark_box">'
html = html + '<center>' + User(username) + '</center>\n'
html = html + '</div>'
html = html + LoginButton(server)
send(server, html, 200)
def LoginPage(server):
config = Set.Load()
Accounts = accounts()
f = Set.Folder()
wrongname = server.parsed.get("wrong", [""])[0]
redirect = server.parsed.get("redirect", [""])[0]
# Generating <head>
html = head(title = "Login",
description = "Login",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + '<center>'
if wrongname:
html = html + '\n<br>Wrong Username / Password<br>\n'
html = html + """
<div class="middle_section_article">
<div class="dark_box">
<form action="do_login" method="post">
<img style="vertical-align: middle" src="/icon/user">
<input class="button" style="width:90%" maxlength="500" id="user_name" required="" name="user_name" pattern="[A-Za-z0-9\.\-\_\]*" placeholder="Username..."></input>
<br>
<img style="vertical-align: middle" src="/icon/lock">
<input class="button" style="width:90%" maxlength="500" id="password" type="password" required="" name="password" placeholder="Password..."></input><br>
<div class="button">
<input type="checkbox" name="logout">
<label>Log Out Other Sessions</label>
</div>
<br><br>
"""
if redirect:
html = html + '<input type="hidden" name="redirect" value="'+Safe(redirect)+'">'
html = html + """
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/unlock">
Login
</button>
</form>
</div></div>
<div class="checklist_section_article">
<div class="dark_box">
Don't have an account? <br>
"""
html = html + Button("Register", "/register", icon="user_new")
send(server, html, 200)
def RegisterPage(server):
user = validate(server.cookie)
config = Set.Load()
Accounts = accounts()
f = Set.Folder()
code = server.parsed.get("code", [""])[0]
userexists = server.parsed.get("userexists", [""])[0]
wrongcode = server.parsed.get("wrongcode", [""])[0]
# Generating <head>
html = head(title = "Register",
description = "Register",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + '<center>'
html = html + """
<div class="middle_section_article">
<div class="dark_box">
<form action="do_register" method="post">
"""
if wrongcode:
html = html + "Invalid Invite Code.<br><br>"
if not code and not user:
html = html + """
<img style="vertical-align: middle" src="/icon/user_link">
<input class="button" style="width:90%" maxlength="500" id="code" required="" name="code" placeholder="Invite code..."></input>
"""
username = ""
else:
for account in Accounts:
if code in Accounts[account].get("invite_codes", []):
username = Simplify(Accounts[account]["invite_codes"][code], "file")
html = html + '<center>Invited by:<br><br>'
html = html + User(account)+'<br>'
html = html + '<input type="hidden" name="code" value="'+code+'">'
break
if not user:
if userexists:
html = html + "Username is taken.<br><br>"
html = html + """<img style="vertical-align: middle" src="/icon/user">
<input class="button" style="width:90%" maxlength="500" id="user_name" required="" name="user_name" pattern="[A-Za-z0-9\.\-\_\]*" placeholder="Username..." value=\""""+username+""""></input>
<br>
<img style="vertical-align: middle" src="/icon/lock">
<input class="button" style="width:90%" maxlength="500" id="password" type="password" required="" name="password" placeholder="Password..."></input><br>
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/user_new">
Register
</button>
"""
elif code:
html = html + "Share this page with those you invited."
else:
html = html + "<br>You already registered and logged in."
html = html + """
</form>
</div></div>
<div class="checklist_section_article">
<div class="dark_box">
Have an account? <br>
"""
html = html + Button("Login", "/login", icon="unlock")
send(server, html, 200)
def SettingsPage(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
config = Set.Load()
# Generating <head>
html = head(title = "Settings",
description = "Settings",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + Button("Public Profile", "/account/"+user.get("username", ""), image="/pictures/monkey.png")
# Main settings
html = html + """
<div class="middle_section_article">
<div class="dark_box">
<center><h2>Public Info</h2></center>
<form action="update_account">
<img style="vertical-align: middle" src="/icon/user">
<input class="button" style="width:90%" maxlength="200" name="title" placeholder="Visible Name" value='"""+user.get("title", "")+"""'>
<img style="vertical-align: middle" src="/icon/image_link">
<input class="button" style="width:90%" maxlength="200" name="avatar" placeholder="Link To Profile Picture" value='"""+user.get("avatar", "")+"""'>
<br>
<center>
<img style="vertical-align: middle" src="/icon/scene">
<b>Bio</b><br>
<textarea class="toot" rows="10" style="width:95%" maxlength="10000" name="bio">"""+Safe(user.get("bio", ""))+"""</textarea>
</center>
<img style="vertical-align: middle" src="/icon/internet">
<input class="button" style="width:90%" maxlength="200" name="website" placeholder="Personal Website" value='"""+user.get("website", "")+"""'>
<img style="vertical-align: middle" src="/icon/frase">
<input class="button" style="width:90%" maxlength="200" name="email" placeholder="Publicly Visible Email" value='"""+user.get("email", "")+"""'>
<img style="vertical-align: middle" src="/icon/mastodon">
<input class="button" style="width:90%" maxlength="200" name="mastodon" placeholder="Mastodon Handle" value='"""+user.get("mastodon", "")+"""'>
<img style="vertical-align: middle" src="/icon/element">
<input class="button" style="width:90%" maxlength="200" name="matrix" placeholder="Matrix Handle" value='"""+user.get("matrix", "")+"""'>
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/ok">
Save
</button>
</form>
</div>
"""
if user.get("email"):
if not user.get("email_verified"):
html = html + """
<div class="dark_box" id="email">
<center><h2>Email Settings</h2>
<br>
Email """+Safe(user.get("email"))+""" is not verified.
<br><br>
"""+Button("Verify", "/email_verify", "ok")+"""
</center>
</div>
"""
else:
html = html + """
<div class="dark_box" id="email">
<form action="email_update">
<center><h2>Email Settings</h2>
<br>
Email """+Safe(user.get("email"))+""" is verified!
<br><br>
</center>
</form>
</div>
"""
# Current Logged in Sessions
sessions = user.get("sessions", {})
html = html + '<div class="dark_box" id="sessions">'
html = html + '<center><h2>Active Sessions</h2>'
for cookie in sessions:
session = sessions[cookie]
CancelButton = Button("Log Out", "/log_out?cookie="+cookie, icon="cancel")
if server.cookie == cookie:
html = html + '<br><img title="This Session" style="vertical-align: middle" src="/icon/checked">'
else:
html = html + '<br><img title="Other Browser" style="vertical-align: middle" src="/icon/unchecked">'
html = html + '<input class="button" style="width:50%" value="' + session + '">'+CancelButton
html = html + '<br><br>'
html = html + '</center>'
html = html + '</div>'
# Invites and Invite codes
invite_codes = user.get("invite_codes", {})
html = html + '<div class="dark_box" id="invites">'
html = html + '<center><h2>Invites</h2></center>'
for code in invite_codes:
nick = invite_codes[code]
Open = ""
if code == server.parsed.get("code", [""])[0]:
Open = "open"
html = html + '<details '+Open+' class="dark_box"><summary id="invite_'+code+'" class="button">'
html = html + '<img style="vertical-align: middle" src="/icon/user">'+nick
html = html + '</summary><br>'
html = html + '<img style="vertical-align: middle" src="/icon/user_link">'
html = html + '<input class="button" style="width:90%" value="' + code + '">'
html = html + Button("Share Link", "/register?code="+code, icon="link")
html = html + Button("Cancel", "/cancel_invite?code="+code, icon="cancel")
html = html + '</details>'
html = html + """
<form action="/create_invite">
<input name="nick" class="button" required="" style="width:50%" placeholder="Name of the person you invite">
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/user_new">
Invite
</button>
</form>
</div>
<div class="dark_box">
<center><h2>Change Password</h2></center>
<form action="change_password" method="post">
<img style="vertical-align: middle" src="/icon/unlock">
<input class="button" style="width:90%" maxlength="200" required type="password" name="password" title="Old Password" placeholder="Old Password">
<img style="vertical-align: middle" src="/icon/lock">
<input class="button" style="width:90%" maxlength="200" required type="password" name="new_password" title="New Password" placeholder="New Password">
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/ok">
Change
</button>
</div>
</div>
"""
notifications = user.get("notifications","")
if notifications:
html = html + '<div class="checklist_section_article">'
for notification in notifications:
html = html + '<div class="article_box">'
html = html + '<br><a href="/read_notification?code='+notification.get("code", "")+'">'
html = html + notification.get("text", "Notification")
html = html + '</a><br><br>'
html = html + '</div>'
send(server, html, 200)
def EditorPage(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
config = Set.Load()
Tabs = tabs()
f = Set.Folder()
# Generating <head>
html = head(title = "Editor",
description = "Editor",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
tab = server.parsed.get("tab", [""])[0]
name = server.parsed.get("name", [""])[0]
if tab in Tabs:
Articles = articles(tab)
else:
Articles = {}
url = tab+"/"+name
article = Articles.get(name, {})
if article:
try:
text = open(f+"/tabs/"+tab+"/"+name+"/text.md")
text = text.read()
except:
text = ""
else:
text = ""
html = html + """
<div class="middle_section_article">
<div class="dark_box">
<form action="do_edit" method="post">
"""
if tab in Tabs:
html = html + '<img style="vertical-align: middle" src="/icon/' + Tabs.get(tab, {}).get("icon", "folder")+ '"> '
html = html + Tabs.get(tab, {}).get("title", tab)
html = html + '<br>'
html = html + '<input type="hidden" name="tab" value="'+tab+'">'
else:
html = html + """
<img style="vertical-align: middle" src="/icon/folder">
<input class="button" style="width:90%" required="" name="tab" placeholder="Category ( Tab )">
"""
if name:
html = html + '<input type="hidden" name="name" value="'+name.replace("'", "&apos;")+'">'
html = html + """
<img style="vertical-align: middle" src="/icon/scene">
<input class="button" style="width:90%" required="" name="title" placeholder="Title" value='"""+article.get("title", "").replace("'", "&apos;")+"""'>
<img style="vertical-align: middle" src="/icon/image_link">
<input class="button" style="width:90%" name="thumbnail" placeholder="Link To Thumbnail ( Optional )" value='"""+article.get("thumbnail", "").replace("'", "&apos;")+"""'>
<img style="vertical-align: middle" src="/icon/copy_file">
<input class="button" style="width:90%" list="Licenses" name="license" placeholder="License ( Optional )" value='"""+article.get("license", "").replace("'", "&apos;")+"""'>
<datalist id="Licenses">
"""
for l in Licenses:
html = html + '<option value="'+l+'">'+l+'</option>'
html = html + """
</datalist>
<img style="vertical-align: middle" src="/icon/mus">
<input class="button" style="width:90%" name="recording" placeholder="Link To Sound Recording ( Optional )" value='"""+article.get("recording", "").replace("'", "&apos;")+"""'>
"""
# Optional Petition
if rank(user.get("username","")) == 0:
petition = article.get("petition", {})
petition_goal = petition.get("goal", "")
petition_api = petition.get("api", {}).get("api", "")
petition_api_key = petition.get("api", {}).get("keys", [""])
petition_api_title = petition.get("api", {}).get("title", "")
petition_api_link = petition.get("api", {}).get("link", "")
if len(petition_api_key) == 1:
petition_api_key = petition_api_key[0]
else:
key = petition_api_key[0]
for n, i in petition_api_key:
if n != 0:
key = key + "/" + i
petition_api_key = key
html = html + """
<details>
<summary class="button">
<img alt="[icon petition]" style="vertical-align: middle" src="/icon/petition">
Petition
</summary>
<div class="dark_box">
<br>
<img alt="[icon ok]" style="vertical-align: middle" src="/icon/ok">
<input class="button" type="number" style="width:90%" name="petition_goal" placeholder="Goal ( How Many Signatures )" value='"""+str(petition_goal)+"""'>
<br>
<details>
<summary class="button">
<img alt="[icon analytics]" style="vertical-align: middle" src="/icon/analytics">
From API
</summary>
<br>
<img alt="[icon link]" style="vertical-align: middle" src="/icon/link">
<input class="button" style="width:90%" name="petition_api" placeholder="API to check ( should be JSON )" value='"""+petition_api+"""'>
<br>
<img alt="[icon checlist]" style="vertical-align: middle" src="/icon/checklist">
<input class="button" style="width:90%" name="petition_api_key" placeholder="Key To Look For ( split with / for nested deep data )" value='"""+petition_api_key+"""'>
<br>
<img alt="[icon scene]" style="vertical-align: middle" src="/icon/scene">
<input class="button" style="width:90%" name="petition_api_title" placeholder="Statistic's Name" value='"""+petition_api_title+"""'>
<br>
<img alt="[icon link]" style="vertical-align: middle" src="/icon/link">
<input class="button" style="width:90%" name="petition_api_link" placeholder="Action Link ( where people can increase the number in the API )" value='"""+petition_api_link+"""'>
<br>
</div>
</details>
"""
html = html + """
<br>
<center>
<textarea class="toot" rows="30" style="width:95%" required="" name="text" placeholder="Text of your post">"""+Safe(text)+"""</textarea>
<br>
<textarea class="toot" rows="5" style="width:95%" name="description" placeholder="Description ( Optional )">"""+article.get("description", "")+"""</textarea>
</center>
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/scene_new">
Publish
</button>
</div>
"""
html = html + LoginButton(server)
send(server, html, 200)
###
def Button(text, link, icon="", image=""):
html = """
<a class="button" href=\""""+link+"""">"""
if icon:
html = html + """
<img alt="[icon """+icon+"""]" src="/icon/"""+icon+"""" style="vertical-align: middle">"""
if image:
html = html + """
<img alt="[icon """+icon+"""]" src=\""""+image+"""" style="height:40px;vertical-align: middle">"""
html = html + """
"""+text+"""</a>
"""
return html
def ArticlePreview(article, Tabs, cookie=""):
html = """
<div class="article_box">
"""
url, tab = article.get("url", ""), article.get("tab","")
sup = ""
if cookie not in article.get("views", {}).get("viewers", ""):
sup = '<center><sup><b> &nbsp; Unread &nbsp; </b></sup></center><br>'
html = html + '<a href="'+url+'"><h1>'
html = html + '<img alt="[icon '+tab+']" src="/icon/'+Tabs.get(tab, {}).get("icon", "folder")+'" style="vertical-align: middle">'
html = html + article.get("title", "")+"</h1></a>"+sup+"\n"
if article.get("thumbnail"):
html = html + '<center><a href="'+url+'"><img alt="[thumbnail]" src="'+article["thumbnail"]+'"></a></center>'
petition = article.get("petition", "")
if petition:
try:
frac = petition.get("signed", 0) / int(petition.get("goal", 1))
except:
frac = 0
html = html + '<br>'+ProgressBar(frac)
author = article.get("author", "")
if author:
html = html + '<br><center>'+User( author )+'</center>'
views = str(article.get("views", {}).get("amount", 0))
try: comments = str(len(article.get("comments", {}).get("comments")))
except: comments = "0"
html = html +'<br><center> 👁 '+views+' 💬 '+comments+' </center>'
html = html + "<br>"+markdown.convert(article.get("description", ""), False)+"<br><br>"
html = html + '</div>\n'
return html
def Footer():
html = """
<center>
"""
html = html + Button("Powered with BDServer", "https://codeberg.org/blenderdumbass/BDServer/", "codeberg")
html = html + """
</center>
"""
return html
def User(username, stretch=False):
try:
with open(Set.Folder()+"/accounts/"+username+".json") as o:
account = json.load(o)
except:
account = {}
# We are doing a lot of reductions in case somebody sneaks html code.
avatar = Safe(account.get("avatar", ""))
if not avatar: avatar = "/pictures/monkey.png"
username = Safe(username)
title = Safe(account.get("title", username))
if account:
html = '<img alt="[avatar]" style="height:50px;vertical-align: middle" src="'+avatar+'">&nbsp;&nbsp;<a href="/account/'+username+'">'+title+'</a></center>\n'
else:
html = '<img alt="[avatar]" style="height:50px;vertical-align: middle" src="'+avatar+'">&nbsp;&nbsp;'+title+'</center>\n'
return html
def Graph(server, url):
if url.endswith(".md"):
url = url.replace(".md", "")
# If there are any values after ? in the path
# which means, that somebody is sending the old
# version of the graph link from the legacy code
# we should not count it as a view.
if "?" in server.path:
AccessDenied(server)
return
# Since /graph/ is used to count views
# we need the cookie to be generated and
# used by the user's browser before we load
# it, since a lot of people might just click
# the link once. In which case the entire page
# including graph loads before the cookie.
if not server.cookie and server.headers.get("user-agent", "") not in ProblematicRefreshes:
Redirect(server, server.path)
ProblematicRefreshes.append(server.headers.get("user-agent", ""))
return
if server.headers.get("user-agent", "") in ProblematicRefreshes:
ProblematicRefreshes.remove(server.headers.get("user-agent", ""))
# Sometimes scrapers try to load graph without
# loading the article first. We don't want to count
# it as a view.
if time.time()-20 > RecentArticles.get(url, 0):
print(consoleForm(server.cookie), "Article wasn't loaded, scrapers!")
AccessDenied(server)
return
user = validate(server.cookie)
# Store general analytics about which search engines were used.
# To get to this article.
referrer = RefferedArticles.get(url, "")
html = """
<head>
<link media="all" href="/css" type="text/css" rel="stylesheet" /> <!-- CSS theme link -->
</head>
<style>
html {
background-color: none;
background-image: none;
}
</style>
"""
try:
with open(Set.Folder()+"/tabs"+url+"/metadata.json") as o:
article = json.load(o)
except:
article = {}
dateformat = "%Y-%m-%d"
dates = article.get("views", {}).get("dates", {})
if dates:
largest = max(dates.values())
startdate = datetime.strptime(sorted(list(dates.keys()), reverse=True)[0], dateformat)
enddate = datetime.strptime(sorted(list(dates.keys()), reverse=True)[-1], dateformat)
alldays = int((startdate - enddate).days)
for n, date in enumerate(sorted(dates, reverse=True)):
amount = dates[date]
width = 100 / (alldays+1)
height = 60 * (amount / largest)
cd = datetime.strptime(date, dateformat)
nd = int((startdate - cd).days)
html = html + '<div style="top:'+str(60-height)+'%;right:'+str((nd)*(width))+'%; width:'+str(max(width-0.5,0.5))+'%; height:'+str(height)+'%" title="'+str(date)+' - '+str(amount)+' views" class="graph_line"></div>\n'
# Saving the view
cookies = [server.cookie]
if user:
# If user is logged in, we want to record
# per reading the article, on all pers
# sessions.
for cookie in user.get("sessions"):
if cookie not in cookies:
cookies.append(cookie)
for cookie in cookies:
if cookie and cookie not in article.get("views", {}).get("viewers", []):
article["views"]["amount"] += 1
article["views"]["viewers"].append(cookie)
if "referrers" not in article["views"]:
article["views"]["referrers"] = {}
if referrer and referrer not in article["views"]["referrers"]:
article["views"]["referrers"][referrer] = 0
if referrer:
article["views"]["referrers"][referrer] += 1
dates = article["views"]["dates"]
date = datetime.now().strftime(dateformat)
dates[date] = dates.get(date, 0) + 1
server.newview = True
with open(Set.Folder()+"/tabs"+url+"/metadata.json", "w") as save:
json.dump(article, save, indent=4)
# If the cookie got all the way here, it is probably loaded by a person
ProbablyHumanCookies.append(server.cookie)
send(server, html, 200)
def CommentInput(server, url):
user = validate(server.cookie)
html = """
<div class="dark_box" id="comments">
<br><center>
<form action="/comment">
<input type="hidden" id="url" name="url" value=\""""+url+"""">
"""
if not user:
html = html + '<img style="vertical-align: middle" src="/icon/user">'
html = html + '<input class="button" style="width:90%" maxlength="200" name="username" placeholder="Optional Nick Name">'
else:
html = html + User(user.get("username", ""))+'<center>'
html = html + """
<br>
<img style="vertical-align: middle" src="/icon/bug">
<input class="button" maxlength="200" style="width:90%" name="warning" placeholder="Optional Trigger Warning">
<textarea class="toot" rows="10" required="" style="width:95%" maxlength="10000" name="text">In my opinion </textarea>
<br>
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/frase">
Post
</button>
</form>
</center>
</div>
"""
return html
def CommentEditInput(server, comment, url, n=0, user=None, request=None):
Accounts = accounts()
html = '<div class="dark_box" id="comment_'+str(n)+'">'
html = html + """
<form action="/comment">
<input type="hidden" id="url" name="url" value=\""""+url+"""">
<input type="hidden" id="number" name="number" value=\""""+str(n)+"""">
"""
if request:
html = html + '<input type="hidden" id="request" name="request" value=\"'+request+'">'
html = html + '<br><center><sup><b> &nbsp; Pending Approval &nbsp; </b></sup></center><br>'
account = comment.get("username", "")
if account in accounts():
html = html + User(Safe(account))
elif account:
html = html + '<img style="vertical-align: middle" src="/icon/user">'
html = html + '<input class="button" style="width:90%" maxlength="200" name="username" placeholder="Optional Nick Name" value="'+account+'">'
warning = comment.get("warning", "")
html = html + """
<br>
<img style="vertical-align: middle" src="/icon/bug">
<input class="button" maxlength="200" style="width:90%" name="warning" placeholder="Optional Trigger Warning" value=\""""+warning+"""">
"""
html = html + """<textarea class="toot" rows="20" required="" style="width:95%" maxlength="10000" name="text">"""
html = html + Safe(comment.get("text", ""))
html = html + '</textarea>'
html = html + """
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/ok">
Save
</button>
"""
html = html + '</form>'
html = html + '</div>'
return html
def Comment(comment, url, n=0, user=None, request=False):
if not request:
html = '<div class="dark_box" id="comment_'+str(n)+'">'
else:
html = '<div class="dark_box" id="request_'+str(n)+'">'
account = comment.get("username", "Anonymous User")
html = html + User(account) + '<br>\n'
if request:
html = html + '<center><sup><b> &nbsp; Pending Approval &nbsp; </b></sup></center><br>'
warning = comment.get("warning", "")
if warning:
html = html + '<details>'
html = html + '<summary class="button" title="Click to show anyway.">'
html = html + '<img src="/icon/bug" style="vertical-align: middle">'
html = html + warning
html = html + '</summary>'
html = html + markdown.convert(Safe(comment.get("text", "")), False)+'<br>'
if warning:
html = html + '</details>'
if moderates(user.get("username"), account):
html = html + '<a class="button" href="/'+url+'?comment_edit='+str(n)+'#comment_'+str(n)+'">'
html = html + '<img src="/icon/edit" style="vertical-align: middle"> Edit'
html = html + '</a>'
html = html + '<a class="button" href="/delete_comment?url='+urllib.parse.quote_plus(url)+'&number='+str(n)+'">'
html = html + '<img src="/icon/cancel" style="vertical-align: middle"> Delete'
html = html + '</a>'
html = html + '</div>\n'
return html
def LoginButton(server):
user = validate(server.cookie)
html = '<div class="login_fixed">'
if not user:
html = html + '<a class="button" href="/login?redirect='+urllib.parse.quote_plus(server.path)+'">'
html = html + '<img alt="[icon user]" style="vertical-align: middle" src="/icon/unlock"> Login'
html = html + '</a>'
else:
notifications = len(user.get("notifications", []))
sup = ""
if notifications:
sup = '<sup>'+str(notifications)+'</sup>'
html = html + '<a class="button" href="/settings">'
avatar = Safe(user.get("avatar", ""))
if not avatar: avatar = "/pictures/monkey.png"
html = html + '<img alt="[avatar]" style="height:40px;vertical-align: middle" src="'+avatar+'"> '
html = html + user.get("title", user.get("username", "Anonymous"))
html = html + sup
html = html + '</a>'
html = html + '</div>'
return html
def ProgressBar(frac):
title = str(round(frac*100,1))+"%"
frac = min(1, frac)
frac = max(0, frac)
html = '<div title="'+title+'" class="back_progress">'
html = html + '<div title="'+title+'" class="front_progress", style="width:'+str(frac*100)+'%">'
html = html + '</div></div>'
return html
def NotFound(server):
config = Set.Load()
html = head(title = "404 Not Found",
description = "404 Not Found",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + """
<center>
<div class="article_box">
<h1>404 Not Found</h1>
</center>
"""
send(server, html, 404)
def AccessDenied(server):
config = Set.Load()
html = head(title = "403 Access Denied",
description = "403 Access Denied",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + """
<center>
<div class="article_box">
<h1>403 Access Denied</h1>
</center>
"""
send(server, html, 404)
def Error(server, text="Some Error Happened."):
config = Set.Load()
html = head(title = "501 Error",
description = "501 Error",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + """
<center>
<div class="article_box">
<h1>501 Error</h1>
"""+text+"""
<br><br>
</center>
"""
send(server, html, 501)
###
def Redirect(server, url, time=0):
print(consoleForm(server.cookie), "Redirecting to: "+url)
html = """<meta http-equiv="Refresh" content=\""""+str(time)+"""; url='"""+url.replace("'", "%27").replace('"', "%22")+"""'" />"""
send(server, html, 200)
def Login(server):
username = server.parsed.get("user_name", [""])[0]
password = server.parsed.get("password" , [""])[0]
hashed = hashlib.sha512(password.encode("utf-8")).hexdigest()
redirect = server.parsed.get("redirect" , [""])[0]
logout = server.parsed.get("logout" , [""])[0]
Accounts = accounts()
# Failed authentication
if username not in Accounts or hashed != Accounts[username].get("password"):
Redirect(server, "/login?wrong=username")
# Succesfull authentication
else:
ProbablyHumanCookies.append(server.cookie)
account = Accounts[username]
if "sessions" not in account:
account["sessions"] = {}
account["sessions"][server.cookie] = server.headers.get("User-Agent")
f = Set.Folder()
folder = f+"/accounts"
# Move the cookie arround
# When a login happens, we want to make the server know
# which articles the person already read and stuff. So
# we want to come all the cookies. Arround.
articles = allArticles()
for title in articles:
article = articles[title]
for cookie in account["sessions"]:
if cookie != server.cookie:
# Making it so it knows what you were watching previously.
if cookie in article.get("views", {}).get("viewers", [])\
and server.cookie not in article["views"]["viewers"]:
article["views"]["viewers"].append(server.cookie)
# Making it so previously logged in account would know.
# what you were watching thus far from this cookie.
if server.cookie in article.get("views", {}).get("viewers", [])\
and cookie not in article["views"]["viewers"]:
article["views"]["viewers"].append(cookie)
with open(f+"/tabs"+article.get("url")+"/metadata.json", "w") as save:
json.dump(article, save, indent=4)
if logout:
for cookie in list(account["sessions"].keys()):
if cookie != server.cookie:
del account["sessions"][cookie]
with open(folder+"/"+username+".json", "w") as save:
json.dump(account, save, indent=4)
if not redirect:
Redirect(server, "/settings")
else:
Redirect(server, redirect)
def Register(server):
# If by mistake we are logged in
user = validate(server.cookie)
if user:
Redirect(server, "/register")
username = Simplify(server.parsed.get("user_name", [""])[0], "file")
code = server.parsed.get("code", [""])[0]
password = server.parsed.get("password" , [""])[0]
hashed = hashlib.sha512(password.encode("utf-8")).hexdigest()
Accounts = accounts()
# We avoid username swappage
if username in Accounts or not username:
if code:
Redirect(server, "/register?code="+code+"&userexists=True#user_name")
else:
Redirect(server, "/register?userexists=True#user_name")
return
# Validating the invite code
invited_by = ""
for account in Accounts:
if code in Accounts[account].get("invite_codes", []):
invited_by = account
break
if not invited_by:
Redirect(server, "/register?wrongcode=True")
return
# Now we can finally make our account.
# New account first
account = {
"username":username,
"bio":"",
"invite_codes":{},
"invited":[],
"invited_by":invited_by,
"password":hashed,
"title":username,
"email":"",
"website":"",
"mastodon":"",
"matrix":"",
"sessions":{
server.cookie:server.headers.get("User-Agent")
}
}
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+username+".json", "w") as save:
json.dump(account, save, indent=4)
# Now the invitor changes
account = Accounts[invited_by]
del account["invite_codes"][code]
account["invited"].append(username)
with open(folder+"/"+account.get("username", "")+".json", "w") as save:
json.dump(account, save, indent=4)
Redirect(server, "/settings")
# Notification
Notify(invited_by, "/account/"+Safe(username), "@"+Safe(username)+" has registered from your invitation.")
def LogOut(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
cookie = server.parsed.get("cookie", [""])[0]
# This might be an attack. So we don't want that.
if cookie not in user.get("sessions",{}):
Redirect(server, "/")
return
del user["sessions"][cookie]
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+user.get("username", "")+".json", "w") as save:
json.dump(user, save, indent=4)
# If the user logged out this session
if cookie == server.cookie:
Redirect(server, "/")
else:
Redirect(server, "/settings#sessions")
def UpdateAccount(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
keys = [
"title",
"avatar",
"bio",
"website",
"email",
"mastodon",
"matrix"
]
for key in keys:
data = server.parsed.get(key, [""])[0]
# Making sure to reverify email.
if key == "email" and user[key] != data:
user["email_verified"] = False
user[key] = Safe(data)
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+user.get("username", "")+".json", "w") as save:
json.dump(user, save, indent=4)
Redirect(server, "/settings")
def UpdatePassword(server):
user = validate(server.cookie)
old_password = server.parsed.get("password", [""])[0]
new_password = server.parsed.get("new_password", [""])[0]
old_hashed = hashlib.sha512(old_password.encode("utf-8")).hexdigest()
new_hashed = hashlib.sha512(new_password.encode("utf-8")).hexdigest()
# Validating the user's password
if user.get("password", "") == old_hashed:
user["password"] = new_hashed
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+user.get("username", "")+".json", "w") as save:
json.dump(user, save, indent=4)
Redirect(server, "/settings")
AccessDenied(server)
def UpdatePublicationRights(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
Accounts = accounts()
account = server.parsed.get("account", [""])[0]
if account not in Accounts:
NotFound(server)
return
if not moderates(user.get("username", ""), account):
AccessDenied(server)
return
Tabs = tabs()
Account = Accounts[account]
if "editsIn" not in Account:
Account["editsIn"] = []
granted = []
revoked = []
for tab in Tabs:
if not editsIn(user.get("username", ""), tab):
AccessDenied(server)
return
tabOn = server.parsed.get(tab, [""])[0]
if tabOn and tab not in Account["editsIn"]:
Account["editsIn"].append(tab)
granted.append(tab)
elif not tabOn and tab in Account["editsIn"]:
Account["editsIn"].remove(tab)
revoked.append(tab)
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+account+".json", "w") as save:
json.dump(Account, save, indent=4)
Redirect(server, "/account/"+account)
# Notification
text = "@"+user.get("username", "")+" "
if granted:
text = text + "granted you publication rights in: "
for n, i in enumerate(granted):
text = text + i
if n != len(granted)-1:
text = text + ", "
if revoked:
text = text + " and "
if revoked:
text = text + "revoked your publication rights in: "
for n, i in enumerate(revoked):
text = text + i
if n != len(revoked)-1:
text = text + ", "
text = text + ".<br>"
if granted or revoked:
Notify(account, "/account/"+account, text)
def DoComment(server):
# Limiting bots from commenting
if not server.cookie:
AccessDenied(server)
return
user = validate(server.cookie)
Accounts = accounts()
url = server.parsed.get("url", ["/"])[0]
if not url.startswith("/"): url = "/" + url
text = server.parsed.get("text", [""])[0]
nick = server.parsed.get("username", [""])[0]
warn = server.parsed.get("warning", [""])[0]
number = server.parsed.get("number", [""])[0]
request = server.parsed.get("request", [""])[0]
wasnumber = number
metadata = Set.Folder()+"/tabs"+url+"/metadata.json"
try:
with open(metadata) as o:
article = json.load(o)
except:
Redirect(server, "/")
return
if "comments" not in article:
article["comments"] = {}
if "comments" not in article["comments"]:
article["comments"]["comments"] = []
if "requests" not in article["comments"]:
article["comments"]["requests"] = []
comment = {
"text":text
}
placeRedirect = "#comment_"
if warn:
comment["warning"] = warn
if not nick and user:
comment["username"] = user.get("username", "")
place = "comments"
elif request:
if nick in Accounts or not nick:
nick = "Anonymous Guest"
comment["username"] = nick
del article["comments"]["requests"][int(request)]
place = "comments"
number = ""
else:
if nick in Accounts or not nick:
nick = "Anonymous Guest"
comment["username"] = nick
placeRedirect = "#request_"
place = "requests"
if not user:
comment["cookie"] = server.cookie
if not number:
article["comments"][place].append(comment)
number = len(article["comments"][place])-1
else:
number = int(number)
if moderates(user.get("username"), article["comments"]["comments"][number]["username"]):
# Making sure moderators done get credit for small edits
# in comments.
originalcommet = article["comments"]["comments"][number]
if originalcommet.get("username") in Accounts:
comment["username"] = originalcommet["username"]
article["comments"]["comments"][number] = comment
try:
with open(metadata, "w") as save:
json.dump(article, save, indent=4)
except:
pass
if not number:
placeRedirect = "#comments"
number = ""
Redirect(server, url+placeRedirect+str(number))
if not wasnumber:
# Notification
username = user.get("username", nick)
if username != article.get("author"):
Notify(article.get("author"), url+placeRedirect+str(number), "@"+Safe(username)+" commented: <br><br> <b>"+article.get("title", "")+"</b><br><br><i>"+Safe(text[:200])+"</i>")
# Mention notifications
searchText = text.lower()
for account in Accounts:
# The author already got the notification.
if account == article.get("author"):
continue
name = Accounts[account].get("title", account)
if account.lower() in searchText or name.lower() in searchText:
Notify(account,
url+placeRedirect+str(number),
"@"+Safe(username)+" mentioned you: <br><br> <b>"+article.get("title", "")+"</b><br><br><i>"+Safe(text[:200])+"</i>")
def Publish(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
config = Set.Load()
Tabs = tabs()
f = Set.Folder()
# Getting data to create
tab = server.parsed.get("tab", [""])[0]
name = server.parsed.get("name", [""])[0]
text = server.parsed.get("text", [""])[0]
title = server.parsed.get("title", [""])[0]
description = server.parsed.get("description", [""])[0]
thumbnail = server.parsed.get("thumbnail", [""])[0]
License = server.parsed.get("license", [""])[0]
recording = server.parsed.get("recording", [""])[0]
# Petition data
petition_goal = server.parsed.get("petition_goal", [""])[0]
petition_api = server.parsed.get("petition_api", [""])[0]
petition_api_key = server.parsed.get("petition_api_key", [""])[0]
petition_api_title = server.parsed.get("petition_api_title", [""])[0]
petition_api_link = server.parsed.get("petition_api_link", [""])[0]
# If this tab doesn't exist, this is an error.
if tab not in Tabs:
AccessDenied(server)
return
# Checking if the user has rights to post in here.
if not editsIn(user.get("username"), tab):
AccessDenied(server)
return
Articles = articles(tab)
if not name:
name = Simplify(title)
# Reading the file
if name in Articles:
metadata = Articles[name]
else:
metadata = {
"title":"",
"timestamp":time.time(),
"description":"",
"author":user.get("username", ""),
"thumbnail":"",
"license":"",
"views":{
"amount":0,
"viewers":[],
"dates":{}
},
"recording":"",
"comments":{
"comments":[],
"requests":[]
}
}
# Checking if the user can edit the posts of the
# author of this article.
if not moderates(user.get("username"), metadata.get("author", "")):
AccessDenied(server)
return
metadata["title"] = title
metadata["description"] = description
metadata["license"] = License
metadata["recording"] = recording
metadata["thumbnail"] = thumbnail
# Petition
if petition_goal:
petition = metadata.get("petition", {
"signed":0,
"signatures":[]
})
try:
petition["goal"] = int(petition_goal)
except:
petition["goal"] = 1
# API petition
if petition_api:
petition["api"] = {
"api" :petition_api,
"keys" :petition_api_key.split("/"),
"title":petition_api_title,
"link" :petition_api_link
}
metadata["petition"] = petition
else:
try:
del metadata["petition"]
except:
pass
# Save the changes
try:
os.makedirs(f+"/tabs/"+tab+"/"+name)
except:pass
with open(f+"/tabs/"+tab+"/"+name+"/metadata.json", "w") as save:
json.dump(metadata, save, indent=4)
with open(f+"/tabs/"+tab+"/"+name+"/text.md", "w") as save:
# Enabling HTML embedding only for the owner
if rank(user.get("username", "")) == 0:
save.write(text)
else:
save.write(Safe(text))
if metadata.get("petition"):
metadata["url"] = "/"+tab+"/"+name
try:
API.Petition(metadata)
except Exception as e:
Error(server, "Cannot Load API Value<br>\n"+Safe(str(e)))
return
Redirect(server, "/"+tab+"/"+name)
def DeleteComment(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
url = server.parsed.get("url", ["/"])[0]
if not url.startswith("/"): url = "/" + url
number = int(server.parsed.get("number", ["0"])[0])
metadata = Set.Folder()+"/tabs"+url+"/metadata.json"
try:
with open(metadata) as o:
article = json.load(o)
except:
Redirect(server, "/")
return
comment = article["comments"]["comments"][number]
if moderates(user.get("username", ""), comment.get("username", "")):
del article["comments"]["comments"][number]
try:
with open(metadata, "w") as save:
json.dump(article, save, indent=4)
except:
pass
if number:
redirect = "#comment_"+str(number-1)
else:
redirect = "#comments"
Redirect(server, url+redirect)
def CancelInvite(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
code = server.parsed.get("code", [""])[0]
if user:
del user["invite_codes"][code]
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+user.get("username", "")+".json", "w") as save:
json.dump(user, save, indent=4)
Redirect(server, "/settings#invites")
else:
Redirect(server, "/")
def CreateInvite(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
nick = server.parsed.get("nick", [""])[0]
if not nick: nick = "Unknown"
code = RandString()
if user:
user["invite_codes"][code] = nick
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+user.get("username", "")+".json", "w") as save:
json.dump(user, save, indent=4)
Redirect(server, "/settings?code="+code+"#invite_"+code)
else:
Redirect(server, "/")
def Notify(username, link, text):
Accounts = accounts()
try:
account = Accounts[username]
if "notifications" not in account:
account["notifications"] = []
notification = {
"link":link,
"text":text,
"code":RandString(20)
}
account["notifications"].append(notification)
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+account.get("username", "")+".json", "w") as save:
json.dump(account, save, indent=4)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Unable to set notification!", e, link, text)
def ReadNotification(server):
user = validate(server.cookie)
# Authorization check
if not user:
AccessDenied(server)
return
code = server.parsed.get("code", [""])[0]
try:
# Apparently I'm stupid to use a link here.
# But I already commited to it and I'm editing
# on a live server. So here we go... O.o
for n, notification in enumerate(user.get("notifications")):
if notification.get("code") == code:
break
n = user["notifications"].pop(n)
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+user.get("username", "")+".json", "w") as save:
json.dump(user, save, indent=4)
Redirect(server, n.get("link", "/"))
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Unable to read notification!", e)
def TimeDifference(timeA, timeB):
text = ""
if timeA < timeB:
if timeB - timeA < 10:
text = "now"
return text
elif timeB - timeA < 60:
text = str(int(timeB - timeA))+" seconds ago"
return text
else:
if int( ( timeB - timeA ) / 60 ) == 1:
text = str(int( ( timeB - timeA ) / 60 ))+" minute ago"
else:
text = str(int( ( timeB - timeA ) / 60 ))+" minutes ago"
return text
else:
if timeA - timeA < 10:
text= "now"
return text
elif timeA - timeB < 60:
text = "in "+str(int(timeA - timeB))+" seconds"
return text
else:
if int( ( timeA - timeB ) / 60 ) == 1:
text = "in "+str(int( ( timeA - timeB ) / 60 ))+" minute"
else:
text = "in "+str(int( ( timeA - timeB ) / 60 ))+" minutes"
return text
def RSS(server):
# Rendering rss feed.
Articles = allArticles()
config = Set.Load()
favicon = config.get("favicon", "")
if favicon.startswith("/"):
favicon = "https://"+config.get("domain", "example.com")+favicon
author = server.parsed.get("author", [""])[0]
title = config.get("title", "My Website")
if author:
Accounts = accounts()
account = Accounts.get(author, {})
title = account.get("title", author)+" at: "+title
rss = """<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>"""+title+"""</title>
<link>https://"""+config.get("domain", "example.com")+"""</link>
<description>"""+config.get("tagline", "")+"""</description>
<image>
<url>"""+favicon+"""</url>
<title>"""+title+"""</title>
<link>https://"""+config.get("domain", "example.com")+"""</link>
</image>
"""
n = 0
for article in Articles:
if author and author != Articles[article].get("author", ""):
continue
n += 1
if n > 10:
break
pubDate = Articles[article].get("timestamp", 0)
pubDate = datetime.fromtimestamp(pubDate)
pubDate = email.utils.format_datetime(pubDate)
rss = rss + """
<item>
<title>"""+Articles[article].get("title", article).replace("&", "&amp;")+"""</title>
<link>https://"""+config.get("domain", "example.com")+Articles[article].get("url", "")+"""</link>
<guid>https://"""+config.get("domain", "example.com")+Articles[article].get("url", "")+"""</guid>
<description><![CDATA["""+markdown.convert(Articles[article].get("description", article), False, fullpath=True)+"""]]></description>
<pubDate>"""+pubDate+"""</pubDate>
</item>
"""
rss = rss + """
</channel>
</rss>
"""
send(server, rss, 200)
def Search(server):
Articles = allArticles()
Tabs = tabs()
config = Set.Load()
# Generating <head>
html = head(title = "Search",
description = "",
config = config
)
html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
# The place where you can type your search
text = server.parsed.get("text",[""])[0]
try: page = int(server.parsed.get("page", ["0"])[0])
except Exception as e:
print(e)
page = 0
searchtitle = server.parsed.get("title",[""])[0]
searchauthor = server.parsed.get("author",[""])[0]
searchpost = server.parsed.get("post",[""])[0]
searchdescription = server.parsed.get("description",[""])[0]
searchcomments = server.parsed.get("comments",[""])[0]
# Supporting legacy search links
if not any([searchtitle,
searchauthor,
searchpost,
searchdescription,
searchcomments
]):
searchtitle = True
searchpost = True
searchdescription = True
searchcomments = True
checkedtitle = ""
if searchtitle: checkedtitle = " checked "
checkedauthor = ""
if searchauthor: checkedauthor = " checked "
checkedpost = ""
if searchpost: checkedpost = " checked "
checkeddescription = ""
if searchdescription: checkeddescription = " checked "
checkedcomments = ""
if searchcomments: checkedcomments = " checked "
html = html + """
<center>
<form action="/search">
<div class="toot">
<input name="text" class="button" placeholder="Search..." value='"""+text+"""'>
<button class="button" type="submit">
<img style="vertical-align: middle" src="/icon/search">
Search
</button>
<br>
<div class="button">
<input type="checkbox" """+checkedtitle+""" name="title"> Title
</div>
<div class="button">
<input type="checkbox" """+checkedauthor+""" name="author"> Author
</div>
<div class="button">
<input type="checkbox" """+checkedpost+""" name="post"> Post Text
</div>
<div class="button">
<input type="checkbox" """+checkeddescription+""" name="description"> Description
</div>
<div class="button">
<input type="checkbox" """+checkedcomments+""" name="comments"> Comments
</div>
</div>
<form>
</center>
"""
# Acutally doing the searching
counted = []
for article in Articles:
points = 0
# Title x 100 points
if searchtitle:
title = Articles[article].get("title", article)
points += title.lower().count(text.lower()) * 100
# Description x 10 points
if searchdescription:
description = Articles[article].get("description", "")
points += description.lower().count(text.lower()) * 10
# Author
if searchauthor:
author = Articles[article].get("author", "")
if author == text:
points += 2 # Perfect match with username preffered
# People might also look at the username
Accounts = accounts()
if text.lower() == Accounts.get(author, {}).get("title", "").lower():
points += 1
# Comments x 1
if searchcomments:
comments = Articles[article].get("comments", {}).get("comments", {})
for comment in comments:
commentText = comment.get("text", "")
points += commentText.lower().count(text.lower())
# Post Text x 1
if searchpost:
try:
f = Set.Folder()
url = Articles[article].get("url")
postText = open(f+"/tabs/"+url+"/text.md").read()
points += postText.lower().count(text.lower())
except Exception as e:
print(e)
if points:
counted.append([points, article])
counted = list(reversed(sorted(counted)))
Buffer = 16
From = Buffer*page
To = From+Buffer
urlNoPage = server.path
if "page=" in urlNoPage: urlNoPage = urlNoPage[:urlNoPage.rfind("&")]
if len(list(counted)) > Buffer:
if page > 0:
html = html + Button(str(page-1), urlNoPage+"&page="+str(page-1), "left")
html = html + '<div class="button">'+str(page)+'</div>'
if To < len(list(counted))-1:
html = html + Button(str(page+1), urlNoPage+"&page="+str(page+1), "right")
html = html + """
<br>
<br>
<!-- Article previews are neatly positioned into a grid here -->
<div class="flexity">
"""
rendered = 0
for n, article in enumerate(counted):
points, article = article
if n < From: continue
if n >= To: break
html = html + ArticlePreview(Articles[article], Tabs, server.cookie)
rendered += 1
html = html + '</div><br>'
if len(list(counted)) > Buffer:
if page > 0:
html = html + Button(str(page-1), urlNoPage+"&page="+str(page-1), "left")
html = html + '<div class="button">'+str(page)+'</div>'
if To < len(list(counted))-1:
html = html + Button(str(page+1), urlNoPage+"&page="+str(page+1), "right")
html = html + LoginButton(server)
send(server, html, 200)