BDServer/modules/Render.py
2024-11-20 14:40:56 +02:00

433 lines
12 KiB
Python

# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import json
from datetime import datetime
from modules import Set
from modules import markdown
from modules.Common import *
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 "/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
cookie = False
if "Cookie" not in str(server.headers):
cookie = True
server.send_response(code)
server.send_header("Content-type", guess_type(server.path))
if cookie:
server.send_header("Set-Cookie", RandString())
server.end_headers()
def head(title="", description="", image="", config={}):
if image.startswith("/"): image = config.get("url","")+image
favicon = config.get("favicon", "/icon/internet")
html = """
<!-- 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=\""""+title+"""">
<meta property="og:description" content=\""""+description+"""">
<meta property="og:image" content=\""""+image+"""">
<!-- 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 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)
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 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 + """
<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>
"""
Tabs = tabs()
for tab in Tabs:
html = html + Button(Tabs[tab].get("title", tab),
"/"+tab,
Tabs[tab].get("icon", "folder"))
html = html + "</center>"
for i in range(50): html = html + "<br>"
html = html + Footer()
send(server, html, 200)
def ListPage(server, tab):
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"))
# 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">
"""
print(page)
rendered = 0
for n, article in enumerate(Articles):
if n < From: continue
if n >= To: break
html = html + ArticlePreview(Articles[article],
tab+"/"+article,
tab, Tabs)
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")
send(server, html, 200)
def ArticlePage(server, url):
if url.endswith(".md"):
url = url.replace(".md", "")
config = Set.Load()
tab, article = url.split("/")
Tabs = tabs()
Articles = articles(tab)
# 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"))
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">'
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>'
# 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>'
# Audio recording of the article
recording = Articles.get(article, {}).get("recording", "")
if recording:
html = html + '<audio controls="controls" style="min-width:100%;" src="'+recording+'"></audio>'
html = html + '<div class="dark_box">'
html = html + markdown.convert(Set.Folder()+"/tabs/"+tab+"/"+article+"/text.md")
html = html + '</div>'
html = html + '</div>'
# Thumbnail and suggestions
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, url, tab, Tabs):
html = """
<div class="article_box">
"""
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>\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>"+article.get("description", "")+"<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 = account.get("avatar", "/pictures/monkey.png").replace("<", "&lt").replace(">", "&gt")
username = username.replace("&", "&amp").replace("<", "&lt").replace(">", "&gt")
title = account.get("title", username).replace("&", "&amp").replace("<", "&lt").replace(">", "&gt")
html = '<img style="height:50px;vertical-align: middle" src="'+avatar+'">&nbsp;&nbsp;<a href="/account/'+username+'">'+title+'</a></center>\n'
return html
def Graph(server, 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>
"""
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 = {}
dates = article.get("views", {}).get("dates", {})
largest = max(dates.values())
dateformat = "%Y-%m-%d"
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)
print(alldays)
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'
send(server, html, 200)