Publishing from Web UI

This commit is contained in:
BlenderDumbass 2024-11-28 05:30:15 +02:00
parent 151efdfc8a
commit f38e020de4
2 changed files with 421 additions and 7 deletions

View file

@ -3,6 +3,7 @@
import os
import json
import time
import random
import hashlib
import urllib.parse
@ -151,6 +152,7 @@ def accounts():
return accounts
def validate(cookie):
Accounts = accounts()
@ -188,6 +190,31 @@ def rank(account, Accounts=None):
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
@ -354,6 +381,8 @@ def MainPage(server):
def ListPage(server, tab):
user = validate(server.cookie)
Tabs = tabs()
Articles = articles(tab)
config = Set.Load()
@ -376,6 +405,9 @@ def ListPage(server, tab):
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:
@ -456,8 +488,19 @@ def ArticlePage(server, url):
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:
@ -578,6 +621,8 @@ def ArticlePage(server, url):
def AccountPage(server, account):
user = validate(server.cookie)
config = Set.Load()
Accounts = accounts()
Tabs = tabs()
@ -641,8 +686,6 @@ def AccountPage(server, account):
# It could be mastodon url and not handle.
# https://social.trom.tf/@morsmortium
try:
if "/" in mastodon:
mastodon = mastodon.replace("https://", "").replace("http://", "")
@ -692,6 +735,46 @@ def AccountPage(server, account):
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">'
@ -920,7 +1003,7 @@ def SettingsPage(server):
<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="height:40px; vertical-align: middle" src="/pictures/monkey.png">
<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>
@ -1033,6 +1116,112 @@ def SettingsPage(server):
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+'">'
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", "")+"""'>
<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", "")+"""'>
<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", "")+"""'>
<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", "")+"""'>
<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)
###
@ -1623,7 +1812,82 @@ def UpdateAccount(server):
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>"
Notify(account, "/account/"+account, text)
def DoComment(server):
user = validate(server.cookie)
@ -1744,6 +2008,102 @@ def DoComment(server):
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):

View file

@ -28,7 +28,6 @@ class handler(BaseHTTPRequestHandler):
q = clr["tbbl"]
b = clr["tbbl"]
#print(dir(self))
try:
if self.newview:
@ -57,15 +56,64 @@ class handler(BaseHTTPRequestHandler):
logfile = open(filename, "ab")
logfile.write((toprint+"\n").encode('utf-8'))
logfile.close()
def do_POST(self):
self.path = self.path.replace("/..", "/")
self.path = self.path.replace("%27", "'")
self.cookie = self.headers.get("Cookie")
self.newview = False
if self.cookie: self.cookie = self.cookie[-50:]
# Failing early to make sure that nobody will
# try attacking this part of the server.
commands = ["do_edit"]
found = False
for i in commands:
if i in self.path:
found = True
break
if not found:
Render.AccessDenied(self)
return
# Not allowing more than 1 MB for the sake of
# network. A full book of Sheiny The Hacker
# which was posted on blenderdumbass.org is
# only about 160 KB. 1MB should be plenty.
length = int(self.headers["Content-Length"])
if length < 1000000:
try:
text = self.rfile.read(length).decode("utf-8")
except:
Render.AccessDenied(self)
return
else:
Render.AccessDenied(self)
return
#parsed_url = urllib.parse.urlparse(text)
self.parsed = urllib.parse.parse_qs(text)
if self.path[1:].startswith("do_edit"):
Render.Publish(self)
else:
Render.NotFound(self)
def do_GET(self):
self.path = self.path.replace("/..", "/")
self.path = self.path.replace("%27", "'")
parsed_url = urllib.parse.urlparse(self.path)
self.parsed = urllib.parse.parse_qs(parsed_url.query)
self.cookie = self.headers.get("Cookie")
if self.cookie: self.cookie = self.cookie[-50:]
@ -100,9 +148,12 @@ class handler(BaseHTTPRequestHandler):
elif self.path[1:].startswith("login"):
Render.LoginPage(self)
elif self.path[1:].startswith("editor"):
Render.EditorPage(self)
elif self.path[1:].startswith("register"):
Render.RegisterPage(self)
elif self.path[1:].startswith("settings"):
Render.SettingsPage(self)
@ -115,6 +166,9 @@ class handler(BaseHTTPRequestHandler):
elif self.path[1:].startswith("update_account"):
Render.UpdateAccount(self)
elif self.path[1:].startswith("grant_publication_rights"):
Render.UpdatePublicationRights(self)
elif self.path[1:].startswith("create_invite"):
Render.CreateInvite(self)