Initial commit.

This commit is contained in:
2026-01-21 13:12:04 -05:00
commit 1e9e5a5925
190 changed files with 25707 additions and 0 deletions
+519
View File
@@ -0,0 +1,519 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import json
import time
import email
import random
import hashlib
import subprocess
import urllib.parse
import urllib.request
import urllib3
from datetime import datetime
from modules import Set
from modules import Analyse
from modules import Federation
from modules import Render
from modules import ActivityPub
from modules import markdown
from modules import Plugins
from modules.Common import *
API_cache = {}
def Get(url, data=None, headers={}, raw=False, timeout=10):
config = Set.Load()
if data:
try:
data = json.dumps(data).encode("utf-8")
except:
try:
data = data.encode("utf-8")
except:
pass
#if not headers:
# headers = {"User-Agent":"BDServer ( "+config.get("domain", "")+" )"}
if not data:
#print("Using urllib3")
req = urllib3.PoolManager()
f = req.request("GET", url, headers=headers, timeout=timeout)
if raw:
return f.data
data = json.loads(f.data.decode("utf-8"))
else:
#print("Using urllib")
req = urllib.request.Request(url, data=data, headers=headers)
f = urllib.request.urlopen(req, timeout=timeout)
if raw:
return f.read()
data = json.loads(f.read().decode('utf-8'))
return data
def Value(url, keys, data={}, timeout=10):
if not data:
data = Get(url, timeout)
for key in keys:
data = data[key]
return data
def Petition(article):
petition = article.get("petition", {})
api = petition.get("api", {})
timestamp = api.get("timestamp",0)
# 5 minutes delay
if time.time() - 300 < timestamp:
return
print(api.get("api", ""))
value = int(Value(api.get("api", ""), api.get("keys", []), timeout=10))
article["petition"]["api"]["timestamp"] = time.time()
article["petition"]["signed"] = value
f = Set.Folder()
with open(f+"/tabs"+article.get("url", "")+"/metadata.json", "w") as save:
json.dump(article, save, indent=4)
def PetitionFetch():
Articles = Render.allArticles()
for article in Articles:
petition = Articles.get(article, {}).get("petition", "")
if petition:
if petition.get("api"):
try:
print("Trying to update petition for ", article)
Petition(Articles[article])
except Exception as e:
print("Petition failed with", e)
def Mastodon(mastoname, posts=4):
# This function will get few latest posts
# and return them back to whatever needs
# them.
# First we want to get the mastodon ID of the
# user in question ( mastodon API is strange ).
if mastoname.startswith("@"):
mastoname = mastoname[1:]
username, host, *rest = mastoname.split("@")
if not "mastoid" in API_cache:
url = "https://"+host+"/api/v1/accounts/lookup?acct="+username
ID = Value(url, ["id"])
API_cache["mastoid"] = ID
ID = API_cache["mastoid"]
# Now that we have the ID we can request the actuall
# list that we came here for.
if time.time()-300 > API_cache.get("mastotime", 0):
url = "https://"+host+"/api/v1/accounts/"+str(ID)+"/statuses?inned=false&limit="+str(posts)
data = Get(url)
API_cache["mastotime"] = time.time()
API_cache["mastodata"] = data
return API_cache["mastodata"]
def GetMetadata(url):
config = Set.Load()
if config.get("domain", "") in url:
url = url.replace("https://", "").replace("http://", "").replace(config.get("domain", ""), "")
if url.startswith("/"):
data = GiveJSON(url[1:])
data["domain"] = config.get("domain", "local")
return data
# Check for https://
if not url.startswith(("http://","https://")):
url = "https://"+url
# Cheking if data can be reached.
domain = url.replace("http://", "").replace("https://", "").split("/")[0]
request = url[url.find(domain)+len(domain):]
jsonurl = "https://"+domain+"/json"+request
try:
data = Get(jsonurl)
if data.get("title", ""):
data["domain"] = domain
return data
except:
pass
data = {}
# Check if there is at least ActivityPub data of this article
try:
apdata = Get(url, headers={"Accept": "application/activity+json"})
try:
username = ActivityPub.GetHandle(apdata.get("attributedTo", ""))
except:
username = "Anonymous"
data["title"] = "Fediverse Post"
data["author"] = username
data["description"] = markdown.toMarkdown(apdata.get("content", ""))
data["domain"] = domain
return data
except Exception as e:
print("ER", e)
pass
# If this doesn't work we are going for something else.
# Regular spoofing of User-Agent should do the trick to load various
# pages that do not want to be loaded otherwise.
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0"})
f = urllib.request.urlopen(req, timeout=10)
page = f.read().decode('utf-8')
# Lets find the title
title = ""
if "<title>" in page and "</title>" in page:
title = page[page.find("<title>")+7:page.find("</title>")]
if title:
data["title"] = title
description = ""
meta = '<meta property="og:description" content="'
if meta in page and '">' in page:
description = page[page.find(meta)+len(meta):]
description = description[:description.find('"')]
if description:
data["description"] = description
data["domain"] = domain
fediverse_creator = ""
meta = '<meta name="fediverse:creator" content="'
if meta in page and '" />' in page:
fediverse_creator = page[page.find(meta)+len(meta):]
fediverse_creator = fediverse_creator[:fediverse_creator.find('" />')]
fediverse_creator = Render.mastohead(fediverse_creator)
if fediverse_creator:
try:
mastoname = fediverse_creator
if mastoname.startswith("@"):
mastoname = mastoname[1:]
username, host, *rest = mastoname.split("@")
url = "https://"+host+"/api/v1/accounts/lookup?acct="+username
mastodata = Get(url)
author = {
"avatar":mastodata.get("avatar", ""),
"username":mastodata.get("username",mastoname),
"title":mastodata.get("display_name",mastoname),
"url":mastodata.get("url", Render.mastolink(mastoname)),
}
data["author"] = author
except:
pass
return data
def APISafeAccount(account, config):
# Function making account dicts safe
# to be shared online.
try: del account["password"]
except: pass
try: del account["sessions"]
except: pass
try: del account["invite_codes"]
except: pass
try: del account["email"]
except: pass
try: del account["activity_pub"]["password"]
except: pass
try: del account["activity_pub"]["pem"]
except: pass
try: del account["matrix"]
except: pass
try: del account["forums"]
except: pass
try: del account["notifications"]
except: pass
# Links
if account.get("avatar", "").startswith("/"):
account["avatar"] = "https://"+config.get("domain", "")+account["avatar"]
account["url"] = "https://"+config.get("domain", "")+"/account/"+account.get("username", "")
return account
def APISafeArticle(article, Accounts, config):
# Function that makes an article dictionary
# safe to be sent over the network.
try: del article["views"]["viewers"]
except: pass
try: del article["views"]["referrers"]
except: pass
try: del article["comments"]["requests"]
except: pass
try: del article["petition"]["signatures"]
except: pass
try: article["author"] = APISafeAccount(Accounts[article["author"]], config)
except: pass
if article.get("thumbnail","").startswith("/"):
article["thumbnail"] = "https://"+config.get("domain", "")+article["thumbnail"]
article["url"] = "https://"+config.get("domain", "")+article.get("url","")
return article
def GiveJSON(url, server=None):
# Function returning json for various things.
config = Set.Load()
data = {}
Tabs = Render.tabs()
Accounts = Render.accounts()
f = Set.Folder()
categories = list(Tabs.keys())
if url in categories:
data = Tabs.get(url, {})
Articles = Render.articles(url)
data["posts"] = {}
for art in Articles:
article = APISafeArticle(Articles[art], Accounts, config)
data["posts"][art] = article
elif url.startswith(tuple(categories)):
Articles = Render.allArticles()
article = APISafeArticle(Articles.get("/"+url,{}), Accounts, config)
data = article
elif url.startswith("plugins"):
data = Plugins.GetData()
elif url.startswith("federate/"):
domain = url.split("/")[1]
# add to the list
auto = config.get("federation", {}).get("auto", False)
banned = config.get("federation", {}).get("banned_websites", [])
known = config.get("federation", {}).get("known_websites", [])
d = Federation.Clean(domain)
if d and d not in banned and d not in known and d != config.get("domain"):
if auto:
print(consoleForm("Federation"), "Auto-adding new domain:", d)
Federation.Add(d, silent=True)
else:
print(consoleForm("Federation"), "Suggested new domain:", d)
Federation.Suggest(d, silent=True)
data = {"federating_with":domain}
data["comments"] = {}
Articles = Render.allArticles()
n = 0
for art in Articles:
if Articles[art].get("reply_to_data", {}).get("domain") == domain:
article = APISafeArticle(Articles[art], Accounts, config)
data["comments"][art] = article
# Making sure to not overbombard the servers.
n += 1
if n > 20:
break
data["known_websites"] = known
# Analytics API
elif url.startswith("analytics/totals"):
data = Analyse.CollectTotals()
data["dates"] = Analyse.CollectTotals(everything=True).get("data", {})
elif url.startswith("analytics/rss"):
try:
days = int(server.parsed.get("days",["10"])[0])
except:
days = 10
data = Analyse.EstimateRSSSubscribers(days)
elif url.startswith("analytics/ua"):
try:
days = int(server.parsed.get("days",["10"])[0])
except:
days = 10
skip_me = server.parsed.get("skip_me",[""])[0]
human_only = server.parsed.get("human_only",[""])[0]
force_percent = server.parsed.get("force_percent",[""])[0]
data = Analyse.UserAgents(days, skip_me=skip_me,
human_only=human_only,
force_percent=force_percent,
onlydata=True)
elif url.startswith("analytics/trends"):
try:
days = int(server.parsed.get("days",["10"])[0])
except:
days = 10
try:
cap = int(server.parsed.get("cap",["10"])[0])
except:
cap = 10
tags = server.parsed.get("tags", [""])[0]
normalize = server.parsed.get("normalize", [""])[0]
data = Analyse.Trends(days, cap=cap,
tags=tags,
normalize=normalize,
onlydata=True)
if server:
data = json.dumps(data).encode("utf-8")
Render.headers(server, 200)
server.wfile.write(data)
else:
return data
def PeerStreaming(peerhandle):
# First we will attempt fetching the response as
# if the handle is a channel.
url = "https://"+peerhandle.split("@")[1]+"/api/v1/video-channels/"+peerhandle.split("@")[0]+"/videos?isLive=True"
try:
return Get(url)["data"]
except:
pass
# If that doesn't work, try full account
url = "https://"+peerhandle.split("@")[1]+"/api/v1/accounts/"+peerhandle.split("@")[0]+"/videos?isLive=True"
try:
return Get(url)["data"]
except:
pass
# If nothing works. Returning nothing.( As list to avoid implemenation friction )
return []
def IsPeerSreaming(peerhandle):
# Wrapper over the PeerStreaming.
# To avoid over bombardment of APIs.
data = API_cache.get(peerhandle+"_streaming",{})
if time.time()-300 > data.get("time", 0):
streaming = PeerStreaming(peerhandle)
data = {"streaming":streaming,
"time":time.time()}
API_cache[peerhandle+"_streaming"]= data
streaming = data.get("streaming", [])
return streaming
def GetMatrixMemberts(matrix):
# Getting matrix room ID
url = "https://matrix.org/_matrix/client/v3/directory/room/"+matrix.replace("#","%23")
ID = Value(url, ["room_id"])
# I was trying to find a simple way to get
# matrix API to work easily. Nothing works.
# We will be parsing HTML code from view.matrix.org
# intead.
url = 'https://view.matrix.org/room/'+ID
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0"})
f = urllib.request.urlopen(req, timeout=10)
page = f.read().decode('utf-8')
p1 = ID+'/members">'
p2 = ' Members</a>'
Members = int(page[page.find(p1)+len(p1):page.find(p2)])
return Members
+117
View File
@@ -0,0 +1,117 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import sys
import json
import random
import hashlib
import urllib.parse
from datetime import datetime
from getpass import getpass
from modules import Set
from modules import markdown
from modules.Common import *
def Load(name):
folder = Set.Folder()+"/accounts/"
try:
with open(folder+name+".json") as o:
return json.load(o)
except:
return {}
def Save(account):
name = account.get("username", "")
folder = Set.Folder()+"/accounts/"
try:
with open(folder+name+".json", "w") as save:
json.dump(account, save, indent=4)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Cannot save account!", e)
def Account():
anything = False
if len(sys.argv) < 3:
anything = True
from modules import Help
Help.Accounts()
if "--list" in sys.argv:
anything = True
List()
elif "--add" in sys.argv:
anything = True
try:
account = sys.argv[ sys.argv.index("--add") + 1]
if "--" in account: 1/0 # Failing this for the error message.
Add(account)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Account name wasn't specified!")
print('Use: $ python3 run.py --account --add blenderdumbass')
elif "--edit" in sys.argv:
anything = True
try:
account = sys.argv[ sys.argv.index("--edit") + 1]
if "--" in account: 1/0 # Failing this for the error message.
Edit(account)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Account name wasn't specified!")
print('Use: $ python3 run.py --account --edit blenderdumbass')
if not anything:
print(clr["bold"]+clr["tdrd"]+"Unknown command!"+clr["norm"])
def List():
folder = Set.Folder()+"/accounts"
for account in os.listdir(folder):
if account.endswith(".json"):
print(account.replace(".json", ""))
def Add(name):
# Adds a new account.
account = Load(name)
if account:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Account already exists.")
account = {
"username":name,
"bio":"",
"invite_codes":{},
"invited":[],
"invited_by":"",
"password":hashlib.sha512(getpass("Account's Password: ").encode("utf-8")).hexdigest(),
"title":name,
"email":"",
"website":"",
"mastodon":"",
"matrix":"",
"sessions":{}
}
Save(account)
print(clr["bold"]+clr["tbyl"]+name+clr["norm"]+" is added."+clr["norm"])
def Edit(name):
folder = Set.Folder()+"/accounts/"
os.system(Set.Load().get("editor", "nano")+" "+folder+name+".json")
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+380
View File
@@ -0,0 +1,380 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import time
import random
import threading
clr = {
"norm":"\033[00m", # Reset to normal
"bold":"\033[01m", # Bold Text
"ital":"\033[03m", # Italic Text
"undr":"\033[04m", # Underlined
"blnk":"\033[05m", # Blinking
# Text
"tdbl":"\033[30m", # Dark Black
"tdrd":"\033[31m", # Dark Red
"tdgr":"\033[32m", # Dark Green
"tdyl":"\033[33m", # Dark Yellow
"tdbu":"\033[34m", # Dark Blue
"tdma":"\033[35m", # Dark Magenta
"tdcy":"\033[36m", # Dark Cyan
"tdwh":"\033[37m", # Dark White
"tbbl":"\033[90m", # Bright Black
"tbrd":"\033[91m", # Bright Red
"tbgr":"\033[92m", # Bright Green
"tbyl":"\033[93m", # Bright Yellow
"tbbu":"\033[94m", # Bright Blue
"tbma":"\033[95m", # Bright Magenta
"tbcy":"\033[96m", # Bright Cyan
"tbwh":"\033[97m", # Bright White
# Background
"bdbl":"\033[40m", # Dark Black
"bdrd":"\033[41m", # Dark Red
"bdgr":"\033[42m", # Dark Green
"bdyl":"\033[43m", # Dark Yellow
"bdbu":"\033[44m", # Dark Blue
"bdma":"\033[45m", # Dark Magenta
"bdcy":"\033[46m", # Dark Cyan
"bdwh":"\033[47m", # Dark White
"bbbl":"\033[100m", # Bright Black
"bbrd":"\033[101m", # Bright Red
"bbgr":"\033[102m", # Bright Green
"bbyl":"\033[103m", # Bright Yellow
"bbbu":"\033[104m", # Bright Blue
"bbma":"\033[105m", # Bright Magenta
"bbcy":"\033[106m", # Bright Cyan
"bbwh":"\033[108m" # Bright White
}
def Simplify(text, extrasimple=True):
good = "QWERTYUIOPLKJHGFDSAZXCVBNMqwertyuiopasdfghjklzxcvbnm.1234567890-_:* "
if extrasimple == "file":
good = "QWERTYUIOPLKJHGFDSAZXCVBNMqwertyuiopasdfghjklzxcvbnm.1234567890-_"
elif extrasimple:
good = "qwertyuiopasdfghjklzxcvbnm.1234567890-_:"
text = text.lower()
ntext = ""
for i in text:
if i in good:
ntext = ntext + i
else:
ntext = ntext + "_"
return ntext
def Safe(text):
return str(text).replace("<", "&lt").replace(">", "&gt")
def RandString(n=50):
# this will make a random string
s = ""
good = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
for i in range(n):
s = s + random.choice(good)
return s
IDColors = {" ANONYMOUS ":clr["norm"]}
def stripDomain(domain):
if "://" in domain:
domain = domain[domain.find("://")+3:]
if "/" in domain:
domain = domain[:domain.find("/")]
return domain
def StripUserAgent(text, via=""):
if "Googlebot" in text:
return "[ Google ]"
if "YandexBot" in text:
return "[ Yandex ]"
if "MYOB/6.66 (AN/ON)" in text:
return "[ i2p ]"
if "Amazonbot" in text:
return "[ Amazon ]"
if "bingbot" in text:
return "[ Bing ]"
if "Bytespider" in text:
return "[ Bytespider ]"
if "FlipboardProxy" in text:
return "[ FlipboardProxy ]"
if "archive.org" in via:
return "[ Wayback Machine ]"
if "facebookexternalhit" in text:
return "[ Facebook / Threads / Instagram ]"
if "Mastodon" in text:
s = "https://"
e = ")"
if s in text:
link = text[text.find(s)+len(s):]
if e in text:
link = link[:link.find(e)].replace("/", "")
return "[ Mastodon: "+link+"]"
return "[ Mastodon ]"
if "Lemmy" in text:
s = "https://"
e = ")"
if s in text:
link = text[text.find(s)+len(s):]
if e in text:
link = link[:link.find(e)].replace("/", "")
return "[ Lemmy: "+link+"]"
return "[ Lemmy ]"
if "Friendica" in text:
s = "https://"
e = ")"
if s in text:
link = text[text.find(s)+len(s):]
if e in text:
link = link[:link.find(e)].replace("/", "")
return "[ Friendica: "+link+"]"
return "[ Friendica ]"
# Browsers
browsers = {
"Edg/":"Ms Edge",
"OPR/":"Opera",
"Presto":"Old Opera",
"PaleMoon":"Palemoon",
"Firefox":"Firefox",
"Chrome":"Chromium",
"AppleWebKit":"Safari",
"Safari":"Safari"
}
browser = ""
for i in browsers:
if i in text:
browser = browsers[i]
break
# Operating system
oss = {
"Android":"Android",
"CrOS":"Chrome OS",
"Linux":"GNU / Linux",
"Windows":"Windows",
"iPad":"iOS",
"iPhone":"iOS",
"Mac OS":"macOS"
}
os = ""
for i in oss:
if i in text:
os = oss[i]
break
if os and browser:
return "[ "+browser+" | "+os+" ]"
return "[ "+text+" ]"
humanCheckCache = {}
pathsHTML = {}
def RecordHTML(path, html):
pathsHTML[path] = html
def GuessHuman(server):
via = server.headers.get("Via", "")
useragent = str(server.headers.get("User-Agent"))
if " | " not in StripUserAgent(useragent, via):
return False
if not server.cookie:
return False
if time.time() -120 > humanCheckCache.get(server.cookie,{}).get("timestamp", 0)\
and not humanCheckCache.get(server.cookie,{}).get("human", False):
try:
del humanCheckCache[server.cookie]
except:
pass
if server.cookie not in humanCheckCache:
humanCheckCache[server.cookie] = {"human":False,
"path":server.path,
"count":0,
"timestamp":time.time()}
return False
elif humanCheckCache[server.cookie].get("human"):
return True
elif not humanCheckCache[server.cookie].get("human"):
if "?" in server.path:
path = server.path[:server.path.find("?")]
else: path = server.path
if path in pathsHTML.get(humanCheckCache[server.cookie].get("path", "/"),""):
humanCheckCache[server.cookie]["count"] += 1
if humanCheckCache[server.cookie]["count"] > 5:
humanCheckCache[server.cookie]["human"] = True
# Garbage collection
try:
del humanCheckCache[server.cookie]["html"]
except:
pass
return True
def typeOfConnection(server):
if " | " not in StripUserAgent(str(server.headers.get("User-Agent"))):
return clr["norm"]+clr["bold"]+clr["bdrd"]+clr["tbwh"]+" BOT! "+clr["norm"]
try:
if not server.cookie:
return clr["norm"]+clr["bold"]+clr["bdrd"]+clr["tbwh"]+" BOT? "+clr["norm"]
from modules import Render
if Render.validate(server.cookie):
return clr["norm"]+clr["bold"]+clr["bdgr"]+clr["tdbl"]+" LOGD "+clr["norm"]
if GuessHuman(server):
return clr["norm"]+clr["bold"]+clr["bdyl"]+clr["tdbl"]+" HUMN "+clr["norm"]
except Exception as e:
print(e)
return clr["norm"]+clr["bold"]+clr["bdwh"]+clr["tdbl"]+" NEW "+clr["norm"]
def consoleForm(obj):
from modules.Render import accounts
# Username preview
username = ""
Accounts = accounts()
for account in Accounts:
if obj in Accounts[account].get("sessions"):
username = account
break
# Function that prints a pretty object info about
if not obj: obj = " ANONYMOUS "
ID = obj
if not username:
NAME = obj[-14:]
else:
NAME = username[:15].upper()
while len(NAME) < 14:
NAME = NAME + " "
c = IDcolor(ID)
return c + " " + NAME + " " + clr["norm"]
def IDcolor(ID):
# Coloring
if ID not in IDColors:
r = random.random()
g = random.random()
b = random.random()
ct = clr["tbwh"]
if max(r, g, b ) > 0.9 or sum((r, g, b))/3 > 0.5:
ct = clr["tdbl"]
c = clr["bold"] + "\033[48;2;"+str(int(r*256))+";"+str(int(g*256))+";"+str(int(b*256))+"m"+ct
IDColors[ID] = c
c = IDColors[ID]
return c
# Licenses for stuff
Licenses = {
"cc0":{
"name":"CC0 Public Domain",
"link":"https://creativecommons.org/publicdomain/zero/1.0/"
},
"cc-by":{
"name":"Creative Commons Attribution",
"link":"https://creativecommons.org/licenses/by/4.0/"
},
"cc-by-sa":{
"name":"Creative Commons Attribution Share-Alike",
"link":"https://creativecommons.org/licenses/by-sa/4.0/"
},
"gpl3":{
"name":"GNU General Public License Version 3",
"link":"https://www.gnu.org/licenses/gpl.html"
},
"lgpl3":{
"name":"GNU Lesser General Public License Version 3",
"link":"https://www.gnu.org/licenses/lgpl.html"
},
"agpl3":{
"name":"GNU Affero General Public License Version 3",
"link":"https://www.gnu.org/licenses/agpl.html"
},
"gpl3-or-later":{
"name":"GNU General Public License Version 3 or any later version",
"link":"https://www.gnu.org/licenses/"
}
}
def csize(x):
x = float(x)
l = ["B","KB", "MB", "GB", "TB"]
for i in range(5):
if x > 1024:
x = x / 1024
else:
return str(round(x, 2))+" "+l[i]
return str(round(x, 2))+" "+l[i]
locks = {}
def ThreadLock(filename):
try: return locks[filename]
except:
locks[filename] = threading.Lock()
return locks[filename]
+108
View File
@@ -0,0 +1,108 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import json
import time
from modules.Common import *
from modules import Set
def Create(name):
# Creates a base directory before publishing.
if not name.count("/") == 1:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Wrong Format!")
print('Use: $ python3 run.py --create articles/My_Article')
name = Simplify(name.split("/")[0])+"/"+Simplify(name.split("/")[1])
folder = Set.Folder()+"/editing"
try:
os.makedirs(folder+"/"+name)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Can't Create!", e)
# The text file
text = open(folder+"/"+name+"/text.md", "w")
text.close()
metadata = {
"title":name.split("/")[1],
"timestamp":time.time(),
"description":"",
"author":"",
"thumbnail":"",
"license":"cc-by-sa",
"views":{
"amount":0,
"viewers":[],
"dates":{}
},
"recording":"",
"comments":{
"comments":[],
"requests":[]
}
}
with open(folder+"/"+name+"/metadata.json", "w") as save:
json.dump(metadata, save, indent=4)
print(clr["bold"]+clr["tbyl"]+name+clr["norm"]+" is created of editing.")
print(' To write text use: $ python3 run.py --write '+name)
print('To edit metadata use: $ python3 run.py --metadata '+name)
print(' To publish use: $ python3 run.py --publish '+name)
def Write(name):
if not name.count("/") == 1:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Wrong Format!")
print('Use: $ python3 run.py --write articles/My_Article')
name = Simplify(name.split("/")[0])+"/"+Simplify(name.split("/")[1])
folder = Set.Folder()+"/editing/"+name
os.system(Set.Load().get("editor", "nano")+" "+folder+"/text.md")
def Metadata(name):
if not name.count("/") == 1:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Wrong Format!")
print('Use: $ python3 run.py --metadata articles/My_Article')
name = Simplify(name.split("/")[0])+"/"+Simplify(name.split("/")[1])
folder = Set.Folder()+"/editing/"+name
os.system(Set.Load().get("editor", "nano")+" "+folder+"/metadata.json")
def Publish(name):
if not name.count("/") == 1:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Wrong Format!")
print('Use: $ python3 run.py --publish articles/My_Article')
name = Simplify(name.split("/")[0])+"/"+Simplify(name.split("/")[1])
folder = Set.Folder()
source = folder+"/editing/"+name
destination = folder+"/tabs/"+name
try:
os.makedirs(folder+"/tabs/"+name)
os.rename(source+"/text.md", destination+"/text.md")
os.rename(source+"/metadata.json", destination+"/metadata.json")
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Could not publish!", e)
print(destination, "has been published!")
+268
View File
@@ -0,0 +1,268 @@
import smtplib
import email
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import json
from modules import Set
from modules.Common import *
from modules import Render
verifying = {}
def Send(reciever, subject, html, text=""):
config = Set.Load()
Email = config.get("email")
if not Email:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" No email credentials!")
try: port = int(Email.get("port", ""))
except: port = 587
message = MIMEMultipart()
message["From"] = Email["visible"]
message["To"] = reciever
message["Subject"] = subject
message['Date'] = email.utils.formatdate(localtime=True)
message.attach(MIMEText(html, "html"))
if text:
message.attach(MIMEText(text, "plain"))
# Send the email
with smtplib.SMTP(Email["server"], port) as server:
server.starttls()
server.login(Email["sender"],
Email["password"])
server.sendmail(Email["sender"],
reciever,
message.as_string())
print('Email "'+subject+'" was sent to', reciever)
def VerifyPage(server):
user = Render.validate(server.cookie)
# Authorization check
if not user:
Render.AccessDenied(server)
return
config = Set.Load()
# Generating <head>
html = Render.head(title = "Verify Email",
description = "Verify Email",
config = config
)
html = html + Render.Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
code = server.parsed.get("code", [""])[0]
if not code:
html = html + """
<div class="middle_section_article">
<div class="dark_box">
<form action="/email_verify">
<center>
<br>
You have recieved an email with a code.<br><br>
<small>It could be in spam.</small>
<br><br>
<input class="button" name="code" required placeholder="Your code...">
<br>
<button class="button" type="submit">
<img alt="[icon ok]" style="vertical-align: middle" src="/icon/ok">
Verify
</button>
</center>
</form>
</div>
</div>
"""
code = RandString(10)
verifying[code] = user.get("email")
text = """
<center>
Your Verification Code
<br>
<div class="toot">"""+code+"""
</div>
</center>
"""
texthtml = Format(text)
Send(user.get("email", ""), "Verification Code", texthtml, text)
Render.send(server, html, 200)
else:
# If we recieved the code.
if code not in verifying:
Render.Error(server, "Wrong Code!")
return
if verifying[code] != user.get("email"):
Render.Error(server, "Wrong Code!")
return
user["email_verified"] = True
f = Set.Folder()
folder = f+"/accounts"
with open(folder+"/"+user.get("username", "")+".json", "w") as save:
json.dump(user, save, indent=4)
del verifying[code]
Render.Redirect(server, "/settings#email")
def SignPetition(server):
config = Set.Load()
# Generating <head>
html = Render.head(title = "Verify Email",
description = "Verify Email",
config = config
)
html = html + Render.Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
article = server.parsed.get("article", [""])[0]
print(article)
Articles = Render.allArticles()
if article not in Articles:
Render.AccessDenied(server)
return
Article = Articles[article]
code = server.parsed.get("code", [""])[0]
email = server.parsed.get("email", [""])[0]
if email in Article.get("petition", {}).get("signatures", []):
Render.Error(server, "You already signed!")
return
if not code:
html = html + """
<div class="middle_section_article">
<div class="dark_box">
<form action="/sign_petition">
<center>
<br>
You have recieved an email with a code.<br><br>
<small>It could be in spam.</small>
<br><br>
<input type="hidden" name="article" value='"""+article+"""'>
<input class="button" name="code" required placeholder="Your code...">
<br>
<button class="button" type="submit">
<img alt="[icon ok]" style="vertical-align: middle" src="/icon/ok">
Verify
</button>
</center>
</form>
</div>
</div>
"""
code = RandString(10)
verifying[code] = email
text = """
<center>
Your Verification Code
<br>
<div class="toot">"""+code+"""
</div>
</center>
"""
text = Format(text)
Send(email, "Verification Code", text)
Render.send(server, html, 200)
else:
# If we recieved the code.
if code not in verifying:
Render.Error(server, "Wrong Code!")
return
Article["petition"]["signed"] += 1
Article["petition"]["signatures"].append(verifying[code])
f = Set.Folder()
with open(f+"/tabs"+article+"/metadata.json", "w") as save:
json.dump(Article, save, indent=4)
del verifying[code]
Render.Redirect(server, article)
###
def Format(text):
config = Set.Load()
favicon = config.get("favicon", "")
if favicon.startswith("/"):
favicon = "https://"+config.get("domain", "")+favicon
css = open(config.get("css", "default.css"), "r")
html = '<html><style>'+css.read()+'</style>'
html = html + '<div class="dark_box"><center>'
if favicon:
html = html + '<img alt="[favicon]" style="height:40px;vertical-align: middle" src="'+favicon+'">'
html = html + " "+config.get("title", "My Website")
html = html + "</center><br></div>"
html = html + '<div class="dark_box">'
html = html + text
html = html + '</div></html>'
return html
+413
View File
@@ -0,0 +1,413 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import sys
import time
import json
import random
import traceback
import hashlib
import urllib.parse
from datetime import datetime
from getpass import getpass
from modules import Set
from modules import API
from modules import Plugins
from modules import Analyse
from modules import Render
from modules import markdown
from modules import ActivityPub
from modules.Common import *
def Federation():
anything = False
if len(sys.argv) < 3:
from modules import Help
Help.Federation()
anything = True
if "--add" in sys.argv:
anything = True
try:
domain = sys.argv[ sys.argv.index("--add") + 1]
if "--" in domain: 1/0 # Failing this for the error message.
Add(domain)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Domain wasn't specified!")
print('Use: $ python3 run.py --federation --add blog.madiator.com')
if "--remove" in sys.argv:
anything = True
try:
domain = sys.argv[ sys.argv.index("--remove") + 1]
if "--" in domain: 1/0 # Failing this for the error message.
Remove(domain)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Domain wasn't specified!")
print('Use: $ python3 run.py --federation --remove threads.net')
if "--ban" in sys.argv:
anything = True
try:
domain = sys.argv[ sys.argv.index("--ban") + 1]
if "--" in domain: 1/0 # Failing this for the error message.
Ban(domain)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Domain wasn't specified!")
print('Use: $ python3 run.py --federation --ban threads.net')
if "--unban" in sys.argv:
anything = True
try:
domain = sys.argv[ sys.argv.index("--unban") + 1]
if "--" in domain: 1/0 # Failing this for the error message.
UnBan(domain)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Domain wasn't specified!")
print('Use: $ python3 run.py --federation --unban blog.madiator.com')
if "--list" in sys.argv:
anything = True
List()
if "--auto_on" in sys.argv:
anything = True
AutoOn()
if "--auto_off" in sys.argv:
anything = True
AutoOff()
#########
if not anything:
print(clr["bold"]+clr["tdrd"]+"Unknown command!"+clr["norm"])
def Clean(domain):
return domain.replace("https://", "").replace("http://", "")
def Add(domain, silent=False):
domain = Clean(domain)
config = Set.Load()
if domain == config.get("domain"):
return
if "federation" not in config: config["federation"] = {}
if "known_websites" not in config["federation"]: config["federation"]["known_websites"] = []
if "suggested_websites" not in config["federation"]: config["federation"]["suggested_websites"] = []
if "banned_websites" not in config["federation"]: config["federation"]["banned_websites"] = []
if domain not in config["federation"]["known_websites"]:
if domain in config["federation"]["suggested_websites"]:
config["federation"]["suggested_websites"].remove(domain)
if domain in config["federation"]["banned_websites"]:
config["federation"]["banned_websites"].remove(domain)
config["federation"]["known_websites"].append(domain)
Set.Save(config)
if not silent:
print(clr["bold"]+clr["tbyl"]+domain+clr["norm"]+" is added for federation.")
def Suggest(domain, silent=False):
domain = Clean(domain)
config = Set.Load()
if domain == config.get("domain"):
return
if "federation" not in config: config["federation"] = {}
if "suggested_websites" not in config["federation"]: config["federation"]["suggested_websites"] = []
if domain not in config["federation"]["suggested_websites"]:
config["federation"]["suggested_websites"].append(domain)
Set.Save(config)
if not silent:
print(clr["bold"]+clr["tbyl"]+domain+clr["norm"]+" is suggested for federation.")
def Remove(domain):
domain = Clean(domain)
config = Set.Load()
if "federation" not in config: config["federation"] = {}
if "known_websites" not in config["federation"]: config["federation"]["known_websites"] = []
try:
config["federation"]["known_websites"].remove(domain)
except:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Domain is not known!")
return
Set.Save(config)
Suggest(domain, True)
print(clr["bold"]+clr["tbyl"]+domain+clr["norm"]+" is removed from federation.")
def Ban(domain):
domain = Clean(domain)
config = Set.Load()
if "federation" not in config: config["federation"] = {}
if "known_websites" not in config["federation"]: config["federation"]["known_websites"] = []
if "suggested_websites" not in config["federation"]: config["federation"]["suggested_websites"] = []
if "banned_websites" not in config["federation"]: config["federation"]["banned_websites"] = []
if domain not in config["federation"]["banned_websites"]:
if domain in config["federation"]["known_websites"]:
config["federation"]["known_websites"].remove(domain)
if domain in config["federation"]["suggested_websites"]:
config["federation"]["suggested_websites"].remove(domain)
config["federation"]["banned_websites"].append(domain)
Set.Save(config)
print(clr["bold"]+clr["tbyl"]+domain+clr["norm"]+" is banned.")
def UnBan(domain):
domain = Clean(domain)
config = Set.Load()
if "federation" not in config: config["federation"] = {}
if "banned_websites" not in config["federation"]: config["federation"]["banned_websites"] = []
try:
config["federation"]["banned_websites"].remove(domain)
except:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Domain is not banned!")
return
Set.Save(config)
Suggest(domain, True)
print(clr["bold"]+clr["tbyl"]+domain+clr["norm"]+" is unbanned.")
def List():
config = Set.Load()
print()
print(" Federating with:")
print()
for domain in config.get("federation", {}).get("known_websites", []):
print(domain)
print()
print(" Suggested:")
print()
for domain in config.get("federation", {}).get("suggested_websites", []):
print(domain)
print()
print(" Banned from auto-federating:")
print()
for domain in config.get("federation", {}).get("banned_websites", []):
print(domain)
def AutoOn():
config = Set.Load()
if "federation" not in config: config["federation"] = {}
config["federation"]["auto"] = True
Set.Save(config)
print("Auto-federation is turned on.")
def AutoOff():
config = Set.Load()
if "federation" not in config: config["federation"] = {}
config["federation"]["auto"] = False
Set.Save(config)
print("Auto-federation is turned off.")
def FederationThread():
# This thread happens alongside the main website thread.
# And is used to request websites various federation
# requests.
while True:
try:
time.sleep(600)
API.PetitionFetch()
# Fetch() # This needs to be rewritten to not try to fetch AP servers
ActivityPub.SendArticles()
Analyse.CollectTotals() # Making sure the totals are collected.
Plugins.onFEDI()
except KeyboardInterrupt:
return
except:
traceback.print_exc()
def Fetch():
config = Set.Load()
known = config.get("federation", {}).get("known_websites", [])
banned = config.get("federation", {}).get("banned_websites", [])
auto = config.get("federation", {}).get("auto", False)
Articles = Render.allArticles()
print(" "+consoleForm("Federation "), "Fetching federation data")
for domain in known:
if domain in banned:
continue
#print(" "+consoleForm("Federation "), "Fetching:", domain)
try:
data = API.Get("https://"+domain+"/json/federate/"+config.get("domain", ""))
except:
#print(" "+consoleForm("Federation "), clr["bold"]+clr["tdrd"]+"Failed to fetch:", clr["norm"]+ domain)
continue
# Adding to the known_websites
for d in data.get("known_websites", []):
d = Clean(d)
if d not in banned and d not in known and d != config.get("domain"):
if auto:
print(" "+consoleForm("Federation "), "Auto-adding new domain:", d)
Add(d, silent=True)
else:
print(" "+consoleForm("Federation "), "Suggested new domain:", d)
Suggest(d, silent=True)
# Adding comments
for comment in data.get("comments", {}):
comment = data["comments"][comment]
is_reply_to = comment.get("is_reply_to", "")
article = is_reply_to.replace("https://", "").replace(config.get("domain", ""), "")
article = Articles.get(article, {})
if article:
# Checking if this comment is already recorded
url = comment.get("url","")
found = False
for c in article.get("comments", {}).get("comments", []):
if c.get("url", "") == url:
found = True
if not found:
print(" "+consoleForm("Federation "), "New comment from:", domain, "on:", article.get("url", ""))
description = comment.get("description", "")
if not description:
description = "A reply from "+domain
description = "*...from "+domain+"*\n\n**"+comment.get("title", "Reply")+"**\n\n"+description
cdata = {"username":comment.get("author",domain),
"text":Safe(description),
"url":Safe(url)}
if "comments" not in article:
article["comments"] = {}
if "comments" not in article["comments"]:
article["comments"]["comments"] = []
article["comments"]["comments"].append(cdata)
metadata = Set.Folder()+"/tabs"+article.get("url","")+"/metadata.json"
try:
with ThreadLock(metadata):
with open(metadata, "w") as save:
json.dump(article, save, indent=4)
except:
pass
# Notification
if article.get("author"):
text = comment.get("author", {}).get("username", "")
text = text + ' from '+domain+' made an article reply to '
text = text + article.get("title", "")
Render.Notify(article.get("author"), Safe(comment.get("url", article.get("url", ""))), Safe(text))
# Get plugins
try:
data = API.Get("https://"+domain+"/json/plugins")
except:
continue
# Load
try:
with ThreadLock(filename):
with open(filename) as o:
Pdata = json.load(o)
except:
Pdata = {}
Pdata[domain] = data
# Save
try:
filename = Set.Folder()+"/plugins.json"
with ThreadLock(filename):
with open(filename, "w") as save:
json.dump(Pdata, save, indent=4)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Cannot save plugins!", e)
+119
View File
@@ -0,0 +1,119 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
from modules.Common import *
def Help():
print()
print(clr["bold"]+" BDServer Help"+clr["norm"])
print()
print(clr["tdyl"]+"--help"+clr["norm"]+" - Prints this Help Page.")
print(clr["tdyl"]+"--run"+clr["norm"]+" - Run the server.")
print(clr["tdyl"]+"--analyse"+clr["norm"]+" - Analyse the website.")
print()
print(clr["tdyl"]+"--set"+clr["norm"]+" - Use for changing settings.")
print(clr["tdyl"]+"--account"+clr["norm"]+" - Manage Accounts.")
print()
print(clr["tdyl"]+"--create"+clr["norm"]+" - Start a new article.")
print()
print(clr["tdyl"]+"--config"+clr["norm"]+" - Edit config file directly, bypassing --set.")
print(clr["tdyl"]+"--folder"+clr["norm"]+" - Show the folder of the website's data.")
print()
print(clr["tdyl"]+"--federation"+clr["norm"]+" - Federation settings with other BDServers.")
print()
print(clr["tdyl"]+"--transfer"+clr["norm"]+" - Transfers legacy website data to the new format.")
print(clr["tdyl"]+"--ban"+clr["norm"]+" - Ban a cookie from DDosing your website.")
print()
print()
print(" If you just installed BDServer start \n with "+clr["tdyl"]+"--set"+clr["norm"]+" to set things up.")
print()
def Set():
print()
print(clr["bold"]+" BDServer Set Help "+clr["norm"])
print()
print(" Set is used for setting up server configurations.")
print()
print(clr["tdyl"]+"--title"+clr["norm"]+" - Set title of the website.")
print(clr["tdyl"]+"--tagline"+clr["norm"]+" - Set tagline of the website.")
print(clr["tdyl"]+"--domain"+clr["norm"]+" - Let the server know the clearnet domain.")
print(clr["tdyl"]+"--tor"+clr["norm"]+" - Let the server know the tor domain.")
print(clr["tdyl"]+"--email"+clr["norm"]+" - Set Email SMTP account for internal automatic messages.")
print(clr["tdyl"]+"--account"+clr["norm"]+" - Set the Main Account, for the footer and stuff.")
print(clr["tdyl"]+"--port"+clr["norm"]+" - Set port where to run the website.")
print(clr["tdyl"]+"--css"+clr["norm"]+" - Set a CSS file.")
print(clr["tdyl"]+"--css_edit"+clr["norm"]+" - edit a CSS file.")
print(clr["tdyl"]+"--favicon"+clr["norm"]+" - Set a favicon file.")
print(clr["tdyl"]+"--add_tab"+clr["norm"]+" - Adds a category of articles.")
print(clr["tdyl"]+"--edit_tab"+clr["norm"]+" - Edit the config of a category.")
print(clr["tdyl"]+"--tab_rows"+clr["norm"]+" - Set in how many rows to draw the tabs.")
print()
print(clr["tdyl"]+"--editor"+clr["norm"]+" - Set editor. Default nano.")
print()
print(clr["tdyl"]+"--fc_api"+clr["norm"]+" - API for software Free Competitors search.")
print()
print()
print(" If you are new here I guess start \n with "+clr["tdyl"]+"--title"+clr["norm"]+" to name your website.")
print()
def Accounts():
print()
print(clr["bold"]+" BDServer Account Help "+clr["norm"])
print()
print(" Account is used to manage website's accounts.")
print()
print(clr["tdyl"]+"--add"+clr["norm"]+" - Add a new account.")
print(clr["tdyl"]+"--list"+clr["norm"]+" - List accounts.")
print(clr["tdyl"]+"--edit"+clr["norm"]+" - Edit accounts.")
print()
def Federation():
print()
print(clr["bold"]+" BDServer Federation Help "+clr["norm"])
print()
print(" Federation is used for interoperability between websites.")
print()
print(clr["tdyl"]+"--add"+clr["norm"]+" - Add a new website to the list.")
print(clr["tdyl"]+"--remove"+clr["norm"]+" - Remove a website from the list.")
print(clr["tdyl"]+"--ban"+clr["norm"]+" - Ban a website from auto-federating.")
print(clr["tdyl"]+"--unban"+clr["norm"]+" - Remove a ban from a website.")
print(clr["tdyl"]+"--list"+clr["norm"]+" - List federation websites.")
print()
print(clr["tdyl"]+"--auto_on"+clr["norm"]+" - Turn on auto-federation.")
print(clr["tdyl"]+"--auto_off"+clr["norm"]+"- Turn off auto-federation.")
print()
def Analyse():
print()
print(clr["bold"]+" BDServer Analyse Help "+clr["norm"])
print()
print(" Analyse traffic and other things related to the website.")
print()
print(clr["tdyl"]+"--logs"+clr["norm"]+" - Search a query in the logs.")
print(clr["tdyl"]+"--totals"+clr["norm"]+" - View website's numbers.")
print(clr["tdyl"]+"--rss"+clr["norm"]+" - Analyse Subscribers.")
print(clr["tdyl"]+"--mastodon"+clr["norm"]+" - See who shares you on Mastodon.")
print(clr["tdyl"]+"--words"+clr["norm"]+" - See popularity of certain words.")
print(clr["tdyl"]+"--performance"+clr["norm"]+" - Compare recent article to one another.")
print(clr["tdyl"]+"--timing"+clr["norm"]+" - See good times to post at ( hours, days, months, years )")
print()
print(clr["tdyl"]+"--ua"+clr["norm"]+" - See Operating Systems and Browsers")
print( " --skip_me - to filter main account")
print( " --human_only - to filter bots")
print()
print(clr["tdyl"]+"--sources"+clr["norm"]+" - See where people are coming from.")
print()
print(clr["tdyl"]+"--trends"+clr["norm"]+" - See what is trending.")
print( " --tags - See hashtags intead.")
print( " --verbose - spit out more data.")
print()
print()
print(" A good start here would be "+clr["tdyl"]+"--totals"+clr["norm"]+" to \n get basic analytics.")
print()
+160
View File
@@ -0,0 +1,160 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import json
from modules import Set
from modules.Common import *
categories = ["/articles", "/books", "/films", "/music", "/games", "/software", "/forum", "/reviews", "/malware", "/legals"]
def count(folder, text):
walk = os.walk(folder)
c = 0
for i in walk:
for b in i[2]:
try:
c = c + open(i[0]+"/"+b).read().count(text)
except:
pass
return c
def Transfer(location):
# This function is pretty pointless. But it is useful for me. Because I
# Was using another, badly written server for years. And now to migrate
# to this server, I need something that will transfer my data over. If
# by any miracle you happended to use my code to build your own website,
# well, using this you too could do the same. LOL.
if not os.path.exists(location):
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Path does not exist!")
return
##### ARTICLES #####
for tab in categories:
for article in os.listdir(location+tab):
if article.endswith(".md"):
title = article[article.replace("/", " ", 1).find("/")+1:].replace("-", " ").replace("_", " ").replace(".md", "").replace("%28", "(").replace("%29", ")")
timestamp = os.path.getctime(location+tab+"/"+article.replace("%28", "(").replace("%29", ")"))
text = open(location+tab+"/"+article)
text = text.read()
# Description is a bit more complicated
art = text
while art.startswith("*"):
art = art[art.find("\n"):]
while art.startswith("\n") or art.startswith("_"):
art = art[1:]
description = art[:art.find("\n")]
# Legacy system only had non blenderdumbass authors
# when it is a forum.
author = "blenderdumbass"
if tab == "/forum":
with open(location+"/accounts.json") as o:
accounts = json.load(o)["accounts"]
for person in accounts:
if tab+"/"+article in accounts[person].get("forums", []):
author = person
with open(location+"/posters.json") as o:
posters = json.load(o)
thumbnail = posters.get(tab+"/"+article, "")
# This will only give us the general number of views.
views = count(location+"/logs", "/graph"+tab+"/"+article)
# But we also want to get number of views per day.
dates = {}
for date in os.listdir(location+"/logs"):
if date.endswith(".log"):
date = date[:-4]
try: datetext = open(location+"/logs/"+date+".log", "r")
except: continue
amount = datetext.read().count("/graph"+tab+"/"+article)
if amount > 0:
dates[date] = amount
# Comments
with open(location+"/comments.json") as o:
commentdata = json.load(o)
comments = {
"comments":commentdata["comments"].get(tab+"/"+article, []),
"requests":commentdata["requests"].get(tab+"/"+article, [])
}
# Making the directory for the article
try: os.makedirs(Set.Folder()+"/tabs"+tab+"/"+article[:-3])
except:pass
# Transferring the texts
textfile = Set.Folder()+"/tabs"+tab+"/"+article[:-3]+"/text.md"
textfile = open(textfile, "w")
textfile.write(text)
textfile.close()
# Metadata
metadata = {
"title":title,
"timestamp":timestamp,
"description":description,
"author":author,
"thumbnail":thumbnail,
"views":{
"amount":views,
"viewers":[],
"dates":dates
},
"recording":"",
"comments":comments
}
with open(Set.Folder()+"/tabs"+tab+"/"+article[:-3]+"/metadata.json", "w") as save:
json.dump(metadata, save, indent=4)
#### ACCOUNTS ####
with open(location+"/accounts.json") as o:
accounts = json.load(o)["accounts"]
for username in accounts:
account = accounts[username]
# We are updating the invites
invite_codes = account.pop("invite_codes")
account["invite_codes"] = {}
for code in invite_codes:
account["invite_codes"][code] = "Unknown"
# We are adding some new stuff to accounts
account["title"] = username # Visible title
account["email"] = "" # Contact info
account["website"] = "" # Website
account["mastodon"] = "" # Mastodon ( useful for fediverse tags )
account["sessions"] = {} # List of cookies accosiated with this account
# We are saving the accounts in a different way too.
with open(Set.Folder()+"/accounts/"+username+".json", "w") as save:
json.dump(account, save, indent=4)
+113
View File
@@ -0,0 +1,113 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
from modules.Common import *
from modules import Set
import os
import sys
import json
import hashlib
import traceback
Plugins = {}
def Import():
# This functions imports the plugins.
pluginsF = Set.Folder()+"/plugins"
sys.path.append(Set.Folder())
if not os.path.exists(pluginsF+"/__init__.py"):
t = open(pluginsF+"/__init__.py", "w")
t.close()
for plugin in os.listdir(pluginsF):
if plugin.endswith(".py"):
plugin = plugin.replace(".py", "")
exec("from plugins import "+plugin)
Plugins[plugin] = eval(plugin)
def onHTML(server, html):
for plugin in Plugins:
if "onHTML" in dir(Plugins[plugin]):
try:
html = Plugins[plugin].onHTML(server, html)
except:
print(traceback.format_exc())
return html
def onGET(server):
for plugin in Plugins:
if "onGET" in dir(Plugins[plugin]):
try:
if Plugins[plugin].onGET(server):
return True
except:
print(traceback.format_exc())
return False
def onFEDI():
for plugin in Plugins:
if "onFEDI" in dir(Plugins[plugin]):
try:
Plugins[plugin].onFEDI()
except:
print(traceback.format_exc())
def GetData():
config = Set.Load()
pluginsF = Set.Folder()+"/plugins"
data = {}
Pdata = {}
try:
with open(Set.Folder()+"/plugins.json") as o:
Pdata = json.load(o)
except:
pass
for plugin in os.listdir(pluginsF):
if plugin.endswith(".py") and plugin != "__init__.py":
text = open(pluginsF+"/"+plugin, "r")
text = text.read()
sha512 = hashlib.sha512(text.encode("utf-8")).hexdigest()
sha256 = hashlib.sha256(text.encode("utf-8")).hexdigest()
md5 = hashlib.md5(text.encode("utf-8")).hexdigest()
try:
PluginData = dict(Plugins[plugin.replace(".py","")].PluginData)
except:
PluginData = {}
pluginData = {"sha512":sha512,
"sha256":sha256,
"md5" :md5}
for i in PluginData:
pluginData[i] = PluginData[i]
pluginData["domains"] = [config.get("domain","localhost")]
for domain in Pdata:
if domain == config.get("domain"):
continue
for pluginname in Pdata[domain]:
pluginD = Pdata[domain][pluginname]
#if pluginD.get("sha512") == sha512:
# pluginData["domains"].append(domain)
data[plugin] = pluginData
return data
File diff suppressed because it is too large Load Diff
+549
View File
@@ -0,0 +1,549 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
from subprocess import *
import json
import os
import time
import email
import random
import threading
import datetime
import hashlib
import urllib.request
import urllib.parse
import subprocess
import os
import sys
from modules import Set
from modules import API
from modules import Render
from modules import Federation
from modules import ActivityPub
from modules import Email
from modules import Themes
from modules import Plugins
from modules.Common import *
print()
# I don't know what I'm doing. But here is an idea
antiDDos = {}
settings = Set.Load()
def DDosProtect(server):
if "/graph/" in server.path:
return True
request = str(server.path) + str(server.headers.get("User-Agent", "None"))
l = settings.get("banned_cookies", [])
for i in l:
if i in str(server.cookie):
return False
# if server.cookie and server.cookie not in Render.KnownCookies:
# server.path = "DDOS"
# server.send_response(403)
# return False
if GuessHuman(server):
return True
if request not in antiDDos:
antiDDos[request] = {"last":time.time()}
return True
if antiDDos[request]["last"] + 10 < time.time():
antiDDos[request]["last"] = time.time()
return True
server.path = "DDOS"
server.send_response(403)
return False
class handler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
if "?" in self.path:
self.path = self.path[:self.path.find("?")]
if self.path == "DDOS":
return
q = clr["tbbl"]
b = clr["tbbl"]
via = self.headers.get("Via", "")
try:
if self.newview:
b = clr["bold"]+clr["tbrd"]
elif self.isPage:
b = clr["norm"]
if self.requestline.startswith("GET"):
for i in self.parsed:
if i != "password":
q = q + "[ "+i+" : "+self.parsed[i][0]+" ] "
else:
q = q + "[ "+i+" : ******* ] "
cookie = self.cookie
toprint = typeOfConnection(self) + consoleForm(cookie)+" "+b+"[ "+str(datetime.datetime.now())+" ] "+self.path+" "+q+" "+clr["tdyl"]+StripUserAgent(str(self.headers.get("User-Agent")), via)+clr["norm"]
except:
cookie = None
toprint = typeOfConnection(self) + consoleForm(cookie)+" "+b+"[ "+str(datetime.datetime.now())+" ] "+self.requestline+" "+clr["tdyl"]+StripUserAgent(str(self.headers.get("User-Agent")), via)+clr["norm"]
toprint = toprint + " TRDS:" + str(threading.active_count())
print(toprint)
folder = Set.Folder()
filename = folder+"/logs/"+datetime.datetime.now().strftime('%Y-%m-%d')+".log"
with ThreadLock(filename):
logfile = open(filename, "ab")
logfile.write((toprint+"\n").encode('utf-8'))
logfile.close()
def do_POST(self):
self.path = self.path.replace("/..", "/")
self.path = self.path.replace("%27", "'")
self.cookie = self.headers.get("Cookie")
self.newview = False
if self.cookie: self.cookie = self.cookie[-200:]
if not DDosProtect(self):
return
self.isPage = False
# Failing early to make sure that nobody will
# try attacking this part of the server.
commands = ["do_edit",
"do_theme_edit",
"do_login",
"do_register",
"change_password",
"upload_image_do",
#"upload_css_global",
#"upload_css_posts",
"activitypub"]
found = False
for i in commands:
if i in self.path:
found = True
break
if not found:
Render.AccessDenied(self)
return
# Not allowing more than 1 MB for the sake of
# network. A full book of Sheiny The Hacker
# which was posted on blenderdumbass.org is
# only about 160 KB. 1MB should be plenty.
length = int(self.headers["Content-Length"])
readdata = False
if length < 3000000:
try:
binaries = ("upload_image_do", "upload_css_global", "upload_css_posts")
if not self.path[1:].startswith(binaries):
text = self.rfile.read(length).decode("utf-8")
readdata = True
else:
text = self.rfile.read(length)
readdata = True
except:
Render.AccessDenied(self)
return
else:
Render.AccessDenied(self)
return
if type(text) == str:
try:
self.parsed = json.loads(text)
except:
self.parsed = urllib.parse.parse_qs(text)
else:
try:
content_type = self.headers['content-type']
boundary = content_type.split("=")[1].encode()
self.parsed = text.split(boundary)
except:
self.parsed = {}
AccessDenied(self)
if self.path[1:].startswith("do_edit"):
Render.Publish(self)
elif self.path[1:].startswith("do_theme_edit"):
Themes.DoThemeEdit(self)
elif self.path[1:].startswith("do_login"):
Render.Login(self)
elif self.path[1:].startswith("change_password"):
Render.UpdatePassword(self)
elif self.path[1:].startswith("do_register"):
Render.Register(self)
elif self.path[1:].startswith("upload_image_do"):
Render.ImageUploadDo(self)
elif self.path[1:].startswith("upload_css_global"):
Render.CSS_Apply(self, "global")
elif self.path[1:].startswith("upload_css_posts"):
Render.CSS_Apply(self, "posts")
elif self.path.startswith("/activitypub/"):
url = self.path[12:]
if "?" in url: url = url[:url.find("?")]
ActivityPub.Parse(self, url, text=text)
else:
Render.NotFound(self)
def do_GET(self):
self.path = self.path.replace("/..", "/")
self.path = self.path.replace("%27", "'")
self.path = self.path.replace("%22", '"')
self.path = self.path.replace("//", '/')
parsed_url = urllib.parse.urlparse(self.path)
self.parsed = urllib.parse.parse_qs(parsed_url.query)
#print(self.headers)
self.cookie = self.headers.get("Cookie")
if self.cookie: self.cookie = self.cookie[-200:]
if not DDosProtect(self):
return
self.isPage = False
# ignore = ["/rss"]
# if not self.cookie and not self.path.startswith(tuple(ignore)):
# Render.Redirect(self, self.path)
# return
self.newview = False
categories = list(Render.tabs().keys())
if self.path[:self.path.find("?")] == "/" or self.path == "/":
self.isPage = True
Render.MainPage(self)
elif self.path[1:] in categories or self.path[1:self.path.find("?")] in categories:
url = self.path[1:]
if "?" in url: url = url[:url.find("?")]
self.isPage = True
Render.ListPage(self, url)
elif self.path[1:].startswith("json"):
url = self.path[6:]
if "?" in url: url = url[:url.find("?")]
API.GiveJSON(url, self)
elif self.path[1:].startswith("markdown"):
url = self.path[10:]
if "?" in url: url = url[:url.find("?")]
if url.startswith(tuple(categories)):
try:
Render.ArticleMarkdown(self, url)
except:
Render.NotFound(self)
else:
Render.NotFound(self)
elif self.path[1:].startswith(tuple(categories)):
url = self.path[1:]
if "?" in url: url = url[:url.find("?")]
self.isPage = True
try:
Render.ArticlePage(self, url)
except Exception as e:
print(e)
Render.NotFound(self)
elif self.path[1:].startswith("account"):
self.isPage = True
url = self.path[9:]
if "?" in url: url = url[:url.find("?")]
try:
Render.AccountPage(self, url)
except:
Render.NotFound(self)
elif self.path[1:].startswith("login"):
self.isPage = True
Render.LoginPage(self)
elif self.path[1:].startswith("help"):
self.isPage = True
Render.Help(self)
elif self.path[1:].startswith("editor"):
self.isPage = True
Render.EditorPage(self)
elif self.path[1:].startswith("theme_editor"):
self.isPage = True
Themes.Editor(self)
elif self.path[1:].startswith("contact"):
self.isPage = True
Render.ContactPage(self)
elif self.path[1:].startswith("upload_image"):
self.isPage = True
Render.ImageUploadPage(self)
elif self.path[1:].startswith("email_verify"):
self.isPage = True
Email.VerifyPage(self)
elif self.path[1:].startswith("register"):
self.isPage = True
Render.RegisterPage(self)
elif self.path[1:].startswith("settings"):
self.isPage = True
Render.SettingsPage(self)
elif self.path[1:].startswith("comment"):
Render.DoComment(self)
elif self.path[1:].startswith("delete_comment"):
Render.DeleteComment(self)
elif self.path[1:].startswith("update_account"):
Render.UpdateAccount(self)
elif self.path[1:].startswith("mastodon_share"):
self.isPage = True
Render.ShareMastodon(self)
elif self.path[1:].startswith("mastodon_comment"):
self.isPage = True
Render.CommentMastodon(self)
elif self.path[1:].startswith("analytics"):
self.isPage = True
Render.AnalyticsPage(self)
elif self.path[1:].startswith("grant_publication_rights"):
Render.UpdatePublicationRights(self)
elif self.path[1:].startswith("create_invite"):
Render.CreateInvite(self)
elif self.path[1:].startswith("cancel_invite"):
Render.CancelInvite(self)
elif self.path[1:].startswith("log_out"):
Render.LogOut(self)
elif self.path[1:].startswith("sign_petition"):
self.isPage = True
Email.SignPetition(self)
elif self.path[1:].startswith("read_notification"):
Render.ReadNotification(self)
elif self.path[1:].startswith("search"):
self.isPage = True
Render.Search(self)
elif self.path == "/test":
self.send_response(200)
self.wfile.write("Success!".encode("utf-8"))
elif self.path.startswith("/graph/"):
url = self.path[6:]
if "?" in url: url = url[:url.find("?")]
Render.Graph(self, url)
elif self.path.startswith("/do_install_plugin/"):
sha = self.path[19:]
if "?" in sha: sha = sha[:sha.find("?")]
Render.DoInstallPlugin(self, sha)
elif self.path.startswith("/do_delete_plugin/"):
sha = self.path[18:]
if "?" in sha: sha = sha[:sha.find("?")]
Render.DoDeletePlugin(self, sha)
elif self.path.startswith("/install_plugin/"):
sha = self.path[16:]
if "?" in sha: sha = sha[:sha.find("?")]
Render.InstallPluginPage(self, sha)
elif self.path.startswith("/rss"):
Render.RSS(self)
elif self.path.startswith("/.well-known/webfinger"):
ActivityPub.Webfinger(self)
elif self.path.startswith("/activitypub/"):
url = self.path[12:]
if "?" in url: url = url[:url.find("?")]
ActivityPub.Parse(self, url)
elif self.path.startswith("/plugins/"):
try:
folder = Set.Folder()
f = open(folder+self.path, "rb")
f = f.read()
Render.headers(self, 200)
self.wfile.write(f)
except:
Render.NotFound(self)
elif self.path.startswith("/plugins"):
Render.PluginListPage(self)
elif self.path.startswith("/pictures/"):
try:
folder = Set.Folder()
f = open(folder+self.path, "rb")
f = f.read()
Render.headers(self, 200)
self.wfile.write(f)
except:
Render.NotFound(self)
elif self.path.startswith("/themes/"):
ID = self.path[8:]
Themes.ViewPage(self, ID)
elif self.path.startswith("/themes"):
Themes.ListPage(self)
elif self.path == "/css":
config = Set.Load()
filename = config.get("css", "default.css")
cssfile = open(filename, "rb")
cssfile = cssfile.read()
Render.headers(self, 200)
self.wfile.write(cssfile)
elif self.path == "/css_global":
Render.CSS_Get(self, "global")
elif self.path == "/css_posts":
Render.CSS_Get(self, "posts")
elif self.path.startswith("/icon/"):
Folder = Set.Folder()
if self.path[6:]+".png" in os.listdir(Folder+"/icons"):
folder = Folder+"/icons"
else:
folder = "icons"
icon = folder+"/"+self.path[6:]+".png"
f = open(icon, "rb")
f = f.read()
Render.headers(self, 200)
self.wfile.write(f)
elif Plugins.onGET(self):
# If plugins return True, there is no 404,
# some page is rendered.
pass
else:
Render.NotFound(self)
config = Set.Load()
PORT = config.get("port", 8080)
class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
pass
serve = ThreadingSimpleServer(("", PORT), handler)
#serve = HTTPServer(("", PORT), handler)
serve.timeout = 5
# Collecting Garbage from the last
# use of the server.
Render.CleanData()
def Test():
while True:
time.sleep(100)
# Check if the server is running.
settings = Set.Load()
port = settings.get("port", 8080)
try:
headers = {"User-Agent":"BDServer from Anti-Slowloris Thread"}
req = urllib.request.Request("http://localhost:"+str(port)+"/test", headers = headers)
f = urllib.request.urlopen(req, timeout=2)
except Exception as e:
if "Success!" != str(e):
print(" "+consoleForm("Anti-Slowloris"), clr["bold"]+clr["tdrd"]+"Failed to connect to /test!"+clr["norm"], clr["tdyl"],e, clr["norm"], "Restarting server!" )
os.execl(sys.executable, *sys.orig_argv)
try:
print("Importing Plugins...")
Plugins.Import()
print("Starting Federation Thread...")
t = threading.Thread(target=Federation.FederationThread, daemon=True)
t.start()
print("Starting Anti-Slowloris Thread...")
t2 = threading.Thread(target=Test, daemon=True)
t2.start()
print("Starting Server...")
print()
print("You are serving on http://localhost:"+str(PORT))
print()
serve.serve_forever()
except KeyboardInterrupt:
print("\nServer Stopped!")
+413
View File
@@ -0,0 +1,413 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
from modules.Common import *
import os
import sys
import json
def Folder():
conf = os.environ.get("PROJECT", "BDServer")
try:
data_dir = os.environ["XDG_DATA_HOME"] + "/" + conf
except:
data_dir = os.path.expanduser("~/.local/share/"+ conf)
try:
os.makedirs(data_dir)
except:
pass
# Adding nessesary subcategories
for i in ["pictures", "tabs", "logs", "accounts", "plugins", "deleted_plugins",
"icons", "themes",
"activitypub/actors",
"activitypub/webfingers"]:
try:
os.makedirs(data_dir+"/"+i)
except:
pass
return data_dir
def Load():
try:
filename = Folder()+"/config.json"
with ThreadLock(filename):
with open(Folder()+"/config.json") as o:
data = json.load(o)
except Exception as e:
data = {}
print(clr["bold"]+clr["tdyl"]+"Error:"+clr["norm"]+" Config doesn't exist! Making new config.")
return data
def Save(data):
try:
filename = Folder()+"/config.json"
with ThreadLock(filename):
with open(filename, "w") as save:
json.dump(data, save, indent=4)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Cannot save config!", e)
def Set():
anything = False
if len(sys.argv) < 3:
from modules import Help
Help.Set()
anything = True
if "--title" in sys.argv:
anything = True
try:
title = sys.argv[ sys.argv.index("--title") + 1]
if "--" in title: 1/0 # Failing this for the error message.
Title(title)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Title Wasn't Specified!")
print('Use: $ python3 run.py --set --title "My Website"')
if "--account" in sys.argv:
anything = True
try:
account = sys.argv[ sys.argv.index("--account") + 1]
if "--" in account: 1/0 # Failing this for the error message.
MainAccount(account)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Account Wasn't Specified!")
print('Use: $ python3 run.py --set --account blenderdumbass')
if "--tagline" in sys.argv:
anything = True
try:
tagline = sys.argv[ sys.argv.index("--tagline") + 1]
if "--" in tagline: 1/0 # Failing this for the error message.
Tagline(tagline)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Tagline Wasn't Specified!")
print('Use: $ python3 run.py --set --title "Ultimate Club for Ultimate Hackers"')
if "--domain" in sys.argv:
anything = True
try:
domain = sys.argv[ sys.argv.index("--domain") + 1]
if "--" in domain: 1/0 # Failing this for the error message.
Domain(domain)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Didn't specify the domain.")
print('Use: $ python3 run.py --set --domain blenderdumbass.org')
if "--tor" in sys.argv:
anything = True
try:
domain = sys.argv[ sys.argv.index("--tor") + 1]
if "--" in domain: 1/0 # Failing this for the error message.
Tor(domain)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Didn't specify the tor domain.")
print('Use: $ python3 run.py --set --domain blenderdumbass.org')
if "--editor" in sys.argv:
anything = True
try:
editor = sys.argv[ sys.argv.index("--editor") + 1]
if "--" in editor: 1/0 # Failing this for the error message.
Editor(editor)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Didn't specify the editor!")
print('Use: $ python3 run.py --set --editor emacs')
if "--email" in sys.argv:
anything = True
Email()
if "--port" in sys.argv:
anything = True
try:
port = int(sys.argv[ sys.argv.index("--port") + 1])
Port(port)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Port Wasn't Specified Correctly!")
print('Use: $ python3 run.py --set --port 8080')
if "--add_tab" in sys.argv:
anything = True
try:
tab = sys.argv[ sys.argv.index("--add_tab") + 1]
if "--" in tab: 1/0 # Failing this for the error message.
AddTab(tab)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Tab Name Wasn't Specified!")
print('Use: $ python3 run.py --set --add_tab "Articles"')
if "--edit_tab" in sys.argv:
anything = True
try:
tab = sys.argv[ sys.argv.index("--edit_tab") + 1]
if "--" in tab: 1/0 # Failing this for the error message.
EditTab(tab)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Tab Name Wasn't Specified!")
print('Use: $ python3 run.py --set --edit_tab "Articles"')
if "--tab_rows" in sys.argv:
anything = True
try:
tab_rows = int(sys.argv[ sys.argv.index("--tab_rows") + 1])
TabRows(tab_rows)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Number Wasn't Specified Correctly!")
print('Use: $ python3 run.py --set --tab_rows 2')
if "--css" in sys.argv:
anything = True
try:
filename = sys.argv[ sys.argv.index("--css") + 1]
if "--" in filename: 1/0 # Failing this for the error message.
SetCSS(filename)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" CSS File Wasn't Specified!", e)
print('Use: $ python3 run.py --set --css ~/mycssfile.css')
if "--favicon" in sys.argv:
anything = True
try:
filename = sys.argv[ sys.argv.index("--favicon") + 1]
if "--" in filename: 1/0 # Failing this for the error message.
SetFavicon(filename)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" File Wasn't Specified!", e)
print('Use: $ python3 run.py --set --css ~/favicon.png')
if "--css_edit" in sys.argv:
anything = True
config = Load()
os.system("nano "+config.get("css", "default.css"))
if "--fc_api" in sys.argv:
anything = True
try:
api = sys.argv[ sys.argv.index("--fc_api") + 1]
if "--" in api: 1/0 # Failing this for the error message.
FreeCompetitors(api)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" API Link Wasn't Specified!")
print('Use: $ python3 run.py --set --fc_api https://sudo.madiator.com')
if not anything:
print(clr["bold"]+clr["tdrd"]+"Unknown command!"+clr["norm"])
def Title(title):
data = Load()
data["title"] = title
Save(data)
print(clr["bold"]+clr["tbyl"]+title+clr["norm"]+" is set as title.")
def Tagline(tagline):
data = Load()
data["tagline"] = tagline
Save(data)
print(clr["bold"]+clr["tbyl"]+tagline+clr["norm"]+" is set as tagline.")
def Domain(domain):
data = Load()
data["domain"] = domain
Save(data)
print(clr["bold"]+clr["tbyl"]+domain+clr["norm"]+" is set as domain.")
def Tor(domain):
data = Load()
data["tor"] = domain
Save(data)
print(clr["bold"]+clr["tbyl"]+domain+clr["norm"]+" is set as tor domain.")
def Editor(editor):
data = Load()
data["editor"] = editor
Save(data)
print(clr["bold"]+clr["tbyl"]+editor+clr["norm"]+" is set as the editor.")
def Port(port):
data = Load()
data["port"] = port
Save(data)
print(clr["bold"]+clr["tbyl"]+str(port)+clr["norm"]+" is set as the website's port.")
def AddTab(tab):
# Tab's Folder
tabsFolder = Folder()+"/tabs/"+Simplify(tab)
try: os.makedirs(tabsFolder)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Could not add tab!", e)
return
# Tab's Config File
data = {"title":tab,
"icon":"folder"}
try:
with open(tabsFolder+"/config.json", "w") as save:
json.dump(data, save, indent=4, sort_keys=True)
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Cannot save tab's config!", e)
return
print(clr["bold"]+clr["tbyl"]+tab+clr["norm"]+" tab is added as "+clr["bold"]+"/"+Simplify(tab)+clr["norm"])
def EditTab(tab):
tabsFolder = Folder()+"/tabs/"+Simplify(tab)
os.system(Load().get("editor", "nano")+" "+tabsFolder+"/config.json")
def SetCSS(filename):
try:
f = open(filename, "r")
f = f.read()
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Cannot Read CSS file!", e)
return
css = open(Folder()+"/style.css", "w")
css.write(f)
css.close()
config = Load()
config["css"] = Folder()+"/style.css"
Save(config)
print("New CSS is set at "+clr["bold"]+Folder()+"/style.css"+clr["norm"])
def SetFavicon(filename):
try:
f = open(filename, "rb")
f = f.read()
except Exception as e:
print(clr["bold"]+clr["tdrd"]+"Error:"+clr["norm"]+" Cannot Read Image!", e)
return
css = open(Folder()+"/pictures/favicon.png", "wb")
css.write(f)
css.close()
config = Load()
config["favicon"] = "/pictures/favicon.png"
Save(config)
print("New favicon is set at "+clr["bold"]+Folder()+"/pictures/favicon.png"+clr["norm"])
def Email():
print("Please make sure your email provider supports")
print("SMTP protocol.")
server = input("SMTP server : ")
port = input("SMTP port : ")
username = input("Username : ")
password = input("Password : ")
sender = input("Sender Address : ")
visible = input("Visible Address: ")
config = Load()
email = {
"server":server,
"port":port,
"username":username,
"password":password,
"sender":sender,
"visible":visible
}
config["email"] = email
Save(config)
print("Email credentials saved!")
def TabRows(tab_rows):
data = Load()
data["tab_rows"] = tab_rows
Save(data)
print(clr["bold"]+clr["tbyl"]+str(tab_rows)+clr["norm"]+" is set as amount of rows to render tabs.")
def MainAccount(account):
data = Load()
data["main_account"] = account
Save(data)
print(clr["bold"]+clr["tbyl"]+account+clr["norm"]+" is set as main account.")
def FreeCompetitors(api):
data = Load()
data["free_competitors"] = api
Save(data)
print(clr["bold"]+clr["tbyl"]+api+clr["norm"]+" is set as Free Competitors API.")
def AddBannedCookie(cookie):
data = Load()
if "banned_cookies" not in data:
data["banned_cookies"] = []
data["banned_cookies"].append(cookie)
Save(data)
print(clr["bold"]+clr["tbyl"]+cookie+clr["norm"]+" is banned.")
print(clr["bold"]+clr["tdrd"]+"To take effect, restart the server."+clr["norm"])
+325
View File
@@ -0,0 +1,325 @@
# AGPL 3 or any later version
# (C) J.Y.Amihud ( Blender Dumbass )
import os
import json
from modules import Set
from modules import Render
from modules import markdown
from modules.Common import *
def themes():
# Returns the metadata of all themes.
folder = Set.Folder()+"/themes/"
themes = {}
for theme in list(os.walk(folder))[0][1]:
try:
with open(folder+theme+"/theme.json") as o:
data = json.load(o)
data["id"] = theme
themes[theme] = data
except Exception as e:
print(e)
return themes
def load(ID):
# Load the raw CSS of a specific ID.
folder = Set.Folder()+"/themes/"+ID
CSS = ""
CSS = open(folder+"/theme.css", "r").read()
return CSS
def get_theme(server, config, user, author={}):
# Load a theme for a specific user
# Getting the server defaults
default = config.get("css", "default.css")
posts = author.get("css_posts")
personal = user.get("css_personal")
if user.get("css_user_global_priority") or not posts:
theme = personal
elif posts:
theme = posts
try:
cssfile = load(theme)
except Exception as e:
if theme:
print(e)
cssfile = open(default, "r").read()
return cssfile
def Preview(server, ID, user={}, download=True, set_website=False, set_personal=False, set_posts=False):
if not user:
user = Render.validate(server.cookie)
html = '<!-- Themes.Preview -->'
html = html + '<div class="dark_box" style="min-height:180px;"><br>'
metadata = themes().get(ID, {})
thumbnail = metadata.get("thumbnail", "")
if thumbnail:
html = html + '<img alt="[avatar]" style="height:150px;float:left;margin-right:20px;margin-bottom:20px" src="'+thumbnail+'">'
name = Safe(metadata.get("name", "Theme"))
html = html + '<b>' + name + '</b>\n'
author = Safe(metadata.get("author", ""))
if author:
html = html + "<br><br>" + Render.User(author)
description = Safe(metadata.get("description", ""))
html = html + '<small>'+markdown.convert(description, False)+"</small><br>"
html = html + "<br><br><br>"
# Doing things with themes
if download:
html = html + Render.Button("View", "/themes/"+ID, "view", newpage=True)
# Editing
edits = Render.editsIn(user.get("username", ""), "Themes_CSS")
if edits and metadata.get("author") == user.get("username"):
html = html + Render.Button("Edit", "/theme_editor?id="+ID, "edit")
if edits:
html = html + Render.Button("Fork", "/theme_editor?id="+ID+"&fork=True", "checklist_new")
if set_personal:
icon = "unchecked"
if user.get("css_personal") == ID:
icon = "checked"
html = html + Render.Button("Personal", "/settings?set_personal_theme="+ID+"#css", icon)
if set_posts:
icon = "unchecked"
if user.get("css_posts") == ID:
icon = "checked"
html = html + Render.Button("Posts", "/settings?set_posts_theme="+ID+"#css", icon)
html = html + '</div><!-- /Themes.Preview -->'
return html
def ViewPage(server, ID):
metadata = themes().get(ID, {})
cssfile = load(ID)
config = Set.Load()
html = Render.head(server,
title = metadata.get("name", "Theme"),
description = metadata.get("description", ""),
image = metadata.get("thumbnail", ""),
config = config,
author = metadata.get("author")
)
html = html + Render.Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + Render.Button("Themes", "/themes", "theme")
html = html + '<!-- Themes.ViewPage -->'
html = html + '<div class="middle_box">'
html = html + Preview(server, ID, download=False)
html = html + "<br><br>"
html = html + "<pre>" + markdown.code_highlight(cssfile) + "</pre><br><br>"
html = html + '</div>'
html = html + Render.LoginButton(server)
Render.send(server, html, 200)
def Editor(server):
config = Set.Load()
html = Render.head(server,
title = "Theme Editor",
description = "Theme Editor",
config = config
)
html = html + Render.Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + '<!-- Themes.Editor -->'
ID = server.parsed.get("id", [""])[0]
FORK = server.parsed.get("fork", [""])[0]
if ID:
metadata = themes().get(ID, {})
text = load(ID)
else:
metadata = {}
text = open("default.css", "r").read()
html = html + """
<div class="middle_section_article">
<div class="dark_box">
<form action="do_theme_edit" method="post">"""
if FORK:
metadata["name"] = "Fork of "+metadata.get("name", "Theme")
if ID and not FORK:
html = html + '<input type="hidden" name="id" value="'+ID.replace("'", "&apos;")+'">'
html = html + """
<img style="vertical-align: middle" src="/icon/theme">
<input class="button" style="width:90%" required="" name="name" placeholder="Name" value='"""+metadata.get("name", "").replace("'", "&apos;")+"""'>
<img style="vertical-align: middle" src="/icon/image_link">
<input class="button" style="width:90%" name="thumbnail"
placeholder="Screenshot"
title="A link to the image which will be rendered alongside the title. A visual representation of the theme."
value='"""+metadata.get("thumbnail", "").replace("'", "&apos;")+"""'>
<center>
<textarea class="toot" rows="5" style="width:95%" name="description" placeholder="Description">"""+metadata.get("description", "")+"""</textarea>
<br>
<textarea class="pre" rows="30" style="width:95%" required="" name="text" placeholder="Text of your post">"""+Safe(text)+"""</textarea>
</center>
<br>
<button class="button" type="submit">
<img class="icon" style="vertical-align: middle" src="/icon/ok">
Publish
</button>
</div>
"""
html = html + '</div>'
html = html + '<div class="checklist_section_article">'
html = html + Render.Button("+ Image", "/upload_image", "image_new", newpage=True)+'<br><br>'
html = html + '</div>'
html = html + Render.LoginButton(server)
Render.send(server, html, 200)
def DoThemeEdit(server):
user = Render.validate(server.cookie)
if not user or not Render.editsIn(user.get("username", ""), "Themes_CSS"):
Render.AccessDenied(server)
return
ID = server.parsed.get("id", [""])[0]
name = server.parsed.get("name", [""])[0]
thumbnail = server.parsed.get("thumbnail", [""])[0]
description = server.parsed.get("description", [""])[0]
text = server.parsed.get("text", [""])[0]
if ID:
metadata = themes().get(ID, {})
if user.get("username") != metadata.get("author"):
Render.AccessDenied(server)
return
else:
ID = RandString()
metadata = {"author":user.get("username", "")}
metadata["name"] = name
metadata["thumbnail"] = thumbnail
metadata["description"] = description
folder = Set.Folder()+"/themes/"+ID
# Making the folders
try: os.makedirs(folder)
except: pass
# Saving the CSS file
filename = folder+"/theme.css"
with ThreadLock(filename):
with open(filename, "w") as save:
save.write(text)
# Saving the metadata
filename = folder+"/theme.json"
with ThreadLock(filename):
with open(filename, "w") as save:
json.dump(metadata, save, indent=4)
Render.Redirect(server, "themes/"+ID)
def ListPage(server):
user = Render.validate(server.cookie)
config = Set.Load()
html = Render.head(server,
title = "Themes",
description = "Themes",
config = config
)
html = html + Render.Button(config.get("title", "My Website"), "/", image=config.get("favicon", "/icon/internet"))
html = html + '<!-- Themes.ListPage -->'
html = html + '<div class="middle_box">'
allThemes = themes()
for theme in allThemes:
html = html + Preview(server, theme, user)
html = html + '</div>'
html = html + Render.Footer(server)
html = html + Render.LoginButton(server)
Render.send(server, html, 200)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+712
View File
@@ -0,0 +1,712 @@
# THIS SOFTWARE IS A PART OF FASTLBRY PROJECT
# THIS SPECIFIC FILE IS UNDER GNU GPLv3 or later
import os
import json
from subprocess import *
import urllib.request
import urllib.parse
from urllib import request, parse
from modules import Render
from modules import Set
from modules import ActivityPub
config = Set.Load()
domain = config.get("domain", "")
preicons = os.listdir(os.getcwd()+"/icons")
icons = []
for i in preicons:
icons.append(i.replace(".png",""))
################################################################################
# Markdown. Or .md file format is an easy way to give your simple text documents
# a bit of flare. Stuff like links, images and quotes are supported. Also bold
# an italic characters.
def lbrynet(method="", params={}):
# First we will make an attempt to use the request module
try:
# To test the SDK subprocess thing I have this little switch
# 1/0 # Division by zero will trigger and error in this 'try'
# and will skip it, to test the other method.
data = {"method":method,
"params":params}
# The port of the LBRY SDK could be different for each user
# so here I'm trying to solve this issue
sdk_url = "http://localhost:5279"
try:
settings = open(os.path.expanduser('~/.local/share/lbry/lbrynet/daemon_settings.yml'))
for line in settings:
if line.startswith("api:"):
sdk_url = "http://"+line[5:]
except:
pass
data = str(json.dumps(data))
data = data.encode('utf-8')
req = request.Request(sdk_url, data=data)
resp = json.loads(request.urlopen(req).read())
try:
resp = resp["result"]
except:
pass
return resp
except Exception as e:
print("Failed", e)
return False
def odysee_get(link):
if link.startswith("lbry://"):
with open("lbry.json") as json_file:
lbry = json.load(json_file)
newlink = link.replace("lbry://", "/lbry/").replace("#", ":")
if newlink not in lbry["hosting"]:
out = lbrynet("resolve",
{"urls":[link]}
)
out = out[link]
try:
price = out["value"]["fee"]["amount"]
except:
price = 0
if price != 0:
return link
dpath = lbrynet("get", {"uri":link, "save_file":True}).get("download_path")
lbry["hosting"][newlink] = dpath
if dpath:
with open("lbry.json", 'w') as f:
json.dump(lbry, f, indent=4)
else:
return link
return newlink+"\n"
return link
def Open(md, AP=False):
# Spliting it for the read.
md = "\n\n"+md
md = md.split("\n")
# First thing is I was to read the headings and convert it into a tree.
tree = []
indent = 1
c = []
skip = 0
for n,line in enumerate(md):
if skip > n:
continue
# Mentions
line2 = line
line = ""
for word in line2.split(" "):
if word.startswith("@"):
href = ""
# possible Activitypub account
if "@" in word[1:]:
actor = ActivityPub.GetRemoteActor(word)
href = actor.get("url", actor.get("id", ""))
if not AP:
if not href:
href = '/account/'+word.replace("@", "")
word = '['+word+']('+href+')'
else:
if not href:
href = 'https://'+domain+'/activitypub/account/'+word.replace("@", "")
word = '<span class="h-card" translate="no"><a href="'+href+'" class="u-url mention">'+word+'</a></span>'
line = line + " " + word
line = line[1:]
ty = "text"
te = line
# Here I want to simply get a type of each line. Later we going to parse
# the links and other things. But first. Let's parse stuff based on
# lines.
if line.startswith("```"):
# THREE ``` aka code block
# This tag will block any other tags
# untill it's untagged
code = ""
#print("#####", n)
for l in md[n+1:]:
if not l.startswith("```"):
code = code + l + "\n"
else:
skip = n + code.count("\n") + 2
break
#print("!!!!!!!!!", skip)
tree.append(["text_cm", code])
te = ""
elif line.startswith("---") or line.startswith("___"):
# Line break in markdown. In this case will be done like this:
te = '</div><div class="dark_box">'
elif line.startswith("#"):
# The titles of the chapter. The Headers are usually written similar
# to how here in python you write comments. It's a # , space, and the
# text.
# The amount of hashes. ## or ### gives different sized text. Officialy
# it should support up to 6 hashes. ######. But why not make it more
# just in case.
ty = line.count("#") # This might give bugs
tree.append([ty, te+"\n"])
# Now the stage 0 is over and we parsed the basic things. Now is the hard
# part to parse out all the images and stuff inside them. It's going to be
# done per part. And we are going to use the same technique I used for the
# conversion of the legacy projects. See : studio/story.py ( in VCStudio )
# We are going to itterate over each letter. And decide what to do by that
newtree = []
for block in tree:
if block[0] == "text_cm":
newtree.append(block)
continue
part = ""
skip = 0
for n, l in enumerate(block[-1]):
if skip > n:
continue
part = part + l
# Here we are going to do something if a give condition is met.
# Usually I gonna do something if [part] ends with a given markdown
# thing. I don't have a manual of markdown on me. So please make it
# more supported. I guess. I might forget things I rarely use.
# Links are made with [stuff you click on](https://example.com)
# but similar to it. Images are done ![Tooltip](Image.png)
# and even weirder you can put one into the other. Like
# [![Tooltip](Image.png)](https://example.com)
# Which going to give you a clickable image.
# For this version what we are going to do is next.
# If we got [![ then it's a clickable image
# If we got ![ then it's just image
# and if we got [ then it's a link.
if part.endswith("[!["):
# IMAGE LINK
newtree.append([block[0], part[:-3]])
tooltip = ""
imageurl = ""
url = ""
t = False
iu = False
skip = n
for le in block[-1][n:]: # For letters in the rest of text
skip = skip + 1
if le == "]":
t = True
elif le == ")" and t and not iu:
iu = True
elif le == ")" and t and iu:
break
elif not t:
tooltip = tooltip +le
elif t and not iu:
imageurl = imageurl + le
else:
url = url+le
tooltip = tooltip[tooltip.find("[")+1:]
imageurl = imageurl[imageurl.find("(")+1:]
url = url[url.find("(")+1:]
apnd = ["image_link", imageurl, url]
newtree.append(apnd)
part = ""
elif part.endswith("!["):
# IMAGE
newtree.append([block[0], part[:-2]])
tooltip = ""
url = ""
t = False
skip = n
for le in block[-1][n:]: # For letters in the rest of text
skip = skip + 1
if le == "]":
t = True
elif le == ")" and t:
break
elif not t:
tooltip = tooltip +le
else:
url = url+le
tooltip = tooltip[tooltip.find("[")+1:]
url = url[url.find("(")+1:]
apnd = ["image", tooltip, url]
newtree.append(apnd)
part = ""
elif part.endswith("[") and not block[-1][n:].startswith('[!['):
# LINK
newtree.append([block[0], part[:-1]])
tooltip = ""
url = ""
t = False
skip = n
for le in block[-1][n:]: # For letters in the rest of text
skip = skip + 1
if le == "]":
t = True
elif le == ")" and t:
break
elif not t:
tooltip = tooltip +le
else:
url = url+le
tooltip = tooltip[tooltip.find("[")+1:]
url = url[url.find("(")+1:]
apnd = ["link", tooltip, url]
newtree.append(apnd)
part = ""
# Now I want to deal with `, *, ** and ***. If you want to help me you
# can implement other types. Such as _, __, ___ and so on. Markdown is
# a very rich language. I'm going to use the cut down version I see other
# people use.
# BTW this is the time. Feb 28. When I switched from Gedit to GNU Emacs.
# Interesting feeling using this programm. I kind a love it even tho
# so many stuff in not intuitive. Like saving is not Ctrl - S but
# Ctrl - X -> Ctrl - S.
# Things like Alt-; to comment multiple lines at ones is HUGE. Also it
# was built by programmers for programmers. So it's a very good tool.
elif part.endswith("**") and not block[-1][n+2:].startswith('*'):
# DOUBLE **
newtree.append([block[0], part[:-2]])
if block[0] == "text":
block[0] = "text_b"
else:
block[0] = "text"
part = ""
elif part.endswith("*") and not block[-1][n+1:].startswith('*'):
# SINGLE *
newtree.append([block[0], part[:-1]])
if block[0] == "text":
block[0] = "text_i"
else:
block[0] = "text"
part = ""
elif part.endswith("`"):
# SINGLE `
newtree.append([block[0], part[:-1]])
if block[0] == "text":
block[0] = "text_c"
else:
block[0] = "text"
part = ""
newtree.append([block[0], part])
#newtree.append(["text", "\n"*20+" [END OF DOCUMENT] "])
tree = newtree
return(tree)
def search_convert(s):
# This function convers a chapter name into a link
# such links are use in notabug.org to link to chapters
# for example example.com/file.md#chapter-name
# With this url it will load the example.com/file.md and
# then skip to the "Chapter Name" chapter.
# This function transforms "Chapter Name" into "chapter-name"
l = " ./\|[]{}()?!@#$%^&*`~:;'\"=,<>"
s = s.lower().replace(" ","-")
r = ""
for i in s:
if i not in l:
r = r + i
return r
def code_highlight(text):
codes = {
'"':"&#34;",
"'":"&#39;",
"`":"&#96;",
"\n":"&#10;",
"<":"&#60;",
">":"&#62;"
}
def R(tag):
for c in codes:
tag = tag.replace(c, codes[c])
return tag
text.replace("&", "&#38;")
for c in ("<", ">"):
text = text.replace(c, codes[c])
text = text.replace("\r","")
# green tags
ntext = ""
for tag in ['"""', "'''", '"', "'", "```", "`", ]:
while text.count(tag) > 1:
ntext = ntext + text[:text.find(tag)]+'<span class="text_string">'+R(tag)
text = text[text.find(tag)+len(tag):]
ntext = ntext + text[:text.find(tag)]+R(tag)+'</span>'
text = text[text.find(tag)+len(tag):]
ntext = ntext + text
text = ntext
# purple tags
ntext = ""
for n, line in enumerate(text.split("\n")):
if "=" in line.replace("class=", "class>") and " " not in line[:line.find("=")].strip():
line = '<span class="text_variable">'+line[:line.replace("class=", "class>").find("=")]+'</span><span class="text_command">=</span>'+line[line.replace("class=", "class>").find("=")+1:]
if n !=0:
ntext = ntext + "\n" + line
else:
ntext = line
text = ntext
# comments
ntext = ""
for n, line in enumerate(text.split("\n")):
if "#" in line.replace("&#", " "):
line = line[:line.replace("&#", " ").find("#")]+'<span class="text_comment">'+line[line.replace("&#", " ").find("#"):]+'</span>'
if n !=0:
ntext = ntext + "\n" + line
else:
ntext = line
# yellow tags
for tag in ["(", ")", "[", "]", "{", "}", "-", "+", ":", "==", "!=", "!", "$",
" / ", "*","try", "except","elif","if", "else", "print", "return",
"def", "for", "while", "range", "True", "False", "true", "false"]:
ntext = ntext.replace(tag, '<span class="text_command">'+tag+'</span>')
return ntext.replace("\n", codes["\n"])
def convert(filename, isfile=True, fullpath=False, comments={}, AP=False):
if fullpath:
from modules import Set
config = Set.Load()
domain = config.get("domain", "")
textReturn = ""
if isfile:
text = open(filename)
text = text.read()
else:
text = filename
md = Open(text, AP=AP)
close_div = False
for n, i in enumerate(md):
close_not_here = False
if i[-1].startswith(">") or i[-1].startswith("&gt") and not close_div:
i[-1] = '<div class="quote">' + i[-1].replace(">", "").replace("&gt", "")
close_div = True
elif close_div:
i[-1] = i[-1].replace(">", "").replace("&gt", "")
try:
NEXT = md[n+1]
except:
NEXT = ["text", ""]
if NEXT[-1].startswith(">") or NEXT[-1].startswith("&gt"):
close_not_here = True
if close_div and "\n" in i[-1] and not close_not_here:
i[-1] = i[-1]+"</div>"
close_div = False
if type(i[0]) == str and i[0].startswith("text") and not i[0] == "text_cm":
tag = ""
ctag = ""
for f in ["i", "b", "c"]:
if f in i[0]:
f = f.replace("c", "code").replace("q", "blockquote")
if f == "code": i[-1] = code_highlight(i[-1])
tag = "<"+f+">"
ctag = "</"+f+">"
if i[-1].startswith("lbry://"):
tag = '<a target="_blank" href="https://odysee.com/'+i[-1].replace("lbry://", "")[:-1]+'">'
ctag = "</a>"
if i[-1].startswith("http"):
tag = '<a target="_blank" href="'+i[-1][:-1]+'">'
ctag = "</a>"
if i[-1].startswith("c:"):
if AP:
i[-1] = ""
continue
try: N = int(i[-1].replace("c:",""))
except:N = None
try:
comment = Render.getcomment(N, comments.get("comments", []))
URL = comments.get("url","")
COMMENTS = comments.get("comments", [])
NOREFERENCE = comments.get("noreference", None)
try:
comment = Render.Comment(comment, URL, n=N, comments=COMMENTS, noreplies=True,
noreference=N,
nohashlink=True)
except:
comment = ""
if NOREFERENCE == None:
TEXT = """
<details>
<summary>
... c:"""+str(N)+"""
</summary>
"""+comment+"""
</details>
"""
i[-1] = TEXT.replace("\n", "")
elif NOREFERENCE == N:
i[-1] = ''
else:
i[-1] = '<small>'+i[-1]+'</small>'
except Exception as e: print(e)
textReturn = textReturn + tag + i[-1] + ctag
elif i[0] == "text_cm":
tag = '<pre>'
ctag = "</pre>"
textReturn = textReturn + tag + code_highlight(i[-1]) + ctag
elif type(i[0]) == int:
if textReturn.strip():
textReturn = textReturn + '\n</div><div class="dark_box"><br>'
textReturn = textReturn + '<a href="#'+search_convert(i[-1].replace("#", "").replace("\n", "")[1:])+'"><h'+str(i[0])+' id="'+search_convert(i[-1].replace("#", "").replace("\n", "")[1:])+'">' + i[-1].replace("#", "") +"</h"+str(i[0])+"></a>"
elif i[0] == "image_link":
if fullpath and i[-1].startswith("/"):
i[-1] = "https://"+domain+i[-1]
textReturn = textReturn + '\n<center><a target="_blank" href="'+i[-1]+'">'+'<img src="'+i[1]+'">'+"</a></center>"
elif i[0] == "image":
if fullpath and i[-1].startswith("/"):
i[-1] = "https://"+domain+i[-1]
if i[1] == "audio":
try:
i[-1] = odysee_get(i[-1])
textReturn = textReturn + '\n<audio controls="controls" width="20%" src="'+i[-1]+'"></audio>'
except:
textReturn = textReturn + '<details class="button"><summary>Failed to resolve.</summary> <code>'+i[-1]+'</code><br> You can try <a href="https://odysee.com/'+i[-1].replace("lbry://", "").replace("#", ":")+'">Odysee</a>. </details>'
elif i[1] == "video":
i[-1] = i[-1].replace("lbry://", "https://player.odycdn.com/speech/")
textReturn = textReturn + """\n<video width="100%" controls>
<source src=\""""+i[-1].replace("#", ":")+"""\" type="video/mp4">
</video> """
elif i[1] == "peertube-video":
i[-1] = i[-1].replace("/w/", "/videos/embed/")
textReturn = textReturn + """
<center>
<iframe style="width:100%; aspect-ratio:16 / 9;" src=\""""+i[-1]+"""" frameborder="0"" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe>
</center>
"""
elif i[1] in icons:
textReturn = textReturn + '<a target="_blank" href="'+i[-1]+'"><img style="vertical-align: middle" src="/icon/'+i[1]+'"></a>'
else:
i[-1] = i[-1].replace("lbry://", "https://spee.ch/")
textReturn = textReturn + '\n<center><a target="_blank" href="'+i[-1]+'"><img title="'+i[1].replace('"', "&quot;")+'" alt="[embedded image]" src="'+i[-1]+'"></a></center>'
elif i[0] == "link":
if not i[-1]:
i[-1] = "/search?text="+urllib.parse.quote_plus(i[1])
if fullpath and i[-1].startswith("/"):
i[-1] = "https://"+domain+i[-1]
textReturn = textReturn + '<a target="_blank" href="'+i[-1]+'">'+i[1]+"</a>"
textReturn = textReturn.replace("\n", "\n<br>")
textReturn = textReturn + "\n\n<!-- Raw Version of the markdown text\n\n"
textReturn = textReturn + text.replace("-->", "( somebody was trying to pentest the site here with exiting the comment early. Good job )") + "\n\n"
textReturn = textReturn + " -->\n\n"
return textReturn
def toMarkdown(text):
newtext = ""
tag = ""
link = ""
write = True
for letter in text:
if letter == "<":
write = False
continue
elif letter == ">":
write = True
if tag.startswith("a"):
link = tag[tag.find('href="')+6:]
link = link[:link.find('"')]
newtext = newtext + "["
elif tag.startswith("/a"):
newtext = newtext + ']('+link+')'
elif tag.startswith("b") or tag.startswith("/b"):
newtext = newtext + "**"
elif tag.startswith("i") or tag.startswith("/i"):
newtext = newtext + "*"
tag = ""
continue
if write:
newtext = newtext + letter
else:
tag = tag + letter
return newtext
+167
View File
@@ -0,0 +1,167 @@
# GPLv3 or later.
# (C) J.Y.Amihud 2025
# Program that troubleshoots execution of python scrips.
try:
import importlib.util
except:
NotFoundError("importlib.util")
exit()
try:
import os
except:
NotFoundError("os")
exit()
import __main__
count = 0
broken = 0
checked = []
def run():
global count
global broken
mainfilename = __main__.__file__
check(mainfilename)
if broken:
print()
print(" "+str(broken)+" missing modules found.")
print(" It is "+str(int(round(broken/count*100)))+"% of the program broken.")
print(" Running may introduce issues.")
print()
print(" You can try to run:")
print(" $ python3 -m pip install {module}")
print(" To install missing modules.")
print()
yn = input(clr["tdyl"]+"[ ? ]"+clr["norm"]+" Try to run anyway? ( Y/n ): ")
if "y" not in yn.lower():
exit()
def check(filename):
global count
global broken
global checked
checked.append(filename)
checkalso = []
text = open(filename, "r")
text = text.read()
if __file__ in filename:
return
for line in text.split("\n"):
if "import" in line and __name__ not in line:
if line.strip().startswith("from"):
line = line.strip()[5:]
line = line.replace(" import ", ".").strip()
if line.endswith("*"):
line = line[:-2]
elif line.strip().startswith("import"):
line = line.replace("import", "").strip()
else:
continue
if "," in line:
line = line[:line.find(",")].strip()
count += 1
try:
spec = importlib.util.find_spec(line)
except:
try:
line = line[:line.rfind(".")]
spec = importlib.util.find_spec(line)
except:
print(clr["tdyl"]+"[ ? ]"+clr["norm"], line)
continue
if spec:
#Found(spec)
if os.getcwd() in str(spec.origin):
checkalso.append(spec.origin)
else:
NotFoundError(line, filename)
broken += 1
if checkalso:
for f in checkalso:
if f not in checked:
try:
check(f)
except:
pass
def Found(spec):
print(clr["tdgr"]+"[ v ]"+clr["norm"], spec.name)
def NotFoundError(module, filename=""):
print(clr["tdrd"]+"[ x ]"+clr["norm"], module, clr["tbbl"]+"in "+filename+clr["norm"])
clr = {
"norm":"\033[00m", # Reset to normal
"bold":"\033[01m", # Bold Text
"ital":"\033[03m", # Italic Text
"undr":"\033[04m", # Underlined
"blnk":"\033[05m", # Blinking
# Text
"tdbl":"\033[30m", # Dark Black
"tdrd":"\033[31m", # Dark Red
"tdgr":"\033[32m", # Dark Green
"tdyl":"\033[33m", # Dark Yellow
"tdbu":"\033[34m", # Dark Blue
"tdma":"\033[35m", # Dark Magenta
"tdcy":"\033[36m", # Dark Cyan
"tdwh":"\033[37m", # Dark White
"tbbl":"\033[90m", # Bright Black
"tbrd":"\033[91m", # Bright Red
"tbgr":"\033[92m", # Bright Green
"tbyl":"\033[93m", # Bright Yellow
"tbbu":"\033[94m", # Bright Blue
"tbma":"\033[95m", # Bright Magenta
"tbcy":"\033[96m", # Bright Cyan
"tbwh":"\033[97m", # Bright White
# Background
"bdbl":"\033[40m", # Dark Black
"bdrd":"\033[41m", # Dark Red
"bdgr":"\033[42m", # Dark Green
"bdyl":"\033[43m", # Dark Yellow
"bdbu":"\033[44m", # Dark Blue
"bdma":"\033[45m", # Dark Magenta
"bdcy":"\033[46m", # Dark Cyan
"bdwh":"\033[47m", # Dark White
"bbbl":"\033[100m", # Bright Black
"bbrd":"\033[101m", # Bright Red
"bbgr":"\033[102m", # Bright Green
"bbyl":"\033[103m", # Bright Yellow
"bbbu":"\033[104m", # Bright Blue
"bbma":"\033[105m", # Bright Magenta
"bbcy":"\033[106m", # Bright Cyan
"bbwh":"\033[108m" # Bright White
}