1086 lines
44 KiB
Python
1086 lines
44 KiB
Python
from flask import Flask, redirect, url_for, render_template, render_template_string, request, send_file, send_from_directory, session
|
|
import yaml, cv2
|
|
from PIL import Image
|
|
from datetime import date, datetime
|
|
import time
|
|
from pprint import pprint
|
|
from random import choice
|
|
from os.path import exists
|
|
from os import remove
|
|
import re
|
|
import marklite
|
|
from hashlib import sha256
|
|
import hwip
|
|
from io import BytesIO
|
|
from base64 import b64encode
|
|
|
|
from nceicon import open_cei
|
|
|
|
app = Flask(__name__)
|
|
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024
|
|
app.secret_key = 'Glyphoglossus Molossus'
|
|
app.config['SESSION_TYPE'] = 'filesystem'
|
|
|
|
|
|
### funs to encode cei files for pyodide
|
|
def pad(to_pad, n):
|
|
while len(to_pad)<n:
|
|
to_pad = "0"+to_pad
|
|
return to_pad
|
|
|
|
def bytes_to_string(bobj):
|
|
string = ""
|
|
for byte in bobj:
|
|
string+=pad(hex(byte).split("x",1)[1],2)
|
|
|
|
return string
|
|
|
|
def string_to_bytes(sobj):
|
|
bobj = bytes([])
|
|
for n in range(0,len(sobj),2):
|
|
bobj+=bytes([int(sobj[n:n+2],16)])
|
|
return bobj
|
|
### end of funs
|
|
|
|
@app.before_request
|
|
def make_session_permanent():
|
|
session.permanent = True
|
|
|
|
@app.route("/noko/<text>")
|
|
def serve_img_noko(text):
|
|
img = hwip.sign(text)
|
|
img_io = BytesIO()
|
|
img.save(img_io, "PNG")
|
|
img_io.seek(0)
|
|
dataurl = '<img src="'+'data:image/png;base64,' + b64encode(img_io.getvalue()).decode('ascii')+'">'
|
|
return dataurl
|
|
|
|
@app.route("/ip/")
|
|
def get_ip():
|
|
return process_ip(request)
|
|
|
|
def process_ip(request):
|
|
if request.environ.get('HTTP_X_FORWARDED_FOR') is None:
|
|
addr = request.environ['REMOTE_ADDR']
|
|
else:
|
|
addr = request.environ['HTTP_X_FORWARDED_FOR']
|
|
|
|
aaddr = addr.split(",")[0]
|
|
try:
|
|
obj = bytes([int(i) for i in aaddr.split(".")])
|
|
sha = sha256(obj).hexdigest()
|
|
except Exception:
|
|
obj = bytes(aaddr, "utf-8")
|
|
sha = sha256(obj).hexdigest()
|
|
return sha
|
|
|
|
@app.route("/favicon.ico/")
|
|
def favicon():
|
|
return render_template_string("""<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">""")
|
|
|
|
@app.route("/")
|
|
def io_manager_index():
|
|
data = load("templates/index.html")
|
|
prev = ""
|
|
post_list = []
|
|
if exists("preview.yml"):
|
|
with open("preview.yml") as f:
|
|
post_list = yaml.safe_load(f.read())
|
|
for i in range(len(post_list)):
|
|
post_list[i]["text"] = post_list[i].get("text") if len(post_list[i].get("text"))<81 else post_list[i].get("text")[:80-3]+"..."
|
|
return render_template_string(data, preview=post_list)
|
|
|
|
@app.route("/getcei/<fname>")
|
|
def getcei(fname):
|
|
path = f"cei/{fname}"
|
|
if exists(path):
|
|
return send_file(path, as_attachment=True)
|
|
else:
|
|
return "<b>Error: Invalid Filename</b>"
|
|
|
|
@app.route("/liteshare/<fname>")
|
|
def liteshare(fname):
|
|
path = f"liteshare/{fname}"
|
|
if exists(path):
|
|
return send_file(path, as_attachment=True)
|
|
else:
|
|
return "<b>Error: Invalid Filename</b>"
|
|
|
|
@app.route("/admin/<input>")
|
|
def admin_actions(input):
|
|
series = input.split("-")
|
|
if series[0]=="setadmin":
|
|
password = series[1]
|
|
if password==load("admin.txt"):
|
|
with open("admin.yml", "w") as f:
|
|
f.write(yaml.dump({"admin": process_ip(request)}))
|
|
elif password==load("typh.txt"):
|
|
d = yaml.safe_load(load("admin.yml"))
|
|
d["typh"] = process_ip(request)
|
|
with open("admin.yml", "w") as f:
|
|
f.write(yaml.dump(d))
|
|
elif series[0]=="ban":
|
|
d = yaml.safe_load(load("admin.yml"))
|
|
hurts_when_ip = [d[key] for key in list(d.keys())]
|
|
if process_ip(request) in hurts_when_ip:
|
|
command, thread, post = series
|
|
thread, post = [int(i) for i in [thread, post]]
|
|
my_list = yaml.safe_load(load(f"{thread}.tp"))["posts"]+yaml.safe_load(load(f"{thread}.tp"))["pinned"]
|
|
post_data = [i for i in my_list if i["id"]==post][0]
|
|
expires = int(time.time()) + 60*60*24*3
|
|
if not exists("banned.yml"):
|
|
with open("banned.yml", "w") as f:
|
|
f.write(yaml.dump({"test": 0}))
|
|
blacklist = yaml.safe_load(load("banned.yml"))
|
|
blacklist[post_data["alias"]] = expires
|
|
with open("banned.yml", "w") as f:
|
|
f.write(yaml.dump(blacklist))
|
|
else:
|
|
return redirect("/post/ran-6-1")
|
|
elif series[0]=="del":
|
|
tpdict = yaml.safe_load(load(f"{series[1]}.tp"))
|
|
tplist = tpdict["posts"]
|
|
postdict = [p for p in tplist if p["id"]==int(series[2])][0]
|
|
d = yaml.safe_load(load("admin.yml"))
|
|
hurts_when_ip = [d[key] for key in list(d.keys())] + [postdict["alias"]]
|
|
#hurts_when_ip = [yaml.safe_load(load("admin.yml"))["admin"],postdict["alias"]]
|
|
if not process_ip(request) in hurts_when_ip:
|
|
return redirect("/post/ran-6-1")
|
|
else:
|
|
tpdict["posts"] = [p for p in tplist if p["id"]!=int(series[2])]
|
|
with open(f"{series[1]}.tp", "w") as f:
|
|
f.write(yaml.dump(tpdict))
|
|
if postdict["media"]:
|
|
for key in ["media","preview","thumbnail"]:
|
|
try:
|
|
remove(postdict[key])
|
|
except Exception:
|
|
pass
|
|
|
|
return redirect("/post/ran-5-1")
|
|
|
|
|
|
@app.route("/post/<info>")
|
|
def post_info(info):
|
|
board, action, link = info.split("-",2)
|
|
additional = "<p><img src='/static/banned.png' width=400></p>" if action=="4" else ""
|
|
actions = {"1": "Posted Successfully",
|
|
"2": "Incorrect Captcha",
|
|
"3": "Unsupported File Format",
|
|
"4": "You're banned",
|
|
"5": "Admin action issued",
|
|
"6": "Error Issuing the Admin Command"}
|
|
action = actions[action]
|
|
post = load("templates/post.html")
|
|
return render_template_string(post.replace("{{additional}}", additional), action=action, link="/"+board+"/"+link), {"Refresh": "3; url="+"/"+board+"/"+link}
|
|
|
|
@app.route("/cei/<file_path>")
|
|
def load_cei(file_path):
|
|
file_path = f"cei/{file_path}"
|
|
cei = bytes_to_string(open(file_path,"rb").read())#str(list(open(file_path,"rb").read()))
|
|
template = """<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<script src="https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.js"></script>
|
|
</head>
|
|
|
|
<body>
|
|
<p id="title">Loading modules...</p>
|
|
<p><img id="cei-image"></p>
|
|
<a href='/ceiget/"""+file_path.rsplit("/",1)[-1]+"""' download='"""+file_path.rsplit("/",1)[-1]+"""'>Download</a>
|
|
<a href='/liteshare/ceicon.zip' download="ceicon.zip">Get the CEI Viewer</a>
|
|
<script>
|
|
var ceibytes = '"""+cei+"""';
|
|
async function main() {
|
|
let pyodide = await loadPyodide();
|
|
await pyodide.loadPackage(["pillow","numpy","opencv-python","pyyaml"]);
|
|
document.getElementById("title").innerHTML = "Decoding image...";
|
|
pyodide.runPython(`
|
|
from js import document, ceibytes
|
|
from PIL import Image
|
|
import numpy as np
|
|
import cv2
|
|
import zlib
|
|
import yaml
|
|
from io import BytesIO
|
|
from base64 import b64encode
|
|
|
|
def get_handle(text):
|
|
if not isinstance(text, str):
|
|
text = " ".join(text)
|
|
if text[0].lower()!=text[0].upper():
|
|
delimit = " "
|
|
else:
|
|
delimit = text[0]
|
|
text = text[1:]
|
|
d = {"tags": []}
|
|
for tag in text.split(delimit):
|
|
if ":" in tag:
|
|
key, value = tag.split(":",1)
|
|
d[key] = value
|
|
else:
|
|
d["tags"].append(tag)
|
|
d["tags"] = " ".join(d["tags"])
|
|
|
|
if "name" in list(d.keys()) and "author" in list(d.keys()):
|
|
base = f"{d['name']} by {d['author']}"
|
|
elif "name" in list(d.keys()):
|
|
base = f"{d['name']}"
|
|
else:
|
|
base = "CEI file"
|
|
additional = ", tags: "+d["tags"] if d["tags"] else ""
|
|
return base+additional
|
|
|
|
def string_to_bytes(sobj):
|
|
bobj = bytes([])
|
|
for n in range(0,len(sobj),2):
|
|
bobj+=bytes([int(sobj[n:n+2],16)])
|
|
return bobj
|
|
|
|
def np_LSS_procedural(LSS, obj = []):
|
|
L, S_1, S_2 = cv2.split(LSS.astype(np.float32))
|
|
vnp_lss = np.vectorize(np_lss)
|
|
parts = 16
|
|
|
|
obj[:] = [None for i in range(parts)]
|
|
|
|
length = LSS.shape[0]
|
|
L_list = [L[int(length*i/parts):int(length*(i+1)/parts),:] for i in range(parts)]
|
|
S_1_list = [S_1[int(length*i/parts):int(length*(i+1)/parts),:] for i in range(parts)]
|
|
S_2_list = [S_2[int(length*i/parts):int(length*(i+1)/parts),:] for i in range(parts)]
|
|
|
|
for n in range(parts):
|
|
l, s1, s2 = [i[n] for i in [L_list,S_1_list,S_2_list]]
|
|
merged = cv2.merge(vnp_lss(l,s1,s2))
|
|
obj[n] = Image.fromarray(merged.astype(np.uint8))
|
|
img_io = BytesIO()
|
|
obj[n].save(img_io, "PNG")
|
|
img_io.seek(0)
|
|
dataurl = 'data:image/png;base64,' + b64encode(img_io.getvalue()).decode('ascii')
|
|
document.getElementById(f"cei-image-{n}").src = dataurl
|
|
|
|
def from_ibyte(ibyte, size):
|
|
ib = bin(ibyte).split("b")[-1]
|
|
while len(ib)<8:
|
|
ib = "0"+ib
|
|
volume = (int(ib[:3], base = 2)+1)*(int(ib[-4:-1], base = 2)+1)
|
|
x = 16 if not int(ib[3]) else size[0]%16
|
|
y = 16 if not int(ib[-1]) else size[1]%16
|
|
return (x,y), ((int(ib[:3], base = 2)+1),(int(ib[-4:-1], base = 2)+1)), volume
|
|
|
|
def cei_dict(bd):
|
|
l = sepget(bd)
|
|
d = dict()
|
|
for n in range(0, len(l), 2):
|
|
match str(l[n], "utf-8"):
|
|
case "version":
|
|
value = int.from_bytes(l[n+1], "big")
|
|
case "size" | "csize":
|
|
value = tuple([int.from_bytes(i, "big") for i in sepget(l[n+1])])
|
|
case "luminocity" | "chromaticity" | "palette":
|
|
value = zlib.decompress(l[n+1])
|
|
case "tags":
|
|
value = str(l[n+1], "utf-8")#tuple([str(i, "utf-8") for i in sepget(l[n+1])])
|
|
|
|
d[str(l[n],"utf-8")] = value
|
|
return d
|
|
|
|
def sepget(sequence):
|
|
items = []
|
|
while len(sequence):
|
|
prelen = sequence[-1]
|
|
sequence = sequence[:-1]
|
|
length = int.from_bytes(sequence[-prelen:], "big")
|
|
sequence = sequence[:-len(sequence[-prelen:])]
|
|
items = [sequence[-length:]] + items
|
|
sequence = sequence[:-length]
|
|
|
|
return items
|
|
|
|
def open_cei(fread, preview = False):
|
|
d = cei_dict(fread)
|
|
luma = d["luminocity"]
|
|
focus = 0
|
|
sectors = []
|
|
luma_bg = np.ndarray((d["size"][1],d["size"][0]), dtype = np.uint8)
|
|
while focus<len(luma):
|
|
sec_size, red_size, volume = from_ibyte(luma[focus], d["size"])
|
|
bytes_ = luma[focus+1:focus+volume+1]
|
|
#print(red_size)
|
|
array = np.array([int(i) for i in bytes_], dtype = np.uint8).reshape((red_size[1], red_size[0]))
|
|
sectors.append(cv2.resize(array, sec_size, interpolation = cv2.INTER_LINEAR))
|
|
focus+=volume+1
|
|
for y in range(0, d["size"][1], 16):
|
|
for x in range(0, d["size"][0], 16):
|
|
sector = sectors.pop(0)
|
|
h, w = sector.shape
|
|
luma_bg[y:y+h,x:x+w] = sector
|
|
#cv2.imshow("img",luma_bg)
|
|
if preview:
|
|
return Image.fromarray(luma_bg), d["tags"]
|
|
pal_ind = 0
|
|
palette = dict()
|
|
for n in range(0, len(d["palette"]), 2):
|
|
palette[pal_ind] = tuple(d["palette"][n:n+2])
|
|
pal_ind+=1
|
|
|
|
colors = np.array([0,palette[0][0],palette[0][1]], dtype = np.uint8)#[]
|
|
csize = d["csize"]
|
|
for n, index in enumerate(d["chromaticity"]):
|
|
if n:
|
|
colors = np.append(colors, np.array([0,palette[index][0],palette[index][1]], dtype = np.uint8), axis = 0)
|
|
|
|
lss = colors.reshape(csize[1],csize[0],3)
|
|
lss = cv2.resize(lss, d["size"], interpolation = cv2.INTER_LINEAR)
|
|
lss[:,:,0] = luma_bg
|
|
return np_LSS(lss), d["tags"]
|
|
|
|
def np_lss(L,S_1,S_2):
|
|
try:
|
|
s1 = S_1/(255-S_1)
|
|
except ZeroDivisionError:
|
|
s1 = S_1
|
|
try:
|
|
s2 = (255-S_2)/S_2
|
|
except ZeroDivisionError:
|
|
s2 = 255
|
|
G = 3*L / ( s1 + s2 + 1 )#source[n*3]
|
|
try:
|
|
R = (S_1*G)/(255-S_1)
|
|
except ZeroDivisionError:
|
|
R = (S_1*G)/1
|
|
try:
|
|
B = G*(255-S_2)/S_2
|
|
except ZeroDivisionError:
|
|
B = G*(255-S_2)/1
|
|
maximal = max([R,G,B])
|
|
if maximal > 255:
|
|
multiplier = 255/maximal
|
|
R,G,B = R*multiplier,G*multiplier,B*multiplier
|
|
R,G,B = round(R),round(G),round(B)
|
|
return R,G,B
|
|
|
|
def np_LSS(LSS):#convert lss back to rgb
|
|
L, S_1, S_2 = cv2.split(LSS.astype(np.float32))
|
|
vnp_lss = np.vectorize(np_lss)
|
|
merged = cv2.merge(vnp_lss(L,S_1,S_2))
|
|
return Image.fromarray(merged.astype(np.uint8))
|
|
|
|
if __name__=="__main__":
|
|
pil_im, text = open_cei(string_to_bytes(ceibytes))
|
|
img_io = BytesIO()
|
|
pil_im.save(img_io, "PNG")
|
|
img_io.seek(0)
|
|
dataurl = 'data:image/png;base64,' + b64encode(img_io.getvalue()).decode('ascii')
|
|
document.getElementById("cei-image").src = dataurl
|
|
document.getElementById("title").innerHTML = get_handle(text);
|
|
`);
|
|
};
|
|
|
|
main();
|
|
</script>
|
|
</body>
|
|
</html>"""
|
|
|
|
return render_template_string(template)
|
|
|
|
@app.route("/<p1>/", methods=['post', 'get'])
|
|
def io_manager_board(p1):
|
|
params = p1+"/"
|
|
r = Request(token = process_ip(request),
|
|
captcha = request.form.get('security_id'),
|
|
content = request.form.get('content'),
|
|
file = request.files.get('my_file'),
|
|
type = request.method,
|
|
params = params)
|
|
return r.process()
|
|
|
|
@app.route("/<p1>/<p2>", methods=['post', 'get'])
|
|
def io_manager_thread(p1, p2):
|
|
params = p1+"/"+p2
|
|
r = Request(token = process_ip(request),
|
|
captcha = request.form.get('security_id'),
|
|
content = request.form.get('content'),
|
|
file = request.files.get('my_file'),
|
|
type = request.method,
|
|
params = params)
|
|
return r.process()
|
|
|
|
class Request:
|
|
def __init__(self, token, captcha, content, file, type, params):
|
|
self.token = token
|
|
self.captcha = captcha
|
|
self.content = content
|
|
self.file = file
|
|
self.type = type
|
|
self.params = params
|
|
|
|
pdict = parse_params(self.params)
|
|
self.pdict = pdict
|
|
|
|
if pdict["type"]=="frontpage":
|
|
self.page = ""
|
|
elif pdict["type"]=="board":
|
|
self.page = Thread(self)
|
|
elif pdict["type"]=="catalog":
|
|
self.page = Catalog(self)
|
|
elif pdict["type"]=="thread":
|
|
self.page = Thread(self)
|
|
elif pdict["type"]=="post":
|
|
self.page = Post(self)
|
|
|
|
def process(self):
|
|
if self.type=="POST":
|
|
return self.make_post_new()
|
|
return render_template_string(self.page.render())
|
|
|
|
def make_post(self):
|
|
if self.pdict["type"]=="board":
|
|
fname = f"{get_post_number()}.tp"
|
|
me, pr, th = process_media(self.file)
|
|
increase_post_number()
|
|
d = {"alias": self.token,
|
|
"id": get_post_number(),
|
|
"name": "Anonymous",
|
|
"thumbnail": th,
|
|
"preview": pr,
|
|
"media": me,
|
|
"text": self.content,
|
|
"date": str(date.today().strftime("%d/%m/%Y"))+" "+datetime.now().strftime("%H:%M:%S")}
|
|
bfile = self.pdict["board"]+".b"
|
|
with open(bfile, "r") as f:
|
|
thread_list = yaml.safe_load(f.read())
|
|
thread_list["posts"] = [fname]+thread_list["posts"]
|
|
if thread_list==None:
|
|
thread_list = []
|
|
with open(bfile, "w") as f:
|
|
f.write(yaml.dump(thread_list))
|
|
with open(fname, "w") as f:
|
|
f.write(yaml.dump([d]))
|
|
return_board = self.pdict["board"]
|
|
return_thread = ""
|
|
elif self.pdict["type"]=="thread":
|
|
fname = f"{self.pdict['index']}.tp"
|
|
me, pr, th = process_media(self.file)
|
|
increase_post_number()
|
|
d = {"alias": self.token,
|
|
"id": get_post_number(),
|
|
"name": "Anonymous",
|
|
"thumbnail": th,
|
|
"preview": pr,
|
|
"media": me,
|
|
"text": self.content,
|
|
"date": str(date.today().strftime("%d/%m/%Y"))+" "+datetime.now().strftime("%H:%M:%S")}
|
|
print("fname:",fname)
|
|
with open(fname, "r") as f:
|
|
tp = yaml.safe_load(f.read())
|
|
tp["posts"]+=[d]
|
|
with open(fname, "w") as f:
|
|
f.write(yaml.dump(tp))
|
|
return_board = self.pdict["board"]
|
|
return_thread = self.pdict["thread"]
|
|
return redirect("/post/"+f"{return_board}-1-{return_thread}")
|
|
|
|
def make_post_new(self):
|
|
if exists("banned.yml"):
|
|
banned = yaml.safe_load(load("banned.yml"))
|
|
if self.token in list(banned.keys()):
|
|
if time.time() < banned[self.token]:
|
|
return_board = self.pdict["board"]
|
|
return_thread = self.pdict["thread"]
|
|
return redirect("/post/"+f"{return_board}-4-{return_thread}")
|
|
else:
|
|
banned.pop(self.token)
|
|
with open("banned.yml", "w") as f:
|
|
f.write(yaml.dump(banned))
|
|
if self.pdict["type"] == "board":
|
|
if not "legacy":
|
|
fname = f"{get_post_number()}.tp"
|
|
me, pr, th = process_media(self.file)
|
|
increase_post_number()
|
|
d = {"alias": self.token,
|
|
"id": get_post_number(),
|
|
"name": "Anonymous",
|
|
"thumbnail": th,
|
|
"preview": pr,
|
|
"media": me,
|
|
"text": self.content,
|
|
"date": str(date.today().strftime("%d/%m/%Y"))+" "+datetime.now().strftime("%H:%M:%S"),
|
|
"board": self.pdict["board"]
|
|
}
|
|
bfile = self.pdict["board"]+".b"
|
|
with open(bfile, "r") as f:
|
|
thread_list = yaml.safe_load(f.read())
|
|
thread_list["posts"] = [fname]+thread_list["posts"]
|
|
if thread_list==None:
|
|
thread_list = []
|
|
with open(bfile, "w") as f:
|
|
f.write(yaml.dump(thread_list))
|
|
with open(fname, "w") as f:
|
|
f.write(yaml.dump([d]))
|
|
return_board = self.pdict["board"]
|
|
return_thread = ""
|
|
else:
|
|
fname = f"{get_post_number()}.tp"
|
|
me, pr, th = process_media(self.file)
|
|
increase_post_number()
|
|
d = {"alias": self.token,
|
|
"id": get_post_number(),
|
|
"name": "Anonymous",
|
|
"thumbnail": th,
|
|
"preview": pr,
|
|
"media": me,
|
|
"text": self.content,
|
|
"date": str(date.today().strftime("%d/%m/%Y"))+" "+datetime.now().strftime("%H:%M:%S"),
|
|
"board": self.pdict["board"]
|
|
}
|
|
bfile = self.pdict["board"]+".b"
|
|
with open(bfile, "r") as f:
|
|
thread_dict = yaml.safe_load(f.read())
|
|
if thread_dict==None:
|
|
thread_dict = {"pinned": [], "posts": []}
|
|
thread_dict["posts"] = [fname]+thread_dict["posts"]
|
|
with open(bfile, "w") as f:
|
|
f.write(yaml.dump(thread_dict))
|
|
with open(fname, "w") as f:
|
|
f.write(yaml.dump({"pinned": [d],"posts": []}))
|
|
return_board = self.pdict["board"]
|
|
return_thread = ""
|
|
elif self.pdict["type"]=="thread":
|
|
fname = f"{self.pdict['index']}.tp"
|
|
self.bump(f"{self.pdict['board']}.b",fname)
|
|
me, pr, th = process_media(self.file)
|
|
increase_post_number()
|
|
d = {"alias": self.token,
|
|
"id": get_post_number(),
|
|
"name": "Anonymous",
|
|
"thumbnail": th,
|
|
"preview": pr,
|
|
"media": me,
|
|
"text": self.content,
|
|
"date": str(date.today().strftime("%d/%m/%Y"))+" "+datetime.now().strftime("%H:%M:%S"),
|
|
"board": self.pdict["board"],
|
|
"thread": self.pdict["index"]
|
|
}
|
|
with open(fname, "r") as f:
|
|
tp = yaml.safe_load(f.read())
|
|
tp["posts"]+=[d]
|
|
with open(fname, "w") as f:
|
|
f.write(yaml.dump(tp))
|
|
return_board = self.pdict["board"]
|
|
return_thread = self.pdict["thread"]
|
|
if not "nsfw" in self.pdict["board"]:#preview in main page modification
|
|
if exists("preview.yml"):
|
|
base_list = yaml.safe_load(open("preview.yml").read())
|
|
else:
|
|
base_list = []
|
|
base_list.append(d)
|
|
if len(base_list)>3:
|
|
base_list.pop(0)
|
|
with open("preview.yml", "w") as f:
|
|
f.write(yaml.dump(base_list))
|
|
return redirect("/post/"+f"{return_board}-1-{return_thread}")
|
|
|
|
def bump(self, bname, tname):
|
|
board_dict = yaml.safe_load(load(bname))
|
|
if tname in board_dict["pinned"]:
|
|
board_dict["pinned"].pop(board_dict["pinned"].index(tname))
|
|
board_dict["pinned"] = [tname] + board_dict["pinned"]
|
|
elif tname in board_dict["posts"]:
|
|
board_dict["posts"].pop(board_dict["posts"].index(tname))
|
|
board_dict["posts"] = [tname] + board_dict["posts"]
|
|
with open(bname, "w") as f:
|
|
f.write(yaml.dump(board_dict))
|
|
|
|
class Catalog:
|
|
def __init__(self, master):
|
|
self.master = master
|
|
|
|
def render(self):
|
|
with open("templates/catalog.html") as f:
|
|
base = f.read()
|
|
row = "<tr> <td>{}</td> <td>{}</td> <td>{}</td> <td>{}</td> </tr>"
|
|
if self.master.pdict["type"]=="catalog":
|
|
post_info_names = get_posts_from_yaml(f"{self.master.pdict['board']}.b")
|
|
post_info = [merge(get_posts_from_yaml(post_info_name)[0] , {"thread": post_info_name.split(".")[0]}) for post_info_name in post_info_names]
|
|
rows = []
|
|
while post_info:
|
|
four = []
|
|
for _ in range(4):
|
|
if post_info:
|
|
four.append(Post(self, post_info.pop(0)).render_for_catalog())
|
|
else:
|
|
four.append("")
|
|
rows.append(row.format(*four))
|
|
return render_template_string( load("templates/anonymous6.html").replace("{{board}}",self.get_catalog()).replace("{{tables}}",render_template_string(base.replace("{{TABLE_ROWS}}","\n".join(rows)))),
|
|
dt = datetime.now().strftime('%d/%m/%Y %H:%M:%S')+random_arquee(), board_title = yaml.safe_load(load(f"{self.master.pdict['board']}.b")).get("title"))
|
|
|
|
def get_catalog(self):
|
|
html = f"""<font color="#47292b">
|
|
<font size="6" style="font-size: 28pt">
|
|
<span lang="en-US">
|
|
<a style="color:#954f72;" href="/{self.master.pdict["board"]}/">
|
|
<b>/{self.master.pdict["board"]}/</b></a>
|
|
</span>
|
|
</font>
|
|
</font>"""
|
|
return html
|
|
|
|
class Thread:
|
|
def __init__(self, master):
|
|
self.master = master
|
|
|
|
def render(self):
|
|
with open("templates/anonymous6.html") as f:
|
|
base = f.read().replace("{{board}}",self.get_catalog())
|
|
if self.master.pdict["type"]=="board":
|
|
post_info_names = get_posts_from_yaml(f"{self.master.pdict['board']}.b")#yaml.safe_load(load(f"{self.master.pdict['board']}.b"))
|
|
post_info = [merge(get_posts_from_yaml(post_info_name)[0] , {"thread": post_info_name.split(".")[0]}) for post_info_name in post_info_names]
|
|
elif self.master.pdict["type"]=="thread":
|
|
post_info = [merge(pi , {"thread": f"{self.master.pdict['thread']}"}) for pi in get_posts_from_yaml(f"{self.master.pdict['thread']}.tp")]
|
|
return render_template_string(base.replace("{{tables}}","\n".join([Post(self, pi).render() for pi in post_info]+self.get_login())),
|
|
dt = datetime.now().strftime('%d/%m/%Y %H:%M:%S')+random_arquee(), board_title = yaml.safe_load(load(f"{self.master.pdict['board']}.b")).get("title"))
|
|
|
|
def get_login(self):
|
|
login = """<p></p><p align='center'>{% block captcha %}{% if """+str(captcha_enabled())+""" %}<img width=400 height=90
|
|
src='"""+serve_captcha("static/"+self.master.token+".jpg")+"""' v:shapes="IMG">{% endif %}"""+"""{% endblock captcha %}<form align='center' action="" method="post" enctype = "multipart/form-data" style=style="text-size-adjust:none">
|
|
</p><div style= "margin-left:auto;margin-right:auto;align:center;width:100%;text-align:center">
|
|
<p>
|
|
<table>
|
|
<tr>
|
|
"""+"""{% block log %}{% if """+str(captcha_enabled())+""" %}<td style= "width:20%"><label>Captcha: </label></td>
|
|
<td style= "width:80%"><input style= "width:100%" type="password" name="security_id"></td>{% endif %}{% endblock log %}"""+"""
|
|
</tr>
|
|
</table>
|
|
</p>
|
|
<p align="center">
|
|
<textarea {ta} style= "width:100%" name="content" rows="10" cols="40" id="maintextarea"></textarea>
|
|
{div}
|
|
</p>
|
|
<p>
|
|
<input name="my_file" type="file">
|
|
</p>
|
|
<p>
|
|
<input type="submit">
|
|
</p></div>
|
|
</form>""".format(ta = """onKeyDown="textCounter(this,'progressbar1',2000)"
|
|
onKeyUp="textCounter(this,'progressbar1',2000)"
|
|
onFocus="textCounter(this,'progressbar1',2000)""", div = """<div style= "margin-left:auto;margin-right:auto;align:center;width:100%" id="progressbar1" class="progress"></div>
|
|
|
|
<script>textCounter(document.getElementById("maxcharfield"),"progressbar1",2000)</script>""")
|
|
return [login]
|
|
|
|
def get_catalog(self):
|
|
html = f"""<font color="#47292b">
|
|
<font size="6" style="font-size: 28pt">
|
|
<span lang="en-US">
|
|
<a style="color:#954f72;" href="/{self.master.pdict["board"]}/catalog">
|
|
<b>/{self.master.pdict["board"]}/</b></a>
|
|
</span>
|
|
</font>
|
|
</font>"""
|
|
return html
|
|
|
|
class Post:
|
|
def __init__(self, master, info):
|
|
#print("Info:", info["thread"])
|
|
self.info = info
|
|
self.master = master
|
|
|
|
def render(self):
|
|
text = self.info["text"]
|
|
text = emojis(text)
|
|
|
|
dots = '''<div class="dropdown">
|
|
<button class="dropbtn"><span lang="EN-US" style="mso-ansi-language:EN-US;padding:0cm 5.4pt 0cm 5.4pt">...<o:p></o:p></span></button>
|
|
<div class="dropdown-content">
|
|
<a href="/admin/del-{}-{}"><span lang="EN-US" style="mso-ansi-language:EN-US;padding:0cm 5.4pt 0cm 5.4pt">Delete post<o:p></o:p></span></a>
|
|
<a href="{}"><span lang="EN-US" style="mso-ansi-language:EN-US;padding:0cm 5.4pt 0cm 5.4pt">View original<o:p></o:p></span></a>
|
|
<a href="/admin/ban-{}-{}"><span lang="EN-US" style="mso-ansi-language:EN-US;padding:0cm 5.4pt 0cm 5.4pt">Ban poster<o:p></o:p></span></a>
|
|
<a onclick="javascript:var mta = document.getElementById('maintextarea');mta.value += '#{}\\n';textCounter(mta,'progressbar1',2000);"><span lang="EN-US" style="mso-ansi-language:EN-US;padding:0cm 5.4pt 0cm 5.4pt">Reply<o:p></o:p></span></a>
|
|
</div>
|
|
</div>'''.format(self.info["thread"],self.info["id"],"/"+self.info["media"],self.info["thread"],self.info["id"],self.info["id"])#board
|
|
|
|
if 0:
|
|
regular = """<p style="text-align:justify">{text}</p>"""#{thread_link} #7C7C7C
|
|
green = """<p style="color:#70AD47;text-align:justify">>{text}</p>"""
|
|
base_link = """<a href={thread_link} style:"color:#7c7c7c;background-color: transparent;text-decoration:none"><span style="text-align:justify">{text}</span></a>"""
|
|
title_form = """<b><p style="font-size:18.0pt;text-align:justify">{title}</p></b>"""
|
|
|
|
processed_text = ""
|
|
focus = 0
|
|
while focus<len(text):
|
|
if text[focus] in "#[>\n":
|
|
if text[focus]=="#":
|
|
start = focus+1
|
|
end = start
|
|
while end<len(text) and text[end] in "0123456789":
|
|
end+=1
|
|
link = f"/{self.master.master.pdict['board']}/{self.info['thread']}#{text[start:end]}"
|
|
high_text = "#"+text[start:end]
|
|
processed_text+=base_link.format(thread_link = link, text = high_text)
|
|
focus = end
|
|
elif text[focus]=="[":
|
|
end = text.find("]", focus)
|
|
processed_text+=title_form.format(title = text[focus+1:end])
|
|
focus = end+1
|
|
elif text[focus]=="\n":
|
|
processed_text+="<p></p>"
|
|
focus+=1
|
|
elif (focus==0 or text[focus-1]=="\n") and text[focus]==">":
|
|
end = focus+1
|
|
while end<len(text) and text[end]!="\n":
|
|
end+=1
|
|
processed_text+=green.format(text = text[focus+1:end])
|
|
focus = end+1
|
|
elif text[focus]==">":
|
|
processed_text+=text[focus]
|
|
focus+=1
|
|
else:
|
|
processed_text+=text[focus]
|
|
focus+=1
|
|
else:
|
|
processed_text = marklite.render(text, {"board":self.master.master.pdict['board'],"thread":self.info['thread']})
|
|
self.is_video = False##########
|
|
#processed_text = emojis(processed_text)
|
|
self.table_info = {"div_id": self.info["id"],
|
|
"x": 200,
|
|
"y": 200,
|
|
"image": self.get_popup(),
|
|
"headercolor": "#ecddbe",
|
|
"bgcolor": "#fff",
|
|
"dots": dots,
|
|
"name": self.info["name"],
|
|
"date": time_label(self.info["date"]),
|
|
"number": self.get_number(),
|
|
"content": processed_text}
|
|
|
|
|
|
table = self.get_table()
|
|
return table
|
|
|
|
def get_number(self):
|
|
inside = "#"+str(self.info["id"])
|
|
base = """<a href="{thread_link}" style:"color:#7c7c7c;background-color: transparent;text-decoration:none"><span style="text-align:justify">{text}</span></a>"""
|
|
return base.format(thread_link = f"/{self.master.master.pdict['board']}/{self.info['thread']}", text = inside)
|
|
|
|
def render_for_catalog(self):
|
|
regular = """<span style="text-align:justify">{text}</span>"""#{thread_link} #7C7C7C
|
|
green = """<p style="color:#70AD47;text-align:justify">>{text}</p>"""
|
|
base_link = """<a href={thread_link} style:"color:#7c7c7c;background-color: transparent;text-decoration:none"><span style="text-align:justify">{text}</span></a>"""
|
|
title_form = """<b><p style="font-size:18.0pt;text-align:justify">{title}</p></b>"""
|
|
|
|
base = load("templates/catalog_post.html")
|
|
text = self.info["text"]# if len(self.info["text"])<25 else self.info["text"][:25]
|
|
self.info["board"] = self.master.master.pdict["board"]
|
|
#text = emojis(text)
|
|
processed_text = ""
|
|
focus = 0
|
|
while focus<len(text) and focus<25:
|
|
#print("inside focus", focus, text[focus])
|
|
if text[focus] in "#[>\n":
|
|
if text[focus]=="#":
|
|
start = focus+1
|
|
end = start+1
|
|
while end<len(text) and text[end] in "0123456789":
|
|
end+=1
|
|
link = f"/{self.master.master.pdict['board']}/{self.info['thread']}-{text[start:end]}"
|
|
high_text = text[start:end]
|
|
processed_text+=base_link.format(thread_link = link, text = high_text)
|
|
focus = end+1
|
|
|
|
elif text[focus]=="[":
|
|
end = text.find("]", focus)
|
|
processed_text+=title_form.format(title = text[focus+1:end])
|
|
focus = end+1 if end!=-1 else focus+1
|
|
elif text[focus]=="\n":
|
|
processed_text+="<p></p>"
|
|
focus+=1
|
|
else:
|
|
end = focus+1
|
|
while end<len(text) and text[end]!="\n":
|
|
end+=1
|
|
processed_text+=green.format(text = text[focus+1:end])
|
|
focus = end+1
|
|
else:
|
|
processed_text+=text[focus]
|
|
focus+=1
|
|
#base = render_template_string(base, number = )
|
|
processed_text = emojis(processed_text)
|
|
return render_template_string(base.replace("{{number}}",self.get_number()).replace("{{title}}",processed_text), thread_link = f"/{self.info['board']}/"+str(self.info["thread"]), image = "/"+self.info["thumbnail"])
|
|
|
|
def get_table(self):
|
|
table = """<div id="{div_id}" class="post">
|
|
<table class = "posttable" bgcolor="{bgcolor}" align="center" border="0" cellspacing="0" cellpadding="0">
|
|
<tr style="background:#ecddbe;">
|
|
<td class="postname"><span style="padding-right:5px">{dots}</span><span>{name}</span></td><td style="width:60%"><table width="100%"><td>{date}</td><td align="right">{number}</td></table></td>
|
|
</tr>
|
|
<tr class="postbody">
|
|
<td width="100%" colspan="2"><div style="padding: 5px;"><span style="width:100%;">{image}</span><p style="width:100%;">{content}</p></div></td>
|
|
</tr>
|
|
</table>
|
|
</div>""".format(**self.table_info)
|
|
return table
|
|
|
|
def get_popup(self):
|
|
print(self.info["media"])
|
|
is_video = (self.info["media"].lower().endswith(".mp4") or self.info["media"].lower().endswith(".webm"))
|
|
if not is_video:
|
|
template = load("templates/popup_universal.html")
|
|
print("image")
|
|
else:
|
|
print("video")
|
|
template = load("templates/popup_universal.html")
|
|
if self.info["media"]:
|
|
if not is_video:
|
|
return render_template_string(template, hash_id = self.info["id"],
|
|
thumbnail = "/"+self.info["thumbnail"],
|
|
preview = "/"+self.info["preview"],
|
|
fname = self.info["media"],
|
|
original_link = "/"+self.info["media"])
|
|
else:
|
|
return render_template_string(template, hash_id = self.info["id"],
|
|
thumbnail = "/"+self.info["thumbnail"],
|
|
preview = "/"+self.info["media"],
|
|
fname = self.info["media"],
|
|
original_link = "/"+self.info["media"])
|
|
else:
|
|
return ""
|
|
|
|
def get_posts_from_yaml(fname):
|
|
try:
|
|
data = yaml.safe_load(load(fname))
|
|
if isinstance(data, dict):
|
|
if data["pinned"]:
|
|
return data["pinned"]+data["posts"]
|
|
else:
|
|
return data["posts"]
|
|
elif isinstance(data, list):
|
|
return data
|
|
else:
|
|
raise TypeError("")
|
|
except Exception as e:
|
|
return []
|
|
|
|
def random_arquee():
|
|
with open("marquee.data") as f:
|
|
data = yaml.safe_load(f.read())
|
|
return " «"+render_template_string(choice(data))+"»"
|
|
|
|
def time_label(data):
|
|
html = """<style>
|
|
/* Container for the text that expands on hover */
|
|
.expanded-text {
|
|
width: 100%;
|
|
display: inline-block;
|
|
}
|
|
/* Longer name hidden by default */
|
|
span.longer-name{
|
|
display:none;
|
|
}
|
|
/* On hover, hide the short name */
|
|
.expanded-text:hover span.short-name{
|
|
display:none;
|
|
}
|
|
/* On hover, display the longer name. */
|
|
.expanded-text:hover span.longer-name{
|
|
display:block;
|
|
}
|
|
</style><span align ="left" class="expanded-text">
|
|
<span class="short-name"><span>{short}</span></span>
|
|
<span class="longer-name"><span>{full_}</span></span></span>""".replace("{short}",data.split(" ")[0]).replace("{full_}", data)#.format(short=data.split(" ")[0], full=data)
|
|
return html
|
|
|
|
def serve_captcha(filename):
|
|
return ""
|
|
|
|
def captcha_enabled():
|
|
return False
|
|
|
|
def get_popup_legacy(pngs):
|
|
one, two, three = [load("templates/"+i) for i in ["pop1.html","pop4.html","pop3.html"]]
|
|
images = []
|
|
for png in pngs:
|
|
tag = "video" if ".mp4" in png else "img"
|
|
if ".png" in png:
|
|
images.append(render_template_string(two, item_hash = str(abs(hash(png))), file_jpg = png.replace(".png",".jpg"),
|
|
uri=serve_pil_image("static/"+png), png=png, full="/static/"+png.split(".")[0]+".png", tag=tag))
|
|
else:
|
|
images.append(render_template_string(two, item_hash = str(abs(hash(png))), file_jpg = png.replace(".png",".jpg"),
|
|
uri=serve_pil_image("static/"+png), png=png, full="/static/"+png.split(".")[0]+".png", tag=tag))
|
|
return one, images[0], three#"\n".join([one,*images,three])
|
|
|
|
def get_popup_video_legacy(pngs):
|
|
one, two, three = [load("templates/"+i) for i in ["pop1.html","pop5.html","pop3.html"]]
|
|
images = []
|
|
for png in pngs:
|
|
tag = "video" if ".mp4" in png else "img"
|
|
if ".mp4" in png or 1:
|
|
images.append(render_template_string(two, item_hash = str(abs(hash(png))), file_jpg = png.replace(".mp4",".jpg"),
|
|
uri=serve_pil_image("static/"+png), png=png, full="/static/"+png.split(".")[0]+".mp4", tag = tag))
|
|
else:
|
|
images.append(render_template_string(two, item_hash = str(abs(hash(png))), file_jpg = png.replace(".mp4",".jpg")))
|
|
return one, images[0], three#"\n".join([one,*images,three])
|
|
|
|
def load(filename):
|
|
with open(filename) as f:
|
|
output = f.read()
|
|
return output
|
|
|
|
def emojis(text):
|
|
sequence = text.split(":")
|
|
if len(sequence)<3:
|
|
return text
|
|
for n in range(1,len(sequence),2):
|
|
if set(sequence[n]).issubset(set("_qwertyuiopasdfghjklzxcvbnm0123456789")):
|
|
sequence[n] = '''<img class="emoji" height="36" '''+f'title=":{sequence[n]}:"'+'''src="{{url_for('static','''+f'filename="emoji.{sequence[n]}.png"'+''')}}">'''
|
|
else:
|
|
sequence[n] = ":"+sequence[n]+":"
|
|
return "".join(sequence)
|
|
|
|
def emojis2(text):
|
|
pattern_list = re.findall("(?<=:)[a-zA-Z]+(?=:)", text)
|
|
for pat in pattern_list:
|
|
print(pat)
|
|
if exists(f"static/emoji.{pat}.png"):
|
|
emoji = '''<img class="emoji" height="36" '''+f'title=":{pat}:"'+'''src="{{url_for('static','''+f'filename="emoji.{pat}.png"'+''')}}">'''
|
|
text = text.replace(f":{pat}:", emoji, 1)
|
|
else:
|
|
pass
|
|
return text
|
|
|
|
def parse_params(params):
|
|
plist = params.split("/")
|
|
d = dict()
|
|
if len(plist)==1 and not plist[0]:#frontpage
|
|
d["type"] = "frontpage"
|
|
elif len(plist)==1:#board
|
|
d["type"] = "board"
|
|
d["board"] = plist[0]
|
|
elif len(plist)==2:#thread or catalog
|
|
if plist[-1].isnumeric():
|
|
d["type"] = "thread"
|
|
d["thread"] = plist[1]
|
|
d["index"] = plist[-1]
|
|
elif plist[-1]=="catalog":
|
|
d["type"] = "catalog"
|
|
else:
|
|
d["type"] = "board"
|
|
d["board"] = plist[0]
|
|
else:#post
|
|
d["thread"] = plist[1]
|
|
d["post"] = plist[2]
|
|
d["type"] = "post"
|
|
print(d)
|
|
return d
|
|
|
|
def get_post_number():
|
|
with open("data/post_number.data") as f:
|
|
n = int(f.read())
|
|
return n
|
|
|
|
def increase_post_number():
|
|
n = get_post_number()+1
|
|
with open("data/post_number.data", "w") as f:
|
|
f.write(str(n))
|
|
|
|
def to_pil(imgOpenCV):
|
|
return Image.fromarray(cv2.cvtColor(imgOpenCV, cv2.COLOR_BGR2RGB))
|
|
|
|
def video_thumbnail(fname):
|
|
vidcap = cv2.VideoCapture(fname)
|
|
video_length = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
success,image = vidcap.read()
|
|
count = 0
|
|
while success and count<0.25*video_length:
|
|
success, image = vidcap.read()
|
|
count += 1
|
|
pimg = to_pil(image)
|
|
pimg.thumbnail((200,200))
|
|
return pimg
|
|
|
|
def process_media(file):
|
|
fname = "static/"+file.filename
|
|
ext = fname.split(".")[-1].lower()
|
|
supported_images = ["png",
|
|
"jpg",
|
|
"jpeg",
|
|
"jpp",
|
|
"gif",
|
|
"webp",
|
|
"apng",
|
|
"tiff"]
|
|
supported_videos = ["mp4",
|
|
"webm"]
|
|
supported_formats = supported_images + supported_videos + ["cei"]
|
|
|
|
if not ext in supported_formats:
|
|
return "", "", ""
|
|
else:
|
|
if ext in supported_images:
|
|
file.save(fname)
|
|
original = Image.open(fname)
|
|
preview = fname+".preview.jpg"
|
|
original.convert("RGB").save(preview, quality = 90, optimize = True, progressive = True)
|
|
thumbnail = fname+".thumbnail.jpg"
|
|
th = original.copy().convert("RGB")
|
|
th.thumbnail((200,200))
|
|
th.save(thumbnail, quality = 75, optimize = True, progressive = True)
|
|
elif ext=="cei":
|
|
fname = "cei/"+file.filename
|
|
file.save(fname)
|
|
original = open_cei(fname, True)[0]
|
|
preview = "static/"+file.filename+".preview.jpg"
|
|
original.convert("RGB").save(preview, quality = 90, optimize = True, progressive = True)
|
|
thumbnail = "static/"+file.filename+".thumbnail.jpg"
|
|
th = original.copy().convert("RGB")
|
|
th.thumbnail((200,200))
|
|
th.save(thumbnail, quality = 75, optimize = True, progressive = True)
|
|
elif ext in supported_videos:
|
|
file.save(fname)
|
|
preview = fname
|
|
th = video_thumbnail(fname)
|
|
thumbnail = fname+".thumbnail.jpg"
|
|
th.save(thumbnail, quality = 75, optimize = True, progressive = True)
|
|
|
|
return fname, preview, thumbnail
|
|
|
|
def ip_name(ip):
|
|
originals = list(".1234567890")
|
|
modified = list("qwertyuiopa")
|
|
name = ""
|
|
for character in ip:
|
|
name += modified[originals.index(character)]
|
|
return str(abs(hash(name)))
|
|
|
|
def merge(dict1, dict2):
|
|
dict3 = dict1.copy()
|
|
dict3.update(dict2)
|
|
return dict3
|
|
|
|
def tag_correction(html):
|
|
htmlist = html.split("<!-- mod -->")
|
|
for n in range(len(htmlist)):
|
|
element = htmlist[n]
|
|
important = element.split("<!-- endmod -->")[0]
|
|
if "<video" in important and not (".mp4" in important or ".webm" in important):
|
|
element = element.replace("<video","<img",1).replace("</video>","",1)
|
|
htmlist[n] = element
|
|
elif "<img" in important and (".mp4" in important or ".webm" in important):
|
|
element = element.replace("<img","<video",1)
|
|
htmlist[n] = element
|
|
print("corrected")
|
|
return "<!-- mod -->".join(htmlist)
|
|
|
|
"""
|
|
- alias: 8e3qe31u4up91451
|
|
date: 08/07/2022 10:44:44
|
|
id: '914'
|
|
image: 4730.png
|
|
name: Anonymous
|
|
text: <3
|
|
"""
|
|
|
|
if __name__ == "__main__":
|
|
app.run(threaded=True)#http://127.0.0.1:5000/ran/1
|