2624 lines
73 KiB
Python
2624 lines
73 KiB
Python
# AGPL 3 or any later version
|
|
# (C) J.Y.Amihud ( Blender Dumbass )
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import random
|
|
import hashlib
|
|
import urllib.parse
|
|
|
|
from datetime import datetime
|
|
|
|
from modules import Set
|
|
from modules import markdown
|
|
from modules.Common import *
|
|
|
|
KnownCookies = []
|
|
RecentArticles = {}
|
|
|
|
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()
|
|
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[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 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" placeholder="Search..." >
|
|
|
|
<button class="button" type="submit">
|
|
<img 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)
|
|
|
|
if url.endswith(".md"):
|
|
url = url.replace(".md", "")
|
|
|
|
# Recording when was the last time
|
|
# the article loaded.
|
|
RecentArticles["/"+url] = time.time()
|
|
|
|
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>
|
|
</details>
|
|
"""
|
|
|
|
html = html + '</div>'
|
|
|
|
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="+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 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>'
|
|
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]
|
|
|
|
# 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">
|
|
|
|
<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>
|
|
|
|
<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">
|
|
"""
|
|
|
|
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>
|
|
|
|
"""
|
|
|
|
# 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>
|
|
|
|
"""
|
|
|
|
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("'", "'")+'">'
|
|
|
|
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("'", "'")+"""'>
|
|
|
|
<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("'", "'")+"""'>
|
|
|
|
<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("'", "'")+"""'>
|
|
<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("'", "'")+"""'>
|
|
|
|
|
|
<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 src="/icon/"""+icon+"""" style="vertical-align: middle">"""
|
|
if image:
|
|
html = html + """
|
|
<img 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> Unread </b></sup></center><br>'
|
|
|
|
html = html + '<a href="'+url+'"><h1>'
|
|
html = html + '<img src="/icon/'+Tabs.get(tab, {}).get("icon", "folder")+'" style="vertical-align: middle">'
|
|
html = html + article.get("title", "")+"</h1></a>"+sup+"\n"
|
|
|
|
if "thumbnail" in article:
|
|
html = html + '<center><a href="'+url+'"><img src="'+article["thumbnail"]+'"></a></center>'
|
|
|
|
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 style="height:50px;vertical-align: middle" src="'+avatar+'"> <a href="/account/'+username+'">'+title+'</a></center>\n'
|
|
else:
|
|
html = '<img style="height:50px;vertical-align: middle" src="'+avatar+'"> '+title+'</center>\n'
|
|
|
|
return html
|
|
|
|
def Graph(server, url):
|
|
|
|
|
|
# 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:
|
|
Redirect(server, server.path)
|
|
return
|
|
|
|
# Sometimes scrapers try to load graph without
|
|
# loading the article first. We don't want to count
|
|
# it as a view.
|
|
if time.time()-10 > RecentArticles.get(url, 0):
|
|
print(consoleForm(server.cookie), "Article wasn't loaded, scrapers!")
|
|
AccessDenied(server)
|
|
return
|
|
|
|
|
|
user = validate(server.cookie)
|
|
|
|
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>
|
|
|
|
"""
|
|
|
|
if url.endswith(".md"):
|
|
url = url.replace(".md", "")
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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> Pending Approval </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> Pending Approval </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">'
|
|
html = html + '<img 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 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 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 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()
|
|
|
|
Accounts = accounts()
|
|
|
|
# Failed authentication
|
|
if username not in Accounts or hashed != Accounts[username].get("password"):
|
|
Redirect(server, "/login?wrong=username")
|
|
|
|
# Succesfull authentication
|
|
else:
|
|
|
|
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"
|
|
with open(folder+"/"+username+".json", "w") as save:
|
|
json.dump(account, save, indent=4)
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
Redirect(server, "/settings")
|
|
|
|
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]
|
|
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 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):
|
|
|
|
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]
|
|
|
|
|
|
|
|
# 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
|
|
|
|
# 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))
|
|
|
|
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 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 = pubDate.strftime('%a, %d %b %H:%M:%S')+" -0000"
|
|
|
|
rss = rss + """
|
|
<item>
|
|
|
|
<title>"""+Articles[article].get("title", article)+"""</title>
|
|
<link>https://"""+config.get("domain", "example.com")+Articles[article].get("url", "")+"""</link>
|
|
<description><![CDATA["""+markdown.convert(Articles[article].get("description", article), False)+"""]]></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)
|