# AGPL 3 or any later version # (C) J.Y.Amihud ( Blender Dumbass ) import os import json import random import hashlib import urllib.parse 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 server.send_response(code) server.send_header("Content-type", guess_type(server.path)) if not server.cookie: server.send_header("Set-Cookie", "temp_id="+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 = """ <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=\""""+Simplify(title, False)+""""> <meta property="og:description" content=\""""+Simplify(description, False)+""""> <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 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 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> """ 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): 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"> """ 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", "") config = Set.Load() tab, article = 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", ""), 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(f+"/tabs/"+tab+"/"+article+"/text.md") html = html + '</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): 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>' # 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] 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>' # 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="height:40px; vertical-align: middle" src="/pictures/monkey.png"> <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 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): 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 = {} 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) 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 not in article.get("views", {}).get("viewers", []): article["views"]["amount"] += 1 article["views"]["viewers"].append(cookie) 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): html = """<meta http-equiv="Refresh" content="0; url='"""+url+"""'" />""" 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, "/") 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 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 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)