# 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("'", "&apos;")
        
    if name:
        html = html + '<input type="hidden" name="name" value="'+name.replace("'", "&apos;")+'">'
        
    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("'", "&apos;")+"""'>
    
    <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("'", "&apos;")+"""'>

    <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("'", "&apos;")+"""'>
    <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("'", "&apos;")+"""'>
    
    <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="![Image]('+fullimagelink+')">'

    
    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> &nbsp; Unread &nbsp; </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+'">&nbsp;&nbsp;<a href="'+url+'">'+title+'</a></center><!-- /Render.User -->\n'
    else:
        html = '<!-- Render.User --><img alt="[avatar]" style="height:50px;vertical-align: middle" src="'+avatar+'">&nbsp;&nbsp;'+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> &nbsp; Pending Approval &nbsp; </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> &nbsp; Pending Approval &nbsp; </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("&", "&amp;")+"""</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("&", "&amp;")+"""</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