# AGPL 3 or any later version # (C) J.Y.Amihud ( Blender Dumbass ) import os import sys import json import time import email import random import threading import hashlib import urllib.parse from datetime import datetime from modules import Set from modules import API from modules import Plugins from modules import Analyse from modules import markdown from modules import Federation from modules.Common import * KnownCookies = [] ProbablyHumanCookies = [] RecentArticles = {} RefferedArticles = {} ProblematicRefreshes = [] 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 ".ttf" in path: return "font/ttf" if ".py" in path: return "application/python" if "/rss" in path or ".rss" in path: return "application/rss+xml" if "/icon" in path or path.endswith(".png"): return "image/png" if path.endswith("gif"): return "image/gif" if path.endswith("jpg"): return "image/jpg" if path.endswith("zip"): return "application/zip" if path.endswith("xz"): return "application/zip" return "text/html" def headers(server, code, filesize=None): # Basic cookie for logins to work cookie = "" useragent = str(server.headers.get("User-Agent")) if not server.cookie and " | " in StripUserAgent(useragent): cookie = RandString(200) KnownCookies.append(cookie) if server.isPage: server.cookie = cookie server.send_response(code) server.send_header("Content-type", guess_type(server.path)) if filesize: server.send_header("Content-Length", filesize) if cookie: server.send_header("Set-Cookie", "temp_id="+cookie) server.end_headers() def head(title="", description="", image="", config={}, author=""): 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=\""""+Safe(title)+""""> <meta property="og:description" content=\""""+Safe(StripMarkdown(description).replace('"',"'"))+""""> <meta property="og:image" content=\""""+image+""""> <!-- RSS Link --> <link rel="alternate" title='"""+config.get("title", "My Website")+"""' type="application/rss+xml" href="/rss" /> """ if author: html = html + """ <link rel="alternate" title='Just @"""+author+"""' type="application/rss+xml" href="/rss?author="""+author+"""" /> """ # Author tags. if author: account = accounts().get(author, {}) name = account.get("title", account) mastodon = account.get("mastodon", "") try: if "/" in mastodon: mastodon = mastodon.replace("https://", "").replace("http://", "") mastodon = mastodon.split("/")[1]+"@"+mastodon.split("/")[0] except: pass if mastodon: html = html + """ <meta name=article:author content=\""""+name+""""> <meta name=fediverse:creator content=\""""+mastodon+""""> """ # Tor tags. tor = config.get("tor", "") if tor: if not tor.startswith("http://"): tor = "http://"+tor html = html + '<meta http-equiv="onion-location" content="'+tor+'" />' html = html + """ <!-- 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 RecordHTML(server.path, html) headers(server, code) html = Plugins.onHTML(server, html) 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 isHuman(server): #cookie = server.cookie #if not GuessHuman(server): return False #return ( cookie in ProbablyHumanCookies and cookie in KnownCookies ) or validate(cookie) return GuessHuman(server) def moderates(moderator, user): Accounts = accounts() if type(user) != str: return False 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 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 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[data["url"]] = 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 = [] active = [] activeDone = False for article in articles: if cookie not in articles[article].get("views", {}).get("viewers", []): move.append(article) petition = articles[article].get("petition", {}) if petition: if petition.get("goal", 0) > petition.get("signed", 0): active.append(article) for n, article in enumerate(move): if article not in active: newarticles[article] = articles[article] if n == 0 and active: for i in active: newarticles[i] = articles[i] activeDone = True for n, article in enumerate(articles): if article not in move + active: newarticles[article] = articles[article] if n == 0 and not activeDone: for i in active: newarticles[i] = articles[i] 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 peerhead(peertube): # We want to turn those 3 types of links # https://peer.madiator.cloud/a/blenderdumbass/video-channels # https://peer.madiator.cloud/c/blender_dumbass/videos # https://peer.madiator.cloud/@blenderdumbass # Into those two types of handles # blenderdumbass@peer.madiator.cloud # blender_dumbass@peer.madiator.cloud # If it is already good, we will just return it back if "@" in peertube and not "http" in peertube: if peertube.startswith("@"): peertube = peertube[1:] return peertube # cutting off the /video* part in the end if "/video" in peertube: peertube = peertube[:peertube.find("/video")] # splitting splits = ["/a/", "/c/", "/@", "/"] domain, handle = "", "" for s in splits: if s in peertube: domain, handle, *rest = peertube.split(s) break # Avoiding catastrophy if not domain: return peertube domain = domain.replace("https://", "").replace("http://", "") # combining peertube = handle + "@" + domain return peertube def peerlink(peertube): PeerHead = peerhead(peertube) return "https://"+PeerHead.split("@")[1]+"/@"+PeerHead.split("@")[0] def mastohead(mastodon): try: if "/" in mastodon: mastodon = mastodon.replace("https://", "").replace("http://", "") mastodon = mastodon.split("/")[1]+"@"+mastodon.split("/")[0] except: pass if not mastodon.startswith("@"): mastodon = "@"+mastodon return mastodon def mastolink(mastodon): return "https://"+mastodon[1:].split("@")[1]+"/@"+mastodon[1:].split("@")[0] def isFreeSoftware(app): with open("fcdata.json") as json_file: licenses = json.load(json_file).get("licenses",[]) if "licenses" in app and app["licenses"]: all_licenses = licenses for al in all_licenses: # Making longer loop once for l in app["licenses"]: if l in [al.get("licenseId",""),al.get("name","")]\ and al.get("isFsfLibre", False): return True ### def MainPage(server): # Reading config config = Set.Load() tab_rows = config.get("tab_rows", 1) # Generating <head> html = head(title = config.get("title", "Website"), description = config.get("description", "Description"), config = config ) html = html + LoginButton(server) html = html + """ <!-- Render.MainPage --> <br> <br> <center> <!-- Render.MainPage ( Website Logo ) --> <img src=\""""+config.get("favicon", "icon/internet")+"""" alt="[LOGO]" style="height:150px;vertical-align: middle"> <br> <br> <!-- Render.MainPage ( Website Title ) --> <h2>"""+config.get("title", "My Website")+"""</h2> <!-- Render.MainPage ( Website Tagline ) --> """+config.get("tagline", "")+""" <br> <br> <!-- Render.MainPage ( Search Form ) --> <form action="/search"> <input name="text" class="button" title="Search text" placeholder="Search..." > <button title="Start the search" class="button" type="submit"> <img class="icon" alt="[icon search]" style="vertical-align: middle" src="/icon/search"> Search </button> </form> <!-- Render.MainPage ( /Search Form ) --> <br> """ # Stream alerts html = html + StreamAlerts(server) html = html + '<!-- Render.MainPage ( Tabs ) -->' Tabs = tabs() for n, tab in enumerate(Tabs): html = html + Button(Tabs[tab].get("title", tab), "/"+tab, Tabs[tab].get("icon", "folder")) if n % int(len(Tabs) / tab_rows) == 0 and n: html = html + "<br>" html = html + "</center><!-- Render.MainPage ( /Tabs ) -->" # Trending articles Accounts = accounts() owner = config.get("main_account", "") mastoposts = [] if owner: mastodon = Accounts.get(owner, {}).get("mastodon") if mastodon: try: mastoposts = API.Mastodon(mastohead(mastodon), 8).copy() except: pass html = html + '<!-- Render.MainPage ( Articles List ) --><div class="flexity">' trends = suggestedArticles(server.cookie) Buffer = 20 for n, article in enumerate(trends): if n >= Buffer: break if n and n % 2 == 0 and mastoposts: html = html + Mastoview(mastoposts.pop(0), owner) article = trends[article] html = html + ArticlePreview(article, Tabs, server.cookie) html = html + '</div><!-- Render.MainPage ( /Articles List ) -->' html = html + Footer(server) #html = html + Totals() html = html + LoginButton(server) send(server, html, 200) def ListPage(server, tab): user = validate(server.cookie) 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")) 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: 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 + """ <!-- Render.ListPage --> <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 + Footer(server) html = html + LoginButton(server) send(server, html, 200) def ArticlePage(server, url): user = validate(server.cookie) referrer = server.headers.get("referer", "") if url.endswith(".md"): url = url.replace(".md", "") # Recording when was the last time # the article loaded. RecentArticles["/"+url] = time.time() RefferedArticles["/"+url] = referrer config = Set.Load() tab, article, *rest = 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", ""), image = Articles.get(article, {}).get("thumbnail", ""), config = config, author = Articles.get(article, {}).get("author") ) 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")) html = html + '<!-- Render.ArticlePage -->' # The article itself html = html + '\n<!-- Render.ArticlePage ( Main Section ) -->\n<div class="middle_section_article">' # Stream altert html = html + '<center>'+StreamAlerts(server)+'</center>' html = html + '\n<!-- Render.ArticlePage ( Title Box ) -->\n<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 + '<br><br>' 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>' timestamp = Articles.get(article, {}).get("timestamp", "") arttime = str(datetime.fromtimestamp(timestamp).strftime("%B %d, %Y")) html = html + "<br><small><center>"+str(arttime)+"</center></small>" # 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>""" referrers = Articles.get(article, {}).get("views", {}).get("referrers", {}) if referrers: for referrer in referrers: # Filtering out probably bad links. if ( "https://" in referrer and ".onion" not in referrer ) or rank(user.get("username", "")) == 0: html = html + Safe(referrer) + " : 👁 "+str(referrers[referrer]) + "<br>\n" html = html + """ </details> """ # Hashtags hashtags = Articles.get(article, {}).get("hashtags", []) if hashtags: html = html + '<br><center>' for tag in hashtags: html = html + '<a href="/search?text=%23'+tag+'&tags=on"><small>#'+tag+'</small></a> ' html = html + '</center><br>' html = html + '</div>\n<!-- Render.ArticlePage ( /Title Box ) -->\n' # Petition petition = Articles.get(article, {}).get("petition", "") if petition: petition_error = False if petition.get("api"): try: API.Petition(Articles[article]) except: petition_error = True html = html + '\n<!-- Render.ArticlePage ( Petition Box ) -->\n<div id="petition" class="dark_box"> <center>' html = html + '<h2>' html = html + 'Petition</h2></center>' html = html + HelpButton(server, "petition", "#petition") try: frac = petition.get("signed", 0) / int(petition.get("goal", 1)) except: frac = 0 html = html + ProgressBar(frac) html = html + "<br><center>"+str(petition.get("signed", 0))+" / "+Safe(str(petition.get("goal", 1)))+" Signatures" # Last update if petition.get("api"): lastUpdate = petition.get("api", {}).get("timestamp", {}) nowTime = time.time() html = html + '<br><br><small>Last updated: '+TimeDifference(lastUpdate, nowTime)+'</small><br><br>' if not petition.get("api"): html = html + """ <details> <summary class="button"> <img alt="[icon petition]" style="vertical-align: middle" src="/icon/petition"> Sign </summary> <form action="/sign_petition"> <input type="hidden" name="article" value='/"""+tab+"/"+article+"""'> <img style="vertical-align: middle" src="/icon/frase"> <input class="button" style="width:90%" required maxlength="200" name="email" placeholder="Email"> <button class="button" type="submit"> <img style="vertical-align: middle" src="/icon/ok"> Submit </button> </form> </details> """ else: html = html + """ <details> <summary class="button"> <img alt="[icon petition]" style="vertical-align: middle" src="/icon/petition"> Sign </summary> <center> <br> This petition is signed by increasing the number of<br> <i>"""+petition.get("api", {}).get("title", "")+"""</i> <br><br> """+Button("Continue", petition.get("api", {}).get("link", ""), "ok")+""" </center> """ html = html + '</div>\n<!-- Render.ArticlePage ( /Petition Box ) -->\n' # License License = Articles.get(article, {}).get("license", "") if License: html = html + '\n<!-- Render.ArticlePage ( License Box ) -->\n<div class="dark_box"> <center>' html = html + 'License:<br>' if License in Licenses: html = html + '<img style="vertical-align: middle" src="/icon/'+License+'"> ' html = html + '<a href="'+Licenses.get(License, {}).get("link","")+'">' html = html + Licenses.get(License, {}).get("name", License)+'</a>' html = html + '</center></div>\n<!-- Render.ArticlePage ( /License Box ) -->\n' not_ai_proof = Articles.get(article, {}).get("not_ai_proof", "") if not_ai_proof: html = html + '<div class="dark_box"> <center>' html = html + '<img style="vertical-align: middle" src="/icon/ok">' html = html + ' Not AI Generated! ' html = html + Button("See Proof", not_ai_proof, 'internet', newpage=True) html = html + '</center></div>' # Audio recording of the article recording = Articles.get(article, {}).get("recording", "") if recording: html = html + '<div class="dark_box"> <center>' html = html + 'Audio Version<br><br>' html = html + '<audio controls="controls" style="min-width:100%;" src="'+recording+'"></audio>' html = html + '<br><br></center></div>' # If this article is meant as a reply to a different article. is_reply_to = Articles.get(article, {}).get("is_reply_to", "") if is_reply_to: reply_to_data = Articles.get(article, {}).get("reply_to_data", {}) html = html + '<div class="dark_box"> ' html = html + '<i>...in reply to:</i><br>' html = html + '<center><h2>'+reply_to_data.get("title", "")+'</h2>' html = html + '<small>'+reply_to_data.get("domain", is_reply_to)+'</small><br><br>' replytoauthor = reply_to_data.get("author") if replytoauthor: html = html + User(replytoauthor) replythumbnail = reply_to_data.get("thumbnail") if replythumbnail: html = html + '<br><img alt=[thumbnail] src="'+replythumbnail+'" style="max-height:150px; vertical-align: middle; float: right; padding: 40px;">' html = html + '</center><i>'+markdown.convert(Safe(reply_to_data.get("description", "")), False)+'</i><br><br>' html = html + Button("View Referenced Publication", is_reply_to, "internet") html = html + '<br><br></div>' html = html + '\n<!-- Render.ArticlePage ( Article ) -->\n<div class="dark_box">' html = html + markdown.convert(f+"/tabs/"+tab+"/"+article+"/text.md") html = html + '</div>\n<!-- Render.ArticlePage ( /Article ) -->\n' # RSS rsslink = "https://"+config.get("domain", "")+"/rss" authorrsslink = rsslink+"?author="+Safe(Articles.get(article, {}).get("author","")) html = html + """ <!-- Render.ArticlePage ( Share Box ) --> <div id="subscribe_share" class="dark_box"> """+HelpButton(server, "subscribe_share", "#subscribe_share")+"""<br><br><br><br> <center> <details> <summary class="button"> <img class="icon" style="vertical-align: middle" src="/icon/rss"> Subscribe RSS </summary> <br> <img style="vertical-align: middle" src="/icon/user"> <input class="button" style="width:50%" value='"""+authorrsslink+"""'> """+Button("Author", authorrsslink, "link")+""" <br> <img style="vertical-align: middle" src="/icon/internet"> <input class="button" style="width:50%" value='"""+rsslink+"""'> """+Button("Website", rsslink, "link")+""" </details> """ # Share on mastodon try: mastoname = mastohead(user.get("mastodon", "")) if mastoname.startswith("@"): mastoname = mastoname[1:] musername, mastoinstane, *rest = mastoname.split("@") except: mastoinstane = "" html = html + """ <details> <summary class="button"> <img class="icon" style="vertical-align: middle" src="/icon/mastodon"> Share on Mastodon </summary> <br> <form action="/mastodon_share"> <img style="vertical-align: middle" src="/icon/mastodon"> <input class="button" required name="instance" style="width:50%" placeholder="Instance ( mastodon.social )" value='"""+mastoinstane+"""'> <input type=hidden name="article" value=\""""+tab+'/'+article+""""> <button class="button" type="submit"> <img class="icon" style="vertical-align: middle" src="/icon/send"> Share </button> </form> </details> </center> <br><br> </div> <!-- Render.ArticlePage ( /Share Box ) --> """ # Comments comments = Articles.get(article, {}).get("comments", {}).get("comments", []) html = html + CommentInput(server, url, 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, comments=comments) # 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 + '\n<!-- Render.ArticlePage ( /Main Section ) -->\n\n<!-- Render.ArticlePage ( Suggestions Section ) -->\n<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 + '\n<!-- Render.ArticlePage ( /Suggestions Section ) -->\n' html = html + Footer(server) html = html + LoginButton(server) send(server, html, 200) def AccountPage(server, account): user = validate(server.cookie) 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 + '<!-- Render.AccountPage -->' 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>' # Protecting emails and stuff from scrubbers if isHuman(server): # 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/email">' html = html + '<a href="mailto:'+email+'"> '+email+'</a>' if Accounts.get(account, {}).get("email_verified"): html = html + '<img title="Email Verified!" style="vertical-align: middle" src="/icon/ok">' html = html + '</center>' # Mastodon mastodon = Safe(Accounts.get(account, {}).get("mastodon" , "")) if mastodon: # It could be mastodon url and not handle. try: Mastodon = mastohead(mastodon) Mastolink = mastolink(Mastodon) html = html + '<center>' html = html + '<img style="vertical-align: middle" src="/icon/mastodon">' html = html + '<a href="'+Mastolink+'"> '+Mastodon+'</a>' html = html + '</center>' except Exception as e: print(e) # PeerTube peertube = Safe(Accounts.get(account, {}).get("peertube" , "")) if peertube: # It could be mastodon url and not handle. try: PeerHead = peerhead(peertube) PeerLink = peerlink(PeerHead) html = html + '<center>' html = html + '<img style="vertical-align: middle" src="/icon/peertube">' html = html + '<a href="'+PeerLink+'"> '+PeerHead+'</a>' html = html + '</center>' except Exception as e: print(e) # GNU Jami jami = Safe(Accounts.get(account, {}).get("jami" , "")) if jami: html = html + '<center>' html = html + '<img style="vertical-align: middle" src="/icon/jami">' html = html + '<input class="button" style="width:50%;" value="'+jami+'">' html = html + '</center>' # 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, jami)): html = html + '<br>' else: html = html + '<center><a href="'+server.path+'"><small>Contact Information Protected from Bots</small></a></center><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>' # 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 + Button("Posts by "+Safe(Accounts.get(account, {}).get("title", account)), "/search?author=on&text="+Safe(account), "search") # 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 + '<br><br></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] redirect = server.parsed.get("redirect", [""])[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 + """ <!-- Render.LoginPage --> <div class="middle_section_article"> <div class="dark_box"> <form action="do_login" method="post"> <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> <div class="button"> <input type="checkbox" name="logout"> <label>Log Out Other Sessions</label> </div> <br><br> """ if redirect: html = html + '<input type="hidden" name="redirect" value="'+Safe(redirect)+'">' html = html + """ <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 + """ <!-- Render.RegisterPage --> <div class="middle_section_article"> <div class="dark_box"> <form action="do_register" method="post"> """ 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", ""), "user") # Main settings html = html + """ <!-- Render.SettingsPage --> <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="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> <center> <textarea class="toot" rows="10" style="width:95%" maxlength="10000" placeholder="Short Bio" 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/email"> <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/peertube"> <input class="button" style="width:90%" maxlength="200" name="peertube" placeholder="PeerTube Handle" value='"""+user.get("peertube", "")+"""'> <img style="vertical-align: middle" src="/icon/jami"> <input class="button" style="width:90%" maxlength="200" name="jami" placeholder="GNU Jami Indentifier" value='"""+user.get("jami", "")+"""'> <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> """ if user.get("email"): if not user.get("email_verified"): html = html + """ <div class="dark_box" id="email"> <center><h2>Email Settings</h2> <br> Email """+Safe(user.get("email"))+""" is not verified. <br><br> """+Button("Verify", "/email_verify", "ok")+""" </center> </div> """ else: html = html + """ <div class="dark_box" id="email"> <form action="email_update"> <center><h2>Email Settings</h2> <br> Email """+Safe(user.get("email"))+""" is verified! <br><br> </center> </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 class="dark_box"> <center><h2>Change Password</h2></center> <form action="change_password" method="post"> <img style="vertical-align: middle" src="/icon/unlock"> <input class="button" style="width:90%" maxlength="200" required type="password" name="password" title="Old Password" placeholder="Old Password"> <img style="vertical-align: middle" src="/icon/lock"> <input class="button" style="width:90%" maxlength="200" required type="password" name="new_password" title="New Password" placeholder="New Password"> <button class="button" type="submit"> <img style="vertical-align: middle" src="/icon/ok"> Change </button> </form> </div> """ # FEDERATION STUFF if rank(user.get("username")) == 0: fremove = server.parsed.get("federation_remove", [""])[0] fapprove = server.parsed.get("federation_approve", [""])[0] fban = server.parsed.get("federation_ban", [""])[0] funban = server.parsed.get("federation_unban", [""])[0] fautooff = server.parsed.get("federation_active_off",[""])[0] fautoon = server.parsed.get("federation_active_on", [""])[0] if fremove: Federation.Remove(fremove) if fapprove: Federation.Add(fapprove) if fban: Federation.Ban(fban) if funban: Federation.UnBan(funban) if fautoon: Federation.AutoOn() if fautooff: Federation.AutoOff() if any((fremove, fapprove, fban, funban,fautooff, fautoon)): Redirect(server, "/settings#federation") config = Set.Load() federationData = config.get("federation", {}) html = html + '<div class="dark_box" id="federation">' html = html + '<center><h2>Federation Settings</h2></center>' # Auto federation button auto = federationData.get("auto", False) if auto: html = html + Button("Auto Approve New Sites", "/settings?federation_active_off=True#federation", "checked") else: html = html + Button("Auto Approve New Sites", "/settings?federation_active_on=True#federation", "unchecked") html = html + '<br><br>' # Add form html = html + """ <form action="/settings"> <input name="federation_approve" class="button" required="" style="width:50%" placeholder="blenderdumbass.org"> <button class="button" type="submit"> <img style="vertical-align: middle" src="/icon/new"> Add </button> </form> """ html = html + '<div class="dark_box">' html = html + '<br><center>Federating:</center><br><br>' known = federationData.get("known_websites", []) for website in known: clear = urllib.parse.quote_plus(website) html = html + Button("Remove", "/settings?federation_remove="+clear+"#federation", "cancel") html = html + website + "<br>" html = html + '</div><div class="dark_box">' html = html + '<br><center>Suggested:</center><br><br>' suggested = federationData.get("suggested_websites", []) for website in suggested: clear = urllib.parse.quote_plus(website) html = html + Button("Approve", "/settings?federation_approve="+clear+"#federation", "ok") html = html + Button("Ban", "/settings?federation_ban="+clear+"#federation", "cancel") html = html + website + "<br>" html = html + '</div><div class="dark_box">' html = html + '<br><center>Banned:</center><br><br>' banned = federationData.get("banned_websites", []) for website in banned: clear = urllib.parse.quote_plus(website) html = html + Button("UnBan", "/settings?federation_unban="+clear+"#federation", "ok") html = html + website + "<br>" html = html + '</div>' html = html + '</div>' html = html + '</div>' html = html + '<div class="checklist_section_article">' html = html + Button("+ Image", "/upload_image", "image_new", newpage=True)+'<br><br>' notifications = user.get("notifications","") if notifications: 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></div>' html = html + '</div>' 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 + """ <!-- Render.EditorPage --> <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 )"> """ hashtags = "" for n, i in enumerate(article.get("hashtags", [])): comma = "" if n: comma = " , " hashtags = hashtags + comma+"#"+i.replace("'", "'") if name: html = html + '<input type="hidden" name="name" value="'+name.replace("'", "'")+'">' 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", "").replace("'", "'")+"""'> <br><br> <details> <summary class="button"> <img alt="[icon new]" style="vertical-align: middle" src="/icon/new"> Optional </summary> <br><br> <img style="vertical-align: middle" src="/icon/image_link"> <input class="button" style="width:90%" name="thumbnail" placeholder="Thumbnail" title="A link to the image which will be rendered alongside the title. A visual representation of the post." value='"""+article.get("thumbnail", "").replace("'", "'")+"""'> <img style="vertical-align: middle" src="/icon/copy_file"> <input class="button" style="width:90%" list="Licenses" name="license" placeholder="License" title="A copyright license for the post. So that the reader would know what are the terms when copying the text." value='"""+article.get("license", "").replace("'", "'")+"""'> <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="Sound" title="The link to sound recording of the text of the post." value='"""+article.get("recording", "").replace("'", "'")+"""'> <img style="vertical-align: middle" src="/icon/hashtag"> <input class="button" style="width:90%" name="hashtags" placeholder="Hashtags" title="Hashtags. Use , to separate. Use of # is optional." value='"""+hashtags+"""'> <img alt="[icon ok]" style="vertical-align: middle" src="/icon/ok"> <input class="button" style="width:90%" name="not_ai_proof" placeholder="Not AI Proof" title="Link to a proof that this work is not AI Generated." value='"""+article.get("not_ai_proof", "")+"""'> <br> <img alt="[icon frase]" style="vertical-align: middle" src="/icon/frase"> <input class="button" style="width:90%" name="is_reply_to" placeholder="Reply to" title="Link to a post, to which this post is a direct reply." value='"""+article.get("is_reply_to", "")+"""'> <br> <center> <textarea class="toot" rows="5" style="width:95%" name="description" placeholder="Description">"""+article.get("description", "")+"""</textarea> </center> """ # Optional Petition if rank(user.get("username","")) == 0: petition = article.get("petition", {}) petition_goal = petition.get("goal", "") petition_api = petition.get("api", {}).get("api", "") petition_api_key = petition.get("api", {}).get("keys", [""]) petition_api_title = petition.get("api", {}).get("title", "") petition_api_link = petition.get("api", {}).get("link", "") if len(petition_api_key) == 1: petition_api_key = petition_api_key[0] else: key = petition_api_key[0] for n, i in petition_api_key: if n != 0: key = key + "/" + i petition_api_key = key html = html + """ <details> <summary class="button"> <img alt="[icon petition]" style="vertical-align: middle" src="/icon/petition"> Petition </summary> <div class="dark_box"> <br> <img alt="[icon ok]" style="vertical-align: middle" src="/icon/ok"> <input class="button" type="number" style="width:90%" name="petition_goal" placeholder="Goal ( How Many Signatures )" value='"""+str(petition_goal)+"""'> <br> <details> <summary class="button"> <img alt="[icon analytics]" style="vertical-align: middle" src="/icon/analytics"> From API </summary> <br> <img alt="[icon link]" style="vertical-align: middle" src="/icon/link"> <input class="button" style="width:90%" name="petition_api" placeholder="API to check ( should be JSON )" value='"""+petition_api+"""'> <br> <img alt="[icon checlist]" style="vertical-align: middle" src="/icon/checklist"> <input class="button" style="width:90%" name="petition_api_key" placeholder="Key To Look For ( split with / for nested deep data )" value='"""+petition_api_key+"""'> <br> <img alt="[icon scene]" style="vertical-align: middle" src="/icon/scene"> <input class="button" style="width:90%" name="petition_api_title" placeholder="Statistic's Name" value='"""+petition_api_title+"""'> <br> <img alt="[icon link]" style="vertical-align: middle" src="/icon/link"> <input class="button" style="width:90%" name="petition_api_link" placeholder="Action Link ( where people can increase the number in the API )" value='"""+petition_api_link+"""'> <br> </div> </details> """ html = html + """ </details> <br> <center> <textarea class="toot" rows="30" style="width:95%" required="" name="text" placeholder="Text of your post">"""+Safe(text)+"""</textarea> </center> <button class="button" type="submit"> <img class="icon" style="vertical-align: middle" src="/icon/scene_new"> Publish </button> </div> </div> """ html = html + '<div class="checklist_section_article">' html = html + Button("+ Image", "/upload_image", "image_new", newpage=True)+'<br><br>' html = html + '</div>' html = html + LoginButton(server) send(server, html, 200) def PluginListPage(server): user = validate(server.cookie) plugins = Plugins.GetData() config = Set.Load() # Generating <head> html = head(title = "Plugins", description = "Plugins that are installed on this website.", config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) html = html + """ <!-- Render.PluginListPage --> <br> <br> <!-- Article previews are neatly positioned into a grid here --> """ html = html + '<div class="middle_box">' html = html + '<h2 id="installed"> Installed plugins:</h2>' html = html + '<div class="flexity">' installed = [] for plugin in plugins: installed.append(plugins[plugin].get("sha512")) plugins[plugin]["installed"] = True plugins[plugin]["deletelink"] = plugin.replace(".py", "") html = html + PluginPreview(plugin, plugins[plugin], deletable=rank(user.get("username")) == 0) + "\n" html = html + '</div>' html = html + '</div><br>' # Suggest new plugins. if rank(user.get("username")) == 0: Pdata = {} #Pdata[config.get("domain", "")] = plugins try: with open(Set.Folder()+"/plugins.json") as o: Ndata = json.load(o) for i in Ndata: Pdata[i] = Ndata[i] except: pass Sorted = {} for domain in Pdata: for pluginname in Pdata[domain]: plugin = Pdata[domain][pluginname] if plugin.get("sha512") and plugin.get("sha512") not in Sorted: plugin["title"] = plugin.get("title", pluginname) plugin["domains"] = [] plugin["url"] = "https://"+domain+"/plugins/"+pluginname Sorted[plugin.get("sha512")] = plugin plugin = Sorted[plugin.get("sha512")] plugin["domains"].append(domain) # This magic makes those with more installs go first in the searches. Sorted = {k:Sorted[k] for k in sorted(Sorted, key=lambda y: len(Sorted[y]["domains"]), reverse=True) } html = html + '<div class="middle_box">' html = html + '<h2 id="installed"> Available plugins:</h2>' html = html + '<div class="flexity">' for plugin in Sorted: if Sorted[plugin].get("sha512") not in installed: html = html + PluginPreview(plugin, Sorted[plugin], deletable=True) + "\n" html = html + '</div>' html = html + '</div><br>' html = html + Footer(server) html = html + LoginButton(server) send(server, html, 200) def InstallPluginPage(server, sha512): # Page that let's you install the plugin in the ned user = validate(server.cookie) # Authorization check if not user: AccessDenied(server) return if rank(user.get("username","")) != 0: AccessDenied(server) return config = Set.Load() # Generating <head> html = head(title = "Intall Plugins", description = "Plugin verification page.", config = config ) # Get the plugin code. try: with open(Set.Folder()+"/plugins.json") as o: Pdata = json.load(o) except: Pdata = {} code = "" sha512Test = False sha256Test = False md5Test = False for domain in Pdata: for pluginname in Pdata[domain]: plugin = Pdata[domain][pluginname] if plugin.get("sha512") == sha512: code = API.Get("https://"+domain+"/plugins/"+pluginname, raw=True).decode() # Security testing sha512Test = hashlib.sha512(code.encode("utf-8")).hexdigest() == plugin.get("sha512") sha256Test = hashlib.sha256(code.encode("utf-8")).hexdigest() == plugin.get("sha256") md5Test = hashlib.md5(code.encode("utf-8")).hexdigest() == plugin.get("md5") if all((sha512Test, sha256Test, md5Test)): break tempsave = open(Set.Folder()+"/temporary_plugin_code.py", 'w') tempsave.write(code) tempsave.close() html = html + '<div class="middle_box">' html = html + '<center><h1>Check The Plugin!</h1></center>' html = html + "<br>Some plugins might be malicious. This plugin is going to run with the server. Please double-check that it doesn't do anything you don't like.<br><br>" html = html + '<a href="https://codeberg.org/blenderdumbass/BDServer/wiki/Plugins" target="_blank">How BDServer Plugins Work?</a><br><br>' # Security if sha512Test: html = html + '<img style="vertical-align: middle" atl=[OKAY] src="/icon/ok">' html = html + ' SHA512 verified!' else: html = html + '<img style="vertical-align: middle" atl=[NO] src="/icon/cancel">' html = html + ' SHA512 verification failed!' html = html + '<br>' if sha256Test: html = html + '<img style="vertical-align: middle" atl=[OKAY] src="/icon/ok">' html = html + ' SHA256 verified!' else: html = html + '<img style="vertical-align: middle" atl=[NO] src="/icon/cancel">' html = html + ' SHA256 verification failed!' html = html + '<br>' if md5Test: html = html + '<img style="vertical-align: middle" atl=[OKAY] src="/icon/ok">' html = html + ' MD5 verified!' else: html = html + '<img style="vertical-align: middle" atl=[NO] src="/icon/cancel">' html = html + ' MD5 verification failed!' html = html + '<br>' # Other tests LICENSE = plugin.get("license","Unknown") if isFreeSoftware({"licenses":[LICENSE]}): html = html + '<img style="vertical-align: middle" atl=[OKAY] src="/icon/ok">' html = html + " " + LICENSE + ' License. Plugin seems to be Libre!' else: html = html + '<img style="vertical-align: middle" atl=[NO] src="/icon/cancel">' html = html + " " + LICENSE + ' License. Plugin seem to be not Libre!' html = html + '<br>' # Other tests if plugin.get("author"): html = html + '<img style="vertical-align: middle" atl=[OKAY] src="/icon/ok">' html = html + ' Includes author credit: '+User(plugin.get("author")) else: html = html + '<img style="vertical-align: middle" atl=[NO] src="/icon/cancel">' html = html + ' No author credit!' html = html + '<br><br>' html = html + "Source code: https://"+domain+"/plugins/"+pluginname html = html + '<pre>'+markdown.code_highlight(code)+'</pre>' html = html + "<br><br>Do not install the plugin unless you areabsolutely sure you want this code to run as a part of your server.<br><br>" html = html + '<br><center>'+Button("Install", "/do_install_plugin/"+(pluginname.replace(".py", "")), "ok") + '</center><br>' html = html + '</div>' html = html + Footer(server) html = html + LoginButton(server) send(server, html, 200) def DoInstallPlugin(server, name): name = name + ".py" tempsave = open(Set.Folder()+"/temporary_plugin_code.py", 'r') tempsave = tempsave.read() while name in os.listdir(Set.Folder()+"/plugins/"): name = name.replace(".py","")+"_another.py" save = open(Set.Folder()+"/plugins/"+name, 'w') save.write(tempsave) save.close() Redirect(server, "/plugins", time=1) os.execl(sys.executable, *sys.orig_argv) def DoDeletePlugin(server, name): user = validate(server.cookie) # Authorization check if not user: AccessDenied(server) return if rank(user.get("username","")) != 0: AccessDenied(server) return plugins = Plugins.GetData() config = Set.Load() name = name + ".py" original = Set.Folder()+"/plugins/"+name tempsave = open(original, 'r') tempsave = tempsave.read() while name in os.listdir(Set.Folder()+"/deleted_plugins/"): name = name.replace(".py","")+"_another.py" save = open(Set.Folder()+"/deleted_plugins/"+name, 'w') save.write(tempsave) save.close() os.remove(original) # Generating <head> html = head(title = "Plugins Deleted!", description = "Plugins Deleted!.", config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) html = html + '<div class="middle_box">' html = html + '<h1>Plugin Removed!</h1><br>' html = html + '<code>'+original+'</code>' html = html + '<br><br>was moved to ' html = html + '<br><br><code>'+Set.Folder()+"/deleted_plugins/"+name+'</code>' html = html + '<br><br> which effectively disabled it.' html = html + '<br><br> It was not fully deleted in case you didn\'t really want to delete it.' html = html + '<br><br> To fully delete it empty the' html = html + '<br><br><code>'+Set.Folder()+'/deleted_plugins/</code>' html = html + '<br><br> directory on the server.<br>' html = html + '</div>' html = html + Footer(server) html = html + LoginButton(server) send(server, html, 200) # Restarting server def restart(): time.sleep(2) os.execl(sys.executable, *sys.orig_argv) t = threading.Thread(target=restart, daemon=True) t.start() def ImageUploadPage(server): user = validate(server.cookie) # Authorization check if not user: AccessDenied(server) return config = Set.Load() html = head(title = "Image Upload", description = "Image Upload", config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) html = html + """ <!-- Render.ImageUploadPage --> <div class="middle_section_article"> <div class="dark_box"> <center> <h2>Upload an image</h2> Up to 3MB ( png, jpg, webm, gif ) <br><br> <form action="upload_image_do" method="post" enctype="multipart/form-data"> <input class="button" name="file" type="file" required accept=".png,.jpg,.webm,.gif" > <br> <button class="button" type="submit"> <img class="icon" style="vertical-align: middle" src="/icon/image_new"> Upload </button> </form> </div></div> """ send(server, html, 200) def ImageUploadDo(server): user = validate(server.cookie) # Authorization check if not user: AccessDenied(server) return config = Set.Load() sfolder = "/pictures/user_upload/"+Safe(user.get("username"))+"/" folder = Set.Folder()+sfolder try: os.makedirs(folder) except: pass fullimagelink = "" for i in server.parsed: b0 = b"Content-Type:" if b0 in i: f = i[i.find(b0)+len(b0):] f = f[f.find(b"\r")+4:] f = f[:f.rfind(b"\r")] ftypes = i[:i.find(b0)].decode() ftype = "" for i in [".png", ".jpg", ".png", ".gif"]: if i in ftypes: ftype = i break if not ftype: AccessDenied(server) return filename = RandString(16)+ftype t = open(folder+filename, "wb") t.write(f) t.close() fullimagelink = sfolder+filename if not fullimagelink: AccessDenied(server) return html = head(title = "Image Upload", description = "Image Upload", config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) html = html + """ <div class="middle_section_article"> <div class="dark_box"> """ html = html + '<center><br>' html = html + '<img src="'+fullimagelink+'">' html = html + '<br><br>Markdown Embed code<br>' html = html + '<input class="button" style="width:90%" value="">' html = html + '<br><br>Image URL<br>' html = html + '<input class="button" style="width:90%" value="'+fullimagelink+'">' html = html + '<br><br>Global Image URL<br>' html = html + '<input class="button" style="width:90%" value="https://'+config.get("domain", "example.com")+fullimagelink+'">' html = html + '<br><br>'+Button("+ Image", "/upload_image", "image_new", newpage=True)+'<br><br>' html = html + '</center></div></div>' send(server, html, 200) ### def Button(text, link, icon="", image="", rel="", newpage=False): # rel tag is useful for mastodon website verifications reltext = "" if rel: reltext = 'rel="'+rel+'" ' if newpage: reltext = reltext + ' target="_blank" ' html = """<!-- Render.Button --> <a """+reltext+"""class="button" href=\""""+link+"""">""" if icon: html = html + """ <img class="icon" alt="[icon """+icon+"""]" src="/icon/"""+icon+"""" style="vertical-align: middle">""" if image: html = html + """ <img alt="[icon """+icon+"""]" src=\""""+image+"""" style="height:40px;vertical-align: middle">""" html = html + """ """+text+"""</a> <!-- /Render.Button --> """ return html def ArticlePreview(article, Tabs, cookie=""): html = """ <!-- Render.ArticlePreview --> <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 alt="[icon '+tab+']" src="/icon/'+Tabs.get(tab, {}).get("icon", "folder")+'" style="vertical-align: middle">' html = html + article.get("title", "")+"</h1></a>"+sup+"\n" if article.get("thumbnail"): html = html + '<center><a href="'+url+'"><img alt="[thumbnail]" src="'+article["thumbnail"]+'"></a></center>' petition = article.get("petition", "") if petition: try: frac = petition.get("signed", 0) / int(petition.get("goal", 1)) except: frac = 0 html = html + '<br>'+ProgressBar(frac) html = html + "<br><center><small>"+str(petition.get("signed", 0))+" / "+Safe(str(petition.get("goal", 1)))+" Signatures</small><br>" 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>" hashtags = article.get("hashtags", []) if hashtags: html = html + '<center>' for tag in hashtags: html = html + '<a href="/search?text=%23'+tag+'&tags=on"><small>#'+tag+'</small></a> ' html = html + '</center><br><br>' html = html + '</div>\n<!-- /Render.ArticlePreview -->\n' return html def Mastoview(mastopost, user="", repost=False): html = """ <!-- Render.Mastoview --> <div class="article_box"> """ url = mastopost.get("url", "") content = mastopost.get("content", "") if content: html = html + '<a href="'+url+'">' if not repost: html = html + '<h1><img alt="[icon mastodon]" src="/icon/mastodon" style="vertical-align: middle">' html = html + ' On Mastodon</h1>' else: html = html + "<i>...repost</i><br><br>" if content: html = html + "</a>" # repost = 🔁 try: media = mastopost.get("media_attachments")[0] except: media = {} if media.get("type", "") == "image": mediaURL = media.get("preview_url", "") if not mediaURL: media.get("url", "") html = html + '<center><a href="'+url+'"><img alt="[thumbnail]" src="'+mediaURL+'"></a></center>' if not repost: html = html + '<br><br><center>'+User(user)+'</center><br><br>' else: html = html + '<br><br><center>'+user+'</center><br><br>' if content: likes = mastopost.get("favourites_count", 0) comments = mastopost.get("replies_count", 0) reposts = mastopost.get("reblogs_count", 0) html = html + '<center>⭐ '+str(likes)+' 💬 '+str(comments)+' 🔁 '+str(reposts)+'</center><br><br>' if not content and mastopost.get("reblog", ""): content = Mastoview(mastopost.get("reblog", ""), mastopost.get("reblog", {}).get("account", {}).get("acct", ""), True) html = html + content html = html + '</div>\n<!-- /Render.Mastoview -->\n' return html def PluginPreview(url, plugin, deletable=False): html = """ <!-- Render.PluginPreview --> <div class="article_box"> """ if not plugin.get("url"): html = html + '<a href="/plugins/'+url+'"><h1>' else: html = html + '<a href="'+plugin.get("url")+'"><h1>' codeis = "<code>"+str(plugin.get("sha512", "!!!!"))[:4]+'</code>' html = html + '<img alt="[icon]" src="/icon/python" style="vertical-align: middle">' html = html + plugin.get("title", url)+"</h1><center>"+codeis+"</center></a>\n" author = plugin.get("author", "") if author: html = html + '<br><center>'+User( author )+'</center>' html = html + "<br>"+markdown.convert(str(plugin.get("description", "")), False)+"<br>" domains = plugin.get("domains", []) if domains: html = html + '<details><summary>'+str(len(domains))+' uses</summary>' for website in domains: image = "https://"+website+"/pictures/favicon.png" wurl = "https://"+website button = Button(website, wurl, image=image, newpage=True) html = html + button + "<br>\n" html = html + '</details>\n' for i in ("sha512","sha256", "md5"): html = html + '<details><summary>'+i+'</summary>' html = html + plugin.get(i, "no data") html = html + '</details>\n' if not plugin.get("installed") and plugin.get("sha512"): html = html + Button("Install", "/install_plugin/"+plugin.get("sha512"), "download") elif deletable: html = html + Button("Remove", "/do_delete_plugin/"+plugin.get("deletelink", ""), "cancel") html = html + '<br></div>\n<!-- /Render.PluginPreview -->\n' return html def Footer(server): html = """ <!-- Render.Footer --> <center> """ html = html + Button("Powered with BDServer", "https://codeberg.org/blenderdumbass/BDServer/", "codeberg") if Plugins.Plugins: html = html + Button("Plugins", "/plugins", "python") html = html + Button("Analytics", "/analytics", "analytics") config = Set.Load() account = config.get("main_account", "") Accounts = accounts() if account in Accounts: if isHuman(server): email = Accounts[account].get("email") if email: html = html + Button("Contact Admin", "mailto:"+email, "email") mastodon = Accounts[account].get("mastodon") if mastodon: html = html + Button("Mastodon", mastolink(mastohead(mastodon)), "mastodon", rel="me") peertube = Accounts[account].get("peertube") if peertube: html = html + Button("PeerTube", peerlink(peertube), "peertube") matrix = Accounts[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 + Button("Matrix", matrixlink, "element") html = html + """ </center> <!-- /Render.Footer --> """ return html def StreamAlerts(server): html = "<!-- Render.StreamAlerts -->" config = Set.Load() account = config.get("main_account", "") Accounts = accounts() if account in Accounts: peertube = Accounts[account].get("peertube") if peertube: streaming = API.IsPeerSreaming(peerhead(peertube)) if streaming and type(streaming) == list: stream = streaming[0] url = stream.get("url", "") title = stream.get("name", "") html = html + '<div class="toot">' html = html + '<a class="button" style="background-color:ff2200;">LIVE!</a> ' html = html + '<b>'+Safe(title)+'</b>' html = html + Button("Watch!", Safe(url), "peertube") html = html + '</div><br>' html = html + "<!-- /Render.StreamAlerts -->" return html def User(username, stretch=False): if type(username) == str: try: with open(Set.Folder()+"/accounts/"+username+".json") as o: account = json.load(o) except: account = {} else: account = username username = account.get("username", "") # We are doing a lot of reductions in case somebody sneaks html code. avatar = Safe(account.get("avatar", "")) if not avatar: avatar = "/icon/user" username = Safe(username) title = Safe(account.get("title", username)) if account: if not account.get("url"): url = '/account/'+username else: url = account["url"] html = '<!-- Render.User --><img alt="[avatar]" style="height:50px;vertical-align: middle" src="'+avatar+'"> <a href="'+url+'">'+title+'</a></center><!-- /Render.User -->\n' else: html = '<!-- Render.User --><img alt="[avatar]" style="height:50px;vertical-align: middle" src="'+avatar+'"> '+title+'</center><!-- /Render.User -->\n' return html def Graph(server, url): if url.endswith(".md"): url = url.replace(".md", "") # If there are any values after ? in the path # which means, that somebody is sending the old # version of the graph link from the legacy code # we should not count it as a view. if "?" in server.path: AccessDenied(server) return # Since /graph/ is used to count views # we need the cookie to be generated and # used by the user's browser before we load # it, since a lot of people might just click # the link once. In which case the entire page # including graph loads before the cookie. if not server.cookie and server.headers.get("user-agent", "") not in ProblematicRefreshes: server.isPage = True Redirect(server, server.path) ProblematicRefreshes.append(server.headers.get("user-agent", "")) return if server.headers.get("user-agent", "") in ProblematicRefreshes: ProblematicRefreshes.remove(server.headers.get("user-agent", "")) # Sometimes scrapers try to load graph without # loading the article first. We don't want to count # it as a view. if time.time()-30 > RecentArticles.get(url, 0): print(consoleForm(server.cookie), "Article wasn't loaded, scrapers!") AccessDenied(server) return user = validate(server.cookie) # Store general analytics about which search engines were used. # To get to this article. referrer = RefferedArticles.get(url, "") html = """ <!-- Render.Graph --> <head> <link media="all" href="/css" type="text/css" rel="stylesheet" /> <!-- CSS theme link --> </head> <style> html { background-color: none; background-image: none; } </style> """ try: with open(Set.Folder()+"/tabs"+url+"/metadata.json") as o: article = json.load(o) except: article = {} dateformat = "%Y-%m-%d" dates = article.get("views", {}).get("dates", {}) if dates: largest = max(dates.values()) 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: via = server.headers.get("Via", "") if " | " not in StripUserAgent(str(server.headers.get("User-Agent")), via): break if cookie in humanCheckCache: humanCheckCache[cookie]["human"] = True else: humanCheckCache[cookie] = {"human":True} if cookie and cookie not in article.get("views", {}).get("viewers", []): article["views"]["amount"] += 1 article["views"]["viewers"].append(cookie) if "referrers" not in article["views"]: article["views"]["referrers"] = {} if referrer and referrer not in article["views"]["referrers"]: article["views"]["referrers"][referrer] = 0 if referrer: article["views"]["referrers"][referrer] += 1 dates = article["views"]["dates"] date = datetime.now().strftime(dateformat) dates[date] = dates.get(date, 0) + 1 server.newview = True with open(Set.Folder()+"/tabs"+url+"/metadata.json", "w") as save: json.dump(article, save, indent=4) # If the cookie got all the way here, it is probably loaded by a person ProbablyHumanCookies.append(server.cookie) html = html + "<!-- /Render.Graph -->" send(server, html, 200) def CommentInput(server, url, comments): user = validate(server.cookie) try: comment_reply = int(server.parsed.get("comment_reply", [""])[0]) except: comment_reply = None textis = "" if comment_reply != None: try: comment = comments[comment_reply] author = comment.get("username", "") if type(author) != str: try: author = author.get("username", "author") except:author = "author" textis = "c:"+str(comment_reply)+"\n\n@"+author+" " except Exception as e: print("comment reply error", e) html = """ <!-- Render.CommentInput --> <div class="dark_box" id="comments"> """+HelpButton(server, "markdown")+""" <br><center> <form action="/comment"> <input type="hidden" id="url" name="url" value=\""""+url+""""> """ if not user: html = html + '<br><br><br><br><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" placeholder="Write comment...">"""+textis+"""</textarea> <br> <button class="button" type="submit"> <img class="icon" style="vertical-align: middle" src="/icon/frase"> Post </button> </form> </center> </div> <!-- /Render.CommentInput --> """ return html def CommentEditInput(server, comment, url, n=0, user=None, request=None): Accounts = accounts() html = '<!-- Render.CommentEditInput -->\n<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><!-- /Render.CommentEditInput -->' return html def Comment(comment, url, n=0, user={}, request=False, comments=[], noreplies=False, noreference=None, nohashlink=False): if not nohashlink: if not request: html = '<!-- Render.Comment -->\n<div class="dark_box" id="comment_'+str(n)+'">' else: html = '<!-- Render.Comment -->\n<div class="dark_box" id="request_'+str(n)+'">' else: html = '<!-- Render.Comment -->\n<div class="dark_box">' account = comment.get("username", "Anonymous User") html = html + User(account) if not request: html = html + ' <a class="sup" href="#comment_'+str(n)+'">c:'+str(n)+'</a>' html = html + '<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, comments={"url":url,"comments":comments, "noreference":noreference})+'<br>' if warning: html = html + '</details>' externalurl = Safe(comment.get("url", "")) if externalurl: html = html + Button("View Full Reply", externalurl, "internet" ) # Replies if not noreplies: replies = [] commentID = 'c:'+str(n) for c in comments: if commentID in c.get("text"): replies.append(c) if replies: html = html + """ <details> <summary> ... replies ( """+str(len(replies))+""" ) </summary> <br> """ rt = "" for c in replies: # rt = rt + "c:"+str(comments.index(c))+"\n" # html = html + markdown.convert(Safe(rt), False, comments={"url":url,"comments":comments, "noreference":n})+'<br>' try: comment = Comment(c, url, n=comments.index(c), comments=comments, noreference=n, noreplies=True, nohashlink=True) html = html + comment + "<br>" except: pass html = html + """ </details><br><br> """ html = html + Button("Reply", '/'+url+'?comment_reply='+str(n)+'#comments', "send" ) Moderates = moderates(user.get("username"), account) if Moderates: html = html + Button("Edit", '/'+url+'?comment_edit='+str(n)+'#comment_'+str(n), "edit" ) if Moderates or rank(user.get("username", "")) == 0: html = html + Button("Delete", '/delete_comment?url='+urllib.parse.quote_plus(url)+'&number='+str(n), "cancel" ) if externalurl: html = html + "<br><br><i>If Deleted comment will re-appear, unless you remove the domain from federation.</i>" html = html + '</div>\n<!-- /Render.Comment -->\n' return html def LoginButton(server): user = validate(server.cookie) html = '<!-- Render.LoginButton -->\n<div class="login_fixed">' if not user: html = html + '<a class="button" href="/login?redirect='+urllib.parse.quote_plus(server.path)+'">' html = html + '<img alt="[icon user]" 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 alt="[avatar]" 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><!-- /Render.LoginButton -->' return html def ProgressBar(frac): title = str(round(frac*100,1))+"%" frac = min(1, frac) frac = max(0, frac) html = '<!-- Render.ProgressBar -->\n<div title="'+title+'" class="back_progress">' html = html + '<div title="'+title+'" class="front_progress", style="width:'+str(frac*100)+'%">' html = html + '</div></div>\n<!-- /Render.ProgressBar -->\n' 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 Error(server, text="Some Error Happened."): config = Set.Load() html = head(title = "501 Error", description = "501 Error", config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) html = html + """ <center> <div class="article_box"> <h1>501 Error</h1> """+text+""" <br><br> </center> """ send(server, html, 501) def FreeCompetitor(free, nonfree, score): html = """ <!-- Render.FreeCompetitor --> <div class="article_box"> """ html = html + '<h1><img alt="[icon fc]" src="/icon/fc" style="vertical-align: middle">' html = html + free.get("names", ["Software"])[0]+'</h1>' icon = free.get("links", {}).get("icon", "") if icon: html = html + '<center><img alt="[thumbnail]" style="min-width:80%;" src="'+icon+'"></center>' text = '<br><br>To replace <b>'+nonfree.get("names", ["The Software"])[0]+'</b>' text = text +' you can use <b>'+free.get("names", ["Software"])[0]+'</b>' text = text +' since it <a href="https://www.gnu.org/philosophy/free-sw.html">respects the user\'s freedom</a> and ' features = [] for feature in free.get("generic_name", []): if feature.startswith("*"): features.append(feature[1:]) oformats = [] for f in free.get("formats_read", []): if f.startswith("*"): oformats.append(f[1:]) sformats = [] for f in free.get("formats_write", []): if f.startswith("*"): sformats.append(f[1:]) onetworks = [] for f in free.get("networks_read", []): if f.startswith("*"): onetworks.append(f[1:]) snetworks = [] for f in free.get("networks_write", []): if f.startswith("*"): snetworks.append(f[1:]) if features: text = text + 'is also a' for n, feature in enumerate(features): AND = " " if n != 0: AND = ', ' if n and n == len(features)-1: AND = ' and ' text = text + AND + feature + " software" text = text +"." if any((oformats, sformats, onetworks, snetworks)): text = text + "<br><br>Also it " if oformats: text = text + 'reads '+str(len(oformats))+' of the same formats as ' text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>' text = text + ' such as: ' for n, f in enumerate(oformats): if n == 4: break AND = " " if n: AND = ', ' if n and ( n == len(oformats)-1 or n == 3 ): AND = ' and ' text = text + AND + f.upper() if sformats: text = text + ' and ' else: text = text + '.' if sformats: text = text + 'saves to '+str(len(sformats))+' of the same formats as ' text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>' text = text + ' such as: ' for n, f in enumerate(sformats): if n == 4: break AND = " " if n: AND = ', ' if n and ( n == len(sformats)-1 or n == 3 ): AND = ' and ' text = text + AND + f.upper() text = text + '.' if any((oformats, sformats)) and any((onetworks, snetworks)): text = text + "<br><br>Also it " if onetworks: text = text + 'can access data from '+str(len(onetworks))+' of the same network protocols as ' text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>' text = text + ' such as: ' for n, f in enumerate(onetworks): if n == 4: break AND = " " if n: AND = ', ' if n and ( n == len(onetworks)-1 or n == 3 ): AND = ' and ' text = text + AND + f.upper() if snetworks: text = text + ' and ' else: text = text + '.' if snetworks: text = text + 'can publish data to '+str(len(snetworks))+' of the same network protocols as ' text = text + '<b>'+nonfree.get("names", ["The Software"])[0]+'</b>' text = text + ' such as: ' for n, f in enumerate(snetworks): if n == 4: break AND = " " if n: AND = ', ' if n and ( n == len(snetworks)-1 or n == 3 ): AND = ' and ' text = text + AND + f.upper() text = text + '.' text = text + '<br><br>' if free.get("issues", []): text = text + 'Unfortunately though ' text = text +'<b>'+free.get("names", ["Software"])[0]+'</b>' text = text +' is not without issues: <br><center>' for issue in free.get("issues", []): text = text + '<div class="toot">' text = text + '<img alt="[icon bug]" src="/icon/bug" style="vertical-align: middle"> '+issue+"<br>" text = text + '</div><br>' text = text + '</center>' text = text + '<center>' for link in free.get("links", {}): if link == "icon": continue name = link.replace("git", "source code").replace("fsd", "FSD") if name.lower() == name: name = name[0].upper()+name[1:] text = text + Button(name, free["links"][link], "internet") text = text + '</center>' html = html + text html = html + '</div>' html = html + '<br><br><!-- /Render.FreeCompetitor -->' return html ### def Redirect(server, url, time=0): print(" "+consoleForm(server.cookie), clr["tbbl"]+"Redirecting to: "+url) html = """<meta http-equiv="Refresh" content=\""""+str(time)+"""; url='"""+url.replace("'", "%27").replace('"', "%22")+"""'" />""" 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() redirect = server.parsed.get("redirect" , [""])[0] logout = server.parsed.get("logout" , [""])[0] Accounts = accounts() # Failed authentication if username not in Accounts or hashed != Accounts[username].get("password"): Redirect(server, "/login?wrong=username") # Succesfull authentication else: ProbablyHumanCookies.append(server.cookie) 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" # 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) if logout: for cookie in list(account["sessions"].keys()): if cookie != server.cookie: del account["sessions"][cookie] with open(folder+"/"+username+".json", "w") as save: json.dump(account, save, indent=4) if not redirect: Redirect(server, "/settings") else: Redirect(server, redirect) 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", "jami", "peertube" ] for key in keys: data = server.parsed.get(key, [""])[0] # Making sure to reverify email. if key == "email" and user[key] != data: user["email_verified"] = False 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 UpdatePassword(server): user = validate(server.cookie) old_password = server.parsed.get("password", [""])[0] new_password = server.parsed.get("new_password", [""])[0] old_hashed = hashlib.sha512(old_password.encode("utf-8")).hexdigest() new_hashed = hashlib.sha512(new_password.encode("utf-8")).hexdigest() # Validating the user's password if user.get("password", "") == old_hashed: user["password"] = new_hashed 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") AccessDenied(server) 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>" if granted or revoked: Notify(account, "/account/"+account, text) def DoComment(server): # Limiting bots from commenting if not isHuman(server): AccessDenied(server) return 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 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] # Hashtags hashtext = server.parsed.get("hashtags", [""])[0] hashtags = [] for i in hashtext.split(","): tag = i.strip().replace("#", "") if tag: hashtags.append(tag) # Petition data petition_goal = server.parsed.get("petition_goal", [""])[0] petition_api = server.parsed.get("petition_api", [""])[0] petition_api_key = server.parsed.get("petition_api_key", [""])[0] petition_api_title = server.parsed.get("petition_api_title", [""])[0] petition_api_link = server.parsed.get("petition_api_link", [""])[0] # Reply data is_reply_to = server.parsed.get("is_reply_to", [""])[0] # Not AI not_ai_proof = server.parsed.get("not_ai_proof", [""])[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 metadata["hashtags"] = hashtags metadata["not_ai_proof"] = not_ai_proof # Petition if petition_goal: petition = metadata.get("petition", { "signed":0, "signatures":[] }) try: petition["goal"] = int(petition_goal) except: petition["goal"] = 1 # API petition if petition_api: petition["api"] = { "api" :petition_api, "keys" :petition_api_key.split("/"), "title":petition_api_title, "link" :petition_api_link } metadata["petition"] = petition else: try: del metadata["petition"] except: pass # Reply if is_reply_to: try: replydata = API.GetMetadata(is_reply_to) except: replydata = {} metadata["is_reply_to"] = is_reply_to metadata["reply_to_data"] = replydata # 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)) if metadata.get("petition"): metadata["url"] = "/"+tab+"/"+name try: API.Petition(metadata) except Exception as e: Error(server, "Cannot Load API Value<br>\n"+Safe(str(e))) return Redirect(server, "/"+tab+"/"+name) 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", "")) or rank(user.get("username", "")) == 0: 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) def TimeDifference(timeA, timeB): text = "" if timeA < timeB: if timeB - timeA < 10: text = "now" return text elif timeB - timeA < 60: text = str(int(timeB - timeA))+" seconds ago" return text else: if int( ( timeB - timeA ) / 60 ) == 1: text = str(int( ( timeB - timeA ) / 60 ))+" minute ago" else: text = str(int( ( timeB - timeA ) / 60 ))+" minutes ago" return text else: if timeA - timeA < 10: text= "now" return text elif timeA - timeB < 60: text = "in "+str(int(timeA - timeB))+" seconds" return text else: if int( ( timeA - timeB ) / 60 ) == 1: text = "in "+str(int( ( timeA - timeB ) / 60 ))+" minute" else: text = "in "+str(int( ( timeA - timeB ) / 60 ))+" minutes" return text def RSS(server): # Rendering rss feed. Articles = allArticles() Accounts = accounts() config = Set.Load() owner = config.get("main_account", "") favicon = config.get("favicon", "") if favicon.startswith("/"): favicon = "https://"+config.get("domain", "example.com")+favicon author = server.parsed.get("author", [""])[0] title = config.get("title", "My Website") if author: account = Accounts.get(author, {}) title = account.get("title", author)+" at: "+title rss = """<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"> <channel> <title>"""+title+"""</title> <link>https://"""+config.get("domain", "example.com")+"""</link> <description>"""+config.get("tagline", "")+"""</description> <image> <url>"""+favicon+"""</url> <title>"""+title+"""</title> <link>https://"""+config.get("domain", "example.com")+"""</link> </image> """ # Peertube stream notification if owner in Accounts: peertube = Accounts[owner].get("peertube") if peertube: streaming = API.IsPeerSreaming(peerhead(peertube)) if streaming and type(streaming) == list: stream = streaming[0] url = stream.get("url", "") title = "LIVE NOW! "+stream.get("name", "") description = stream.get("description", "") pubDate = time.time() pubDate = datetime.fromtimestamp(pubDate) pubDate = email.utils.format_datetime(pubDate) # Rand insures that the stream RSS notification # will also be shown to the subscribers during # the stream. By slightly changing the URL all # the time. rand = RandString(10) rss = rss + """ <item> <title>"""+title.replace("&", "&")+"""</title> <link>"""+url+"""?rand="""+rand+"""</link> <guid>"""+url+"""?rand="""+rand+"""</guid> <description><![CDATA["""+markdown.convert(description, False)+"""]]></description> <pubDate>"""+pubDate+"""</pubDate> </item> """ n = 0 for article in Articles: if author and author != Articles[article].get("author", ""): continue n += 1 if n > 10: break pubDate = Articles[article].get("timestamp", 0) pubDate = datetime.fromtimestamp(pubDate) pubDate = email.utils.format_datetime(pubDate) thumbnail = Articles[article].get("thumbnail", "") if thumbnail.startswith("/"): thumbnail = "https://"+config.get("domain", "example.com")+thumbnail rss = rss + """ <item> <title>"""+Articles[article].get("title", article).replace("&", "&")+"""</title> <link>https://"""+config.get("domain", "example.com")+Articles[article].get("url", "")+"""</link> <guid>https://"""+config.get("domain", "example.com")+Articles[article].get("url", "")+"""</guid> <media:thumbnail url=\""""+thumbnail+""""/> <description><![CDATA["""+markdown.convert(Articles[article].get("description", article), False, fullpath=True)+"""]]></description> <pubDate>"""+pubDate+"""</pubDate> </item> """ rss = rss + """ </channel> </rss> """ send(server, rss, 200) def Search(server): Articles = allArticles() Tabs = tabs() config = Set.Load() # Free Competitors support FreeCompetitors = config.get("free_competitors", "") # Generating <head> html = head(title = "Search", description = "", config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) # The place where you can type your search text = server.parsed.get("text",[""])[0] try: page = int(server.parsed.get("page", ["0"])[0]) except Exception as e: print(e) page = 0 searchtitle = server.parsed.get("title",[""])[0] searchauthor = server.parsed.get("author",[""])[0] searchpost = server.parsed.get("post",[""])[0] searchdescription = server.parsed.get("description",[""])[0] searchcomments = server.parsed.get("comments",[""])[0] searchtags = server.parsed.get("tags",[""])[0] searchfc = server.parsed.get("fc",[""])[0] # Supporting legacy search links if not any([searchtitle, searchauthor, searchpost, searchfc, searchdescription, searchcomments, searchtags ]): searchtitle = True searchpost = True searchdescription = True searchcomments = True searchfc = True searchtags = True checkedtitle = "" if searchtitle: checkedtitle = " checked " checkedfc = "" if searchfc: checkedfc = " checked " checkedauthor = "" if searchauthor: checkedauthor = " checked " checkedpost = "" if searchpost: checkedpost = " checked " checkeddescription = "" if searchdescription: checkeddescription = " checked " checkedcomments = "" if searchcomments: checkedcomments = " checked " checkedtags = "" if searchtags: checkedtags = " checked " html = html + """ <center> <form action="/search"> <div class="toot"> <input name="text" class="button" placeholder="Search..." value='"""+text+"""'> <button class="button" type="submit"> <img class="icon" style="vertical-align: middle" src="/icon/search"> Search </button> <br> """ if FreeCompetitors: html = html + """ <div class="button"> <input type="checkbox" """+checkedfc+""" name="fc"> Free Software </div>""" html = html + """ <div class="button"> <input type="checkbox" """+checkedtitle+""" name="title"> Title </div> <div class="button"> <input type="checkbox" """+checkedauthor+""" name="author"> Author </div> <div class="button"> <input type="checkbox" """+checkedpost+""" name="post"> Post Text </div> <div class="button"> <input type="checkbox" """+checkeddescription+""" name="description"> Description </div> <div class="button"> <input type="checkbox" """+checkedcomments+""" name="comments"> Comments </div> <div class="button"> <input type="checkbox" """+checkedtags+""" name="tags"> Hashtags </div> </div> </form> </center> """ # Acutally doing the searching counted = [] for n, article in enumerate(Articles): points = 0 sortpoints = (len(Articles)-n)/100 # Title x 100 points if searchtitle: title = Articles[article].get("title", article) points += title.lower().count(text.lower()) * 100 # Description x 10 points if searchdescription: description = Articles[article].get("description", "") points += description.lower().count(text.lower()) * 10 # Hashtags x 10 points if searchtags: hashtags = Articles[article].get("hashtags", []) for tag in hashtags: if tag.lower() == text.strip().replace("#", "").lower(): points += 10 # Author if searchauthor: author = Articles[article].get("author", "") if author == text: points += 2 # Perfect match with username preffered # People might also look at the username Accounts = accounts() if text.lower() == Accounts.get(author, {}).get("title", "").lower(): points += 1 # Comments x 1 if searchcomments: comments = Articles[article].get("comments", {}).get("comments", {}) for comment in comments: commentText = comment.get("text", "") points += commentText.lower().count(text.lower()) # Post Text x 1 if searchpost: try: f = Set.Folder() url = Articles[article].get("url") postText = open(f+"/tabs/"+url+"/text.md").read() points += postText.lower().count(text.lower()) except Exception as e: print(e) if points: points = points + sortpoints counted.append([points, article]) counted = list(reversed(sorted(counted))) Buffer = 16 From = Buffer*page To = From+Buffer urlNoPage = server.path if "page=" in urlNoPage: urlNoPage = urlNoPage[:urlNoPage.rfind("&")] if len(list(counted)) > Buffer: if page > 0: html = html + Button(str(page-1), urlNoPage+"&page="+str(page-1), "left") html = html + '<div class="button">'+str(page)+'</div>' if To < len(list(counted))-1: html = html + Button(str(page+1), urlNoPage+"&page="+str(page+1), "right") if searchfc and FreeCompetitors: html = html + '<center><div class="toot">' html = html + ' The Free Software Search is Powered by ' html = html + Button("Free Competitors", "https://notabug.org/jyamihud/FreeCompetitors", "fc") html = html + '</div></center>' html = html + """ <br> <br> <!-- Article previews are neatly positioned into a grid here --> <div class="flexity"> """ # Free Competitors Search if searchfc: try: fcdata = API.Get(FreeCompetitors+"/json/"+text.replace(" ", "+")) except: fcdata = {} fcMatch = 0.5 # If we found a match in software if fcdata.get("found", {}).get("match", 0) >= fcMatch: fcSearchMatch = max( x[0] for x in fcdata.get("suggestions",[])) * 0.2 for soft in fcdata.get("suggestions",[]): if fcSearchMatch > soft[0] or not isFreeSoftware(soft[1]): continue if soft[1].get("names", [""])[0] in fcdata.get("found", {}).get("data", {}).get("names", []): continue html = html + FreeCompetitor(soft[1], fcdata.get("found", {}).get("data",{}), soft[0]) rendered = 0 for n, article in enumerate(counted): points, article = article if n < From: continue if n >= To: break html = html + ArticlePreview(Articles[article], Tabs, server.cookie) rendered += 1 html = html + '</div><br>' if len(list(counted)) > Buffer: if page > 0: html = html + Button(str(page-1), urlNoPage+"&page="+str(page-1), "left") html = html + '<div class="button">'+str(page)+'</div>' if To < len(list(counted))-1: html = html + Button(str(page+1), urlNoPage+"&page="+str(page+1), "right") html = html + LoginButton(server) send(server, html, 200) def Totals(): # Renders a little thing which shows website's statistics. totals = Analyse.CollectTotals() html = '<!-- Render.Totals --><center><div class="middle_box">' html = html + '<div class="flexity">' stuff = { "views":{ "icon":"view", "title":"Views", "description":"Estimated views of all posts."}, "posts":{ "icon":"scene", "title":"Posts", "description":"Overall posts on this website."}, "users":{ "icon":"user", "title":"Users", "description":"Invited and registered users."}, "subscribers":{ "icon":"rss", "title":"Subscribers", "description":"Estimated RSS subscribers."}, "comments":{ "icon":"frase", "title":"Comments", "description":"All comments on all posts."}, "signatures":{ "icon":"petition", "title":"Signatures", "description":"All Signatures on all Petitions."}, "mastodon":{ "icon":"mastodon", "title":"Mastodon", "description":"Mastodon followers."}, "peertube":{ "icon":"peertube", "title":"PeerTube", "description":"PeerTube Subscribers."}, "matrix":{ "icon":"element", "title":"Matrix", "description":"People in the Matrix chat."} } for n, i in enumerate(stuff): if i == "mastodon": html = html + '</div><div class="flexity">' if i in totals: html = html + '<div class="article_box" title="'+stuff[i]["description"]+'">' html = html + '<h2><img src="/icon/'+stuff[i]["icon"]+'" style="vertical-align: middle"><br>' html = html + str(totals[i])+'</h2>' html = html + stuff[i]['title']+'<br><br>' html = html + '</div>' html = html + '</div>' html = html + Button("JSON", "/json/analytics/totals", "terminal") html = html + '</div></center><!-- /Render.Totals -->' return html def Graphed(data, percents=True, icons={}): # Draws a progress barred graph of things html = "" for i in data: icon = "" if i in icons: icon = '<img alt="[icon]" style="vertical-align: middle" src="/icon/' + icons[i] + '"> ' html = html + '<b>'+ icon + Safe(i) + "</b> " + str(round(data[i], 1)) + "%" + '<br><br>' html = html + ProgressBar(data[i]/100) html = html + "<br>" return html def AnalyticsPage(server): # The page that draws analytics config = Set.Load() Articles = allArticles() Tabs = tabs() # Generating <head> html = head(title = "Analytics", description = "Analytics", config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) # Totals html = html + Totals() # Operating systems and browsers osbr = Analyse.UserAgents(10, skip_me=True, human_only=True, force_percent=True, onlydata=True) html = html + '<div class="middle_box" >' html = html + '<h2 id="oss"> Operating Systems:</h2>' html = html + Graphed(osbr["oss"], percents=True, icons={"GNU / Linux":"gnu_linux", "Windows":"windows", "iOS":"apple", "macOS":"apple", "Chrome OS":"chromium", "Android":"android"}) html = html + '<br>' html = html + '<h2 id="browsers"> Web Browsers:</h2>' html = html + Graphed(osbr["browsers"],percents=True, icons={"Firefox":"firefox", "Chromium":"chromium", "Safari":"safari", "Ms Edge":"edge", "Opera":"opera", "Old Opera":"opera"}) html = html + """ <form action="/json/analytics/ua"> <input class="button" name="days" placeholder="Days" title="Days"> <div class="button" title="Skip Owner"> <input type="checkbox" checked name="skip_me"> Skip Owner </div> <div class="button" title="Humans Only"> <input type="checkbox" checked name="human_only"> Humans Only </div> <div class="button" title="Percents"> <input type="checkbox" checked name="force_percent"> Percents </div> <button class="button" type="submit"> <img class="icon" style="vertical-align: middle" src="/icon/terminal"> JSON </button> </form> """ html = html + '</div>' # Trends html = html + '<div class="middle_box" >' trendingHashtags = Analyse.Trends(10, cap=20, tags=True, normalize=True, onlydata=True) trendingArticles = Analyse.Trends(10, cap=6, onlydata=True) html = html + '<h2 id="trends" >Trends:</h2>' html = html +'Showing 20 most viewed hashtags in the last 10 days ( normalized ).<br><br>' html = html + '<center>' for tag in trendingHashtags: tag = tag.replace("#", "") html = html + '<a href="/search?text=%23'+tag+'&tags=on"><small>#'+tag+'</small></a> ' html = html + '</center>' html = html +'<br><br>Showing 6 most viewed articles in the last 10 days.<br><br>' html = html + '<div class="flexity">' for article in trendingArticles: html = html + ArticlePreview(Articles[article], Tabs, server.cookie) html = html + '</div>' html = html + """ <form action="/json/analytics/trends"> <input class="button" style="width:200px;" name="days" placeholder="Days" title="Days"> <input class="button" style="width:200px;" name="cap" placeholder="Publications" title="Publications"> <div class="button" title="Tags"> <input type="checkbox" name="tags"> Tags </div> <div class="button" title="Normalize Tags ( divide views of a tag per frequancy, helps to see not what the author like to publish, but what people like to read )"> <input type="checkbox" name="normalize"> Normalize Tags </div> <button class="button" type="submit"> <img class="icon" style="vertical-align: middle" src="/icon/terminal"> JSON </button> </form> """ html = html + '</div>' # Federated websites html = html + '<div class="middle_box" >' html = html + '<h2 id="federation"> Federated with:</h2>' fedata = API.GiveJSON("federate/").get("known_websites",[]) for website in fedata: image = "https://"+website+"/pictures/favicon.png" url = "https://"+website button = Button(website, url, image=image, newpage=True) html = html + button + " " html = html + '\n<br><center>'+Button("JSON", "/json/federate/", "terminal")+'</center><br>\n' html = html + '</div>' html = html + Footer(server) html = html + LoginButton(server) send(server, html, 200) def ShareMastodon(server): # Does the share of the share button instance = server.parsed.get("instance", [""])[0] article = server.parsed.get("article" , [""])[0] config = Set.Load() Articles = allArticles() data = Articles.get("/"+article) if not data: Error(server, "Article does not exist.") return if not instance: Error(server, "No Mastodon Instace Provided.") return text = RenderMastondShareText(data) if not instance.startswith("http"): instance = "https://"+instance link = instance+"/share?text="+urllib.parse.quote_plus(text) Redirect(server, link) def RenderMastondShareText(data): config = Set.Load() link = "https://"+config.get("domain", "")+data.get("url", "") tags = data.get("hashtags", []) hashtags = "" for tag in tags: hashtags = hashtags + "#"+tag+" " bottompart = link+"\n\n"+hashtags upper = "From: "+config.get("title", "")+"\n\n" readorlisten = "Read" if data.get("recording"): readorlisten = readorlisten + " or listen" readorlisten = readorlisten + ": " sofar = 23 + 4 + len(hashtags)+ len(upper) + len(readorlisten) left = 500 - sofar bottompart = readorlisten + bottompart description = StripMarkdown(data.get("description", "")).strip() if len(description) <= left: text = upper + description + "\n\n" + bottompart else: text = upper + description[:left-3] + "...\n\n" + bottompart return text def StripMarkdown(text): page = markdown.Open(text) text = "" for i in page: if str(i[0]).startswith("text") or i[0] == "link": text = text + i[1] elif type(i[0]) == int: text = text + i[1].upper() return text def Help(server): # Renders a help menu. topic = server.parsed.get("topic", [""])[0] redirect = server.parsed.get("redirect",[""])[0] try: text = markdown.convert("help/"+topic+".md") except: text = "The topic you are looking for is not documented." config = Set.Load() f = Set.Folder() # Generating <head> html = head(title = "Help "+topic, description = "Help "+topic, config = config ) html = html + Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet")) html = html + '<div class="middle_section_article">' html = html + '<div class="dark_box"> ' html = html + text if redirect: html = html + '</div><br>' html = html + Button("Go Back", redirect, "ok") html = html + '<br><br></div>' html = html + LoginButton(server) send(server, html, 200) def HelpButton(server, topic, endlink=""): url = '/help?topic='+topic if endlink: url = url+'&redirect='+urllib.parse.quote_plus(server.path+endlink) html = '' #html = html + '<div class="login_fixed">' html = html + Button("Help", url, icon="question", newpage = not endlink ) html = html + '<br><br>' return html