2024-11-19 13:47:45 +02:00
|
|
|
# AGPL 3 or any later version
|
|
|
|
# (C) J.Y.Amihud ( Blender Dumbass )
|
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
import os
|
|
|
|
import json
|
2024-11-20 14:40:56 +02:00
|
|
|
from datetime import datetime
|
2024-11-19 19:04:03 +02:00
|
|
|
|
2024-11-19 13:47:45 +02:00
|
|
|
from modules import Set
|
2024-11-20 14:40:56 +02:00
|
|
|
from modules import markdown
|
2024-11-19 19:04:03 +02:00
|
|
|
from modules.Common import *
|
2024-11-19 13:47:45 +02:00
|
|
|
|
|
|
|
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):
|
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
# Basic cookie for logins to work
|
|
|
|
cookie = False
|
|
|
|
if "Cookie" not in str(server.headers):
|
|
|
|
cookie = True
|
|
|
|
|
2024-11-19 13:47:45 +02:00
|
|
|
server.send_response(code)
|
|
|
|
server.send_header("Content-type", guess_type(server.path))
|
2024-11-19 19:04:03 +02:00
|
|
|
|
|
|
|
if cookie:
|
|
|
|
server.send_header("Set-Cookie", RandString())
|
|
|
|
|
2024-11-19 13:47:45 +02:00
|
|
|
server.end_headers()
|
|
|
|
|
|
|
|
def head(title="", description="", image="", config={}):
|
|
|
|
|
|
|
|
if image.startswith("/"): image = config.get("url","")+image
|
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
favicon = config.get("favicon", "/icon/internet")
|
2024-11-19 13:47:45 +02:00
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
html = """
|
2024-11-19 13:47:45 +02:00
|
|
|
<!-- 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 -->
|
2024-11-19 19:04:03 +02:00
|
|
|
<link rel="icon" href=\""""+favicon+""""> <!-- Tiny image in the tab -->
|
2024-11-19 13:47:45 +02:00
|
|
|
|
|
|
|
<!-- 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+"""">
|
2024-11-20 14:40:56 +02:00
|
|
|
|
|
|
|
<!-- This meta tag is needed for pretty rendering on phones -->
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
2024-11-19 13:47:45 +02:00
|
|
|
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
return html
|
|
|
|
|
|
|
|
def send(server, html, code):
|
|
|
|
|
|
|
|
# Add headers
|
|
|
|
headers(server, code)
|
|
|
|
server.wfile.write(html.encode("utf-8"))
|
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
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
|
2024-11-20 14:40:56 +02:00
|
|
|
|
|
|
|
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)}
|
2024-11-19 19:04:03 +02:00
|
|
|
|
2024-11-20 14:40:56 +02:00
|
|
|
return articles
|
|
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
2024-11-19 13:47:45 +02:00
|
|
|
def MainPage(server):
|
|
|
|
|
|
|
|
# Reading config
|
|
|
|
config = Set.Load()
|
|
|
|
|
|
|
|
# Generating <head>
|
|
|
|
html = head(title = config.get("title", "Website"),
|
|
|
|
description = config.get("description", "Description"),
|
|
|
|
config = config
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
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"))
|
2024-11-20 14:40:56 +02:00
|
|
|
|
|
|
|
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>'
|
2024-11-19 19:04:03 +02:00
|
|
|
|
2024-11-20 14:40:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-11-19 13:47:45 +02:00
|
|
|
|
|
|
|
send(server, html, 200)
|
|
|
|
|
2024-11-20 14:40:56 +02:00
|
|
|
|
|
|
|
###
|
|
|
|
|
|
|
|
def Button(text, link, icon="", image=""):
|
2024-11-19 13:47:45 +02:00
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
html = """
|
|
|
|
<a class="button" href=\""""+link+"""">"""
|
|
|
|
if icon:
|
|
|
|
html = html + """
|
|
|
|
<img src="/icon/"""+icon+"""" style="vertical-align: middle">"""
|
2024-11-20 14:40:56 +02:00
|
|
|
if image:
|
|
|
|
html = html + """
|
|
|
|
<img src=\""""+image+"""" style="height:40px;vertical-align: middle">"""
|
|
|
|
|
2024-11-19 19:04:03 +02:00
|
|
|
html = html + """
|
|
|
|
"""+text+"""</a>
|
|
|
|
|
|
|
|
"""
|
|
|
|
return html
|
2024-11-19 13:47:45 +02:00
|
|
|
|
2024-11-20 14:40:56 +02:00
|
|
|
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("<", "<").replace(">", ">")
|
|
|
|
username = username.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
|
|
title = account.get("title", username).replace("&", "&").replace("<", "<").replace(">", ">")
|
|
|
|
html = '<img style="height:50px;vertical-align: middle" src="'+avatar+'"> <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)
|