207 lines
9.0 KiB
Python
207 lines
9.0 KiB
Python
from bottle import request
|
|
from skcode.tags.internal import HardNewlineTreeNode as NewLineTreeNode
|
|
from cheroot.ssl.builtin import BuiltinSSLAdapter
|
|
from skcode import parse_skcode, render_to_html
|
|
from bottle import ServerAdapter
|
|
from cheroot import wsgi
|
|
import datetime
|
|
import ssl
|
|
import os
|
|
import re
|
|
|
|
from config import boards, default_user, secret, limit
|
|
from config import root, cert_path, key_path
|
|
|
|
from templates import board_file, thread_file, board_box, signin_link, account
|
|
from templates import board_post, thread_post, random_post
|
|
|
|
# BBCODE PARSER
|
|
def render(text):
|
|
return render_to_html(parse_skcode(text,newline_node_cls=NewLineTreeNode))
|
|
|
|
# DEFINITIONS
|
|
def setup(db, board=None):
|
|
db.execute("CREATE TABLE IF NOT EXISTS BOARDS (ID INTEGER PRIMARY KEY,NAME TEXT UNIQUE, DESCRIPTION TEXT,LAST REAL,HIDDEN INTEGER, FIXED INTEGER DEFAULT 0 );")
|
|
db.execute(f"CREATE TABLE IF NOT EXISTS POSTS (ID INTEGER PRIMARY KEY, SUBJECT TEXT, AUTHOR TEXT, COMMENT TEXT, TIME REAL,LAST REAL , BOARD TEXT, POST INTEGER );")
|
|
db.execute(f"CREATE TABLE IF NOT EXISTS USERS (ID INTEGER PRIMARY KEY, USERNAME TEXT UNIQUE, TIME REAL, PASSWORD TEXT, PAGE TEXT );")
|
|
db.execute(f"CREATE TABLE IF NOT EXISTS SESSIONS (USERNAME TEXT UNIQUE, TIME REAL, KEY TEXT);")
|
|
db.execute(f"CREATE TABLE IF NOT EXISTS FILES (ID INTEGER PRIMARY KEY, NAME TEXT,POST INTEGER, TYPE TEXT , FILE BLOB, HIDDEN INTEGER);")
|
|
db.execute(f"CREATE TABLE IF NOT EXISTS THUMBS (ID INTEGER PRIMARY KEY, NAME TEXT UNIQUE, TYPE TEXT, FILE BLOB);")
|
|
for b,d in boards.items():
|
|
hide = 0
|
|
if "nsfw" in b or "nsfw" in d.lower() :
|
|
hide = 1
|
|
db.execute("INSERT INTO BOARDS (NAME,DESCRIPTION,HIDDEN,FIXED) VALUES (?,?,?,?) ON CONFLICT(NAME) DO UPDATE SET DESCRIPTION = ?;",[b,d,hide,1,d])
|
|
if board:
|
|
hide = 0
|
|
if "nsfw" in board:
|
|
hide = 1
|
|
db.execute("INSERT OR IGNORE INTO BOARDS (NAME,HIDDEN) VALUES (?,?);",[board,hide])
|
|
|
|
def get_user(db):
|
|
session_id = request.get_cookie("session_id", secret=secret)
|
|
if session_id:
|
|
row = db.execute("SELECT * FROM SESSIONS WHERE KEY = ? ;",[session_id]).fetchone()
|
|
return row['USERNAME'] if row else None
|
|
|
|
def get_description(db, board):
|
|
return db.execute(f"SELECT DESCRIPTION FROM BOARDS WHERE NAME = ?;",[board]).fetchone()['description'] or ""
|
|
|
|
def get_title(db,id):
|
|
return db.execute(f"SELECT * FROM POSTS WHERE ID = ?;",[id]).fetchone()['SUBJECT']
|
|
|
|
def user_link(user):
|
|
if user == default_user:
|
|
return f'<b>Anonymous</b>'
|
|
return f'<a href="/u/{user}"><b>{user}</b></a>'
|
|
|
|
def ddate():
|
|
return os.popen('ddate').read()
|
|
|
|
def header(db, user=None):
|
|
b = []
|
|
for board in db.execute("SELECT * FROM BOARDS WHERE FIXED = 1 AND HIDDEN != 1;"):
|
|
b.append(f"<a style='text-align:center;' href='/.{board['name']}'>{board['name'].upper()} </a>")
|
|
for board in db.execute("SELECT * FROM BOARDS WHERE (lAST IS NOT NULL AND FIXED = 0) AND HIDDEN != 1 ORDER BY LAST DESC LIMIT 4;"):
|
|
b.append(f"<a style='text-align:center;' href='/.{board['name']}'>{board['name'].upper()} </a>")
|
|
return " | ".join(b)
|
|
|
|
def motd():
|
|
return f"No greyfaces allowed! {ddate()}"
|
|
|
|
def generate_file(file,board=None,checked=False):
|
|
t = file["TYPE"].split("/")
|
|
fname = file["NAME"]
|
|
if t[0] == "image":
|
|
src = f'/files/{fname}'
|
|
else:
|
|
src = f'/thumbs/{fname}'
|
|
if board:
|
|
return board_file.render(board=board,src=src)
|
|
else:
|
|
name = file["NAME"]
|
|
return thread_file.render(name=name,src=src,checked=checked)
|
|
|
|
def get_login(user):
|
|
return account.render(user=user) if user else signin_link
|
|
|
|
def get_boards(db):
|
|
brds = ""
|
|
for b in db.execute("SELECT * FROM BOARDS WHERE LAST IS NOT NULL AND HIDDEN != 1;"):
|
|
format = {}
|
|
board = b["NAME"]
|
|
format['board'] = board
|
|
format['threads'] = db.execute("SELECT COUNT(*) FROM POSTS WHERE BOARD = ? AND (ID = POST);",[board]).fetchone()[0]
|
|
format['description'] = b["DESCRIPTION"] or ""
|
|
brds += board_box.render(**format)
|
|
return brds
|
|
|
|
def get_files(db,t,page=1):
|
|
page = page-1
|
|
files = ""
|
|
tlist = ", ".join(f"'{n}'" for n in t)
|
|
for file in db.execute(f"""SELECT DISTINCT NAME, TYPE FROM FILES WHERE TYPE IN ({tlist}) AND HIDDEN != 1 AND NAME NOT IN ( SELECT DISTINCT NAME FROM FILES WHERE TYPE IN ({tlist}) AND HIDDEN != 1 ORDER BY ID DESC LIMIT ?) ORDER BY ID DESC LIMIT ?;""",[limit*1.5*page,limit*1.5]):
|
|
files+=generate_file(file)
|
|
return files
|
|
|
|
|
|
def get_nav(db, board, id=None, page=1,t=[]):
|
|
if id:
|
|
count = db.execute("SELECT COUNT(*) FROM POSTS WHERE POST = ?",[id]).fetchone()[0]
|
|
pages = []
|
|
m = (count % limit)
|
|
p = (count - m) / limit
|
|
p = int(p) + (1 if m != 0 else 0)
|
|
for i in range(1,p+1):
|
|
pages.append(f'<a href="/.{board}/thread/{id}/page/{i}">[{i}]</a>')
|
|
return f'<div class="nav">{" | ".join(pages)}</div>'
|
|
else:
|
|
if board == "gallery":
|
|
count = db.execute(f"""SELECT COUNT(DISTINCT NAME) FROM FILES WHERE TYPE IN ({", ".join(f"'{n}'" for n in t)});""").fetchone()[0]
|
|
m = (count % ((limit * 1.5)))
|
|
p = (count - m) / (limit * 1.5)
|
|
# p = int(p) + (1 if m != 0 else 0)
|
|
elif board == "library":
|
|
count = db.execute(f"""SELECT COUNT(DISTINCT NAME) FROM FILES WHERE TYPE IN ({", ".join(f"'{n}'" for n in t)});""").fetchone()[0]
|
|
m = (count % (limit * 1.5))
|
|
p = (count - m) / (limit * 1.5)
|
|
# p = int(p) + (1 if m != 0 else 0)
|
|
else:
|
|
count = db.execute("SELECT COUNT(*) FROM POSTS WHERE BOARD = ? AND POST = ID;",[board]).fetchone()[0]
|
|
m = (count % limit)
|
|
p = (count - m) / limit
|
|
# p = int(p) + (1 if m != 0 else 0)
|
|
pages = []
|
|
|
|
p = int(p) + (1 if m != 0 else 0)
|
|
for i in range(1,p+1):
|
|
pages.append(f'<a href="/.{board}/page/{i}">[{i}]</a>')
|
|
return f'<div class="nav">{" | ".join(pages)}</div>'
|
|
|
|
|
|
def get_random(db):
|
|
posts = ""
|
|
for post in db.execute(f"SELECT * FROM POSTS WHERE ID = POST AND BOARD NOT IN ( SELECT NAME FROM BOARDS WHERE HIDDEN = 1) ORDER BY RANDOM() LIMIT ?;",[limit]):
|
|
dt = datetime.datetime.fromtimestamp(post['time'])
|
|
date = dt.strftime('%d %m %Y')
|
|
time = dt.strftime("%H:%M:%S")
|
|
sddate = os.popen(f"ddate +'%d/%b/%Y' {date}").read()
|
|
files = ""
|
|
for file in db.execute("SELECT * FROM FILES WHERE POST = ? LIMIT 1;",[post["ID"]]):
|
|
files+=generate_file(file,board=post["BOARD"])
|
|
posts += random_post.render(id=post["ID"],board=post["BOARD"],subject=post["SUBJECT"],author=user_link(post["AUTHOR"]),date=sddate,time=time,files=files)
|
|
|
|
return posts
|
|
|
|
def get_posts(db,board=None,page=1,id=None):
|
|
page = page-1
|
|
posts = ""
|
|
if id:
|
|
for post in db.execute(f"SELECT * FROM POSTS WHERE POST = ? AND ID NOT IN ( SELECT ID FROM POSTS WHERE POST = ? LIMIT ?) LIMIT ?;",[id,id,limit * page,limit]):
|
|
dt = datetime.datetime.fromtimestamp(post['time'])
|
|
date = dt.strftime('%d %m %y')
|
|
time = dt.strftime("%H:%M:%S")
|
|
sddate = os.popen(f"ddate +'%d/%b/%Y YOLD' {date}").read()
|
|
files = ""
|
|
first = True
|
|
for file in db.execute("SELECT * FROM FILES WHERE POST = ?;",[post["ID"]]):
|
|
files+=generate_file(file,checked=first)
|
|
if first:
|
|
first = False
|
|
comment = render(post['COMMENT'] or "")
|
|
posts += thread_post.render(id=post["ID"],author=user_link(post["AUTHOR"]),sddate=sddate,time=time,board=board,post=post["POST"],files=files,comment=comment)
|
|
|
|
elif board:
|
|
for post in db.execute("""
|
|
SELECT * FROM POSTS
|
|
WHERE (ID = POST AND BOARD = ?)
|
|
AND
|
|
ID NOT IN ( SELECT ID FROM POSTS WHERE ID = POST AND BOARD = ? ORDER BY LAST DESC LIMIT ? )
|
|
ORDER BY LAST DESC LIMIT ?;
|
|
""", [board, board, limit * page, limit]):
|
|
dt = datetime.datetime.fromtimestamp(post['time'])
|
|
date = dt.strftime('%d %m %Y')
|
|
time = dt.strftime("%H:%M:%S")
|
|
sddate = os.popen(f"ddate +'%d/%b/%Y' {date}").read()
|
|
files = ""
|
|
for file in db.execute("SELECT * FROM FILES WHERE POST = ? LIMIT 1;",[post["ID"]]):
|
|
files+=generate_file(file,board=board)
|
|
posts += board_post.render(id=post["ID"],board=post["BOARD"],subject=post["SUBJECT"],author=user_link(post["AUTHOR"]),date=sddate,time=time,files=files)
|
|
return posts
|
|
|
|
def get_style():
|
|
return open(f"{root}/css/style.css").read()
|
|
|
|
check_user = re.compile("^[a-zA-Z][a-zA-Z0-9-_\.]{1,23}$")
|
|
|
|
class SSLServer(ServerAdapter):
|
|
def run(self, handler):
|
|
server = wsgi.Server((self.host, self.port), handler)
|
|
server.ssl_adapter = BuiltinSSLAdapter(cert_path, key_path)
|
|
server.ssl_adapter.context.options |= ssl.OP_NO_TLSv1
|
|
server.ssl_adapter.context.options |= ssl.OP_NO_TLSv1_1
|
|
try:
|
|
server.start()
|
|
finally:
|
|
server.stop()
|