# AGPL 3 or any later version # (C) J.Y.Amihud ( Blender Dumbass ) import os import json import random 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 = """ """+title+""" """ 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) accounts[account.replace(".json","")] = data except Exception as e: print(e) pass return accounts 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 int(round(len(text)/2500)) ### def MainPage(server): # Reading config config = Set.Load() # Generating html = head(title = config.get("title", "Website"), description = config.get("description", "Description"), config = config ) html = html + """

[LOGO]

"""+config.get("title", "My Website")+"""

"""+config.get("tagline", "")+"""

""" Tabs = tabs() for tab in Tabs: html = html + Button(Tabs[tab].get("title", tab), "/"+tab, Tabs[tab].get("icon", "folder")) html = html + "
" # Trending articles html = html + '
' 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 + '
' 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 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 + '
'+str(page)+'
' if To < len(Articles)-1: html = html + Button(str(page+1), tab+"?page="+str(page+1), "right") html = html + """

""" print(page) 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 + '

' # 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 + '
'+str(page)+'
' 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) f = Set.Folder() # Generating html = head(title = Articles.get(article, {}).get("title", article), description = Articles.get(article, {}).get("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 + '
' html = html + '
' html = html +"

"+Articles.get(article, {}).get("title", article)+"

" # Page author author = Articles.get(article, {}).get("author", "") if author: html = html + '
'+User( author )+'
' # 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 + '
👁 '+views+'' html = html + """

""" html = html + '
' # Audio recording of the article recording = Articles.get(article, {}).get("recording", "") if recording: html = html + '' html = html + '
' html = html + markdown.convert(f+"/tabs/"+tab+"/"+article+"/text.md") html = html + '
' html = html + '
' # Thumbnail and suggestions html = html + '
' thumbnail = Articles.get(article, {}).get("thumbnail") if thumbnail: html = html + '

' html = html + '' html = html + '

' suggestions = suggestedArticles(server.cookie, random=True) toomuch = previewsToSize(open(f+"/tabs/"+tab+"/"+article+"/text.md").read()) 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 + "" send(server, html, 200) def AccountPage(server, account): config = Set.Load() Accounts = accounts() Tabs = tabs() Articles = allArticles() f = Set.Folder() # Generating 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 + '
' html = html + '
' html = html +"

"+Accounts.get(account, {}).get("title", account)+"

" html = html + '
' html = html + '
' html = html +"
Invited by:
"+User(Accounts.get(account, {}).get("invited_by", account))+"
" html = html + '
' bio = Safe(Accounts.get(account, {}).get("bio" , "")) if bio: html = html + '
' html = html + markdown.convert(bio, False) html = html + '
' # Posts by this account html = html + '
' for article in Articles: if Articles[article].get("author") != account: continue html = html + ArticlePreview(Articles[article], Tabs, server.cookie) html = html + '
' html = html + '
' # Thumbnail and suggestions html = html + '
' avatar = Safe(Accounts.get(account, {}).get("avatar" , "")) if avatar: html = html + '

' html = html + '' html = html + '

' # Invited invited = Accounts.get(account, {}).get("invited", []) if invited: html = html + '
Invited:
' for username in invited: html = html + '
' html = html + '
' + User(username) + '
\n' html = html + '
' send(server, html, 200) ### def Button(text, link, icon="", image=""): html = """ """ if icon: html = html + """ """ if image: html = html + """ """ html = html + """ """+text+""" """ return html def ArticlePreview(article, Tabs, cookie=""): html = """
""" url, tab = article.get("url", ""), article.get("tab","") sup = "" if cookie not in article.get("views", {}).get("viewers", ""): sup = '
  Unread  

' html = html + '

' html = html + '' html = html + article.get("title", "")+"

"+sup+"\n" if "thumbnail" in article: html = html + '
' author = article.get("author", "") if author: html = html + '
'+User( author )+'
' views = str(article.get("views", {}).get("amount", 0)) try: comments = str(len(article.get("comments", {}).get("comments"))) except: comments = "0" html = html +'
👁 '+views+' 💬 '+comments+'
' html = html + "
"+markdown.convert(article.get("description", ""), False)+"

" html = html + '
\n' return html def Footer(): html = """
""" html = html + Button("Powered with BDServer", "https://codeberg.org/blenderdumbass/BDServer/", "codeberg") html = html + """
""" 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)) html = '  '+title+'\n' return html def Graph(server, url): html = """ """ 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 + '
\n' # Saving the view if server.cookie not in article.get("views", {}).get("viewers", []): try: article["views"]["amount"] += 1 article["views"]["viewers"].append(server.cookie) with open(Set.Folder()+"/tabs"+url+"/metadata.json", "w") as save: json.dump(article, save, indent=4) except Exception as e: print(e) pass send(server, html, 200)