484 lines
16 KiB
Python
484 lines
16 KiB
Python
from bottle import request, redirect, static_file, response, abort
|
|
from pdf2image import convert_from_bytes
|
|
from hashlib import md5
|
|
import datetime
|
|
import random
|
|
import uuid
|
|
import os
|
|
import io
|
|
|
|
from templates import index_page, board_page, thread_page, user_page ,error_page
|
|
from templates import user_content, user_form, default_content
|
|
from templates import collection_page, random_page
|
|
from templates import signin_page, signup_page
|
|
from templates import account, signout_link
|
|
|
|
from config import secret, default_user, limit, invalid_nicknames
|
|
from config import root, banner_dir
|
|
from config import app
|
|
|
|
from util import render, setup, header, motd, get_login, check_user
|
|
from util import get_boards, get_posts, get_random, get_nav, get_files
|
|
from util import get_description, get_style, get_title, get_user
|
|
|
|
|
|
# INDEX
|
|
@app.route("/", "GET")
|
|
def index(db):
|
|
setup(db)
|
|
user = get_user(db)
|
|
boards=get_boards(db)
|
|
|
|
format = {}
|
|
format['title'] = "Hidden Boards"
|
|
format['style'] = get_style()
|
|
format['header'] = header(db)
|
|
format['login'] = get_login(user)
|
|
format['boards'] = boards
|
|
format['motd'] = motd()
|
|
return index_page.render(**format)
|
|
|
|
# LIBRARY
|
|
@app.route("/.library/page/<page:int>/", "GET")
|
|
@app.route("/.library/page/<page:int>", "GET")
|
|
@app.route("/.library/", "GET")
|
|
@app.route("/.library", "GET")
|
|
def library_index(db,page=1):
|
|
setup(db)
|
|
user = get_user(db)
|
|
board = "library"
|
|
t = ["application/pdf"]
|
|
files = get_files(db, t,page=page)
|
|
if not files and page != 1:
|
|
abort(404)
|
|
format = {}
|
|
format['title'] = "LIBRARY"
|
|
format['style'] = get_style()
|
|
format['header'] = header(db)
|
|
format['description'] = get_description(db,board)
|
|
format['user'] = user or default_user
|
|
format['login'] = get_login(user)
|
|
format['board'] = board
|
|
format['posts'] = files
|
|
format['nav'] = get_nav(db,board,t=t)
|
|
return collection_page.render(**format)
|
|
|
|
# LIBRARY POST
|
|
@app.route("/.library/page/<page:int>/", "POST")
|
|
@app.route("/.library/page/<page:int>", "POST")
|
|
@app.route("/.library/", "POST")
|
|
@app.route("/.library", "POST")
|
|
def library_post(db):
|
|
abort(406)
|
|
|
|
# GALLERY
|
|
@app.route("/.gallery/page/<page:int>/", "GET")
|
|
@app.route("/.gallery/page/<page:int>", "GET")
|
|
@app.route("/.gallery/", "GET")
|
|
@app.route("/.gallery", "GET")
|
|
def gallery_get(db,page=1):
|
|
setup(db)
|
|
user = get_user(db)
|
|
board = "gallery"
|
|
t = ["image/gif","image/png","image/jpeg"]
|
|
files = get_files(db, t,page=page)
|
|
if not files and page != 1:
|
|
abort(404)
|
|
format = {}
|
|
format['title'] = "GALLERY"
|
|
format['style'] = get_style()
|
|
format['header'] = header(db)
|
|
format['description'] = get_description(db,board)
|
|
format['user'] = user or default_user
|
|
format['login'] = get_login(user)
|
|
format['board'] = board
|
|
format['posts'] = files
|
|
format['nav'] = get_nav(db,board,t=t)
|
|
return collection_page.render(**format)
|
|
|
|
# GALLERY POST
|
|
@app.route("/.gallery/page/<page:int>/", "POST")
|
|
@app.route("/.gallery/page/<page:int>", "POST")
|
|
@app.route("/.gallery/", "POST")
|
|
@app.route("/.gallery", "POST")
|
|
def gallery_post(db):
|
|
abort(406)
|
|
|
|
# RANDOM
|
|
@app.route("/.random/page/<page:int>/", "POST")
|
|
@app.route("/.random/page/<page:int>", "POST")
|
|
@app.route("/.random/", "POST" )
|
|
@app.route("/.random", "POST")
|
|
def random_post(page=None):
|
|
abort(406)
|
|
|
|
@app.route("/.random/page/<page:int>/", "GET")
|
|
@app.route("/.random/page/<page:int>", "GET")
|
|
def random_pages(page):
|
|
abort(404)
|
|
|
|
|
|
@app.route("/.random/", "GET" )
|
|
@app.route("/.random", "GET")
|
|
def random_get(db):
|
|
board="random"
|
|
setup(db,board)
|
|
user = get_user(db)
|
|
posts = get_random(db)
|
|
format = {}
|
|
format['title'] = board.upper()
|
|
format['style'] = get_style()
|
|
format['header'] = header(db)
|
|
format['description'] = get_description(db,board)
|
|
format['user'] = user or default_user
|
|
format['login'] = get_login(user)
|
|
format['board'] = board
|
|
format['posts'] = posts
|
|
return random_page.render(**format)
|
|
|
|
|
|
# BOARD
|
|
@app.route("/.<board:re:[a-z-]+>/page/<page:int>/", "GET")
|
|
@app.route("/.<board:re:[a-z-]+>/page/<page:int>", "GET")
|
|
@app.route("/.<board:re:[a-z-]+>/", "GET")
|
|
@app.route("/.<board:re:[a-z-]+>", "GET")
|
|
def board_index(db,board,page=1):
|
|
setup(db,board)
|
|
user = get_user(db)
|
|
posts = get_posts(db,board,page)
|
|
if page != 1 and not posts:
|
|
abort(404)
|
|
format = {}
|
|
format['title'] = board.upper()
|
|
format['style'] = get_style()
|
|
format['header'] = header(db)
|
|
format['description'] = get_description(db,board)
|
|
format['user'] = user or default_user
|
|
format['login'] = get_login(user)
|
|
format['board'] = board
|
|
format['posts'] = posts
|
|
format['nav'] = get_nav(db,board)
|
|
return board_page.render(**format)
|
|
|
|
@app.route("/.<board:re:[a-z]+>/page/<page:int>/", "POST")
|
|
@app.route("/.<board:re:[a-z]+>/page/<page:int>", "POST")
|
|
@app.route("/.<board:re:[a-z]+>/", "POST")
|
|
@app.route("/.<board:re:[a-z]+>", "POST")
|
|
def post_board(db,board,page=None):
|
|
setup(db,board)
|
|
user = get_user(db)
|
|
content = request.forms.get("content") or ""
|
|
title = request.forms.get("title")
|
|
author = user or default_user
|
|
files = request.files.getall("files")
|
|
time = datetime.datetime.now() + datetime.timedelta(hours=3)
|
|
time = time.timestamp()
|
|
c = db.execute(f"INSERT INTO POSTS (SUBJECT,AUTHOR,COMMENT,TIME,LAST,BOARD) values (?,?,?,?,?,?);", [title,author,content,time,time,board])
|
|
db.execute("UPDATE BOARDS SET LAST = ? WHERE NAME = ?;",[time, board])
|
|
db.execute(f"UPDATE POSTS SET POST = {c.lastrowid} WHERE ID = {c.lastrowid};")
|
|
if db.execute("SELECT HIDDEN FROM BOARDS WHERE NAME = ?;",[board]).fetchone()[0]:
|
|
hide = 1
|
|
else:
|
|
hide = 0
|
|
for file in files:
|
|
ext = "." + file.filename.split(".")[-1]
|
|
if ext == "."+file.filename:
|
|
ext = ""
|
|
f = file.file.read()
|
|
fname = f"{md5(f).hexdigest()}{ext}"
|
|
|
|
db.execute("INSERT INTO FILES (NAME,POST,TYPE,FILE,HIDDEN) VALUES (?,?,?,?,?);",[fname,c.lastrowid,file.content_type,f,hide])
|
|
redirect(f"/.{board}/thread/{c.lastrowid}")
|
|
|
|
# THREAD
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>/page/<page:int>/", "GET")
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>/page/<page:int>", "GET")
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>/", "GET")
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>", "GET")
|
|
def thread_get(db,board,id,page=1):
|
|
setup(db,board)
|
|
user = get_user(db)
|
|
posts = get_posts(db,id=id,page=page)
|
|
if not posts:
|
|
abort(404)
|
|
format = {}
|
|
format['login'] = get_login(user)
|
|
format['title'] = get_title(db,id)
|
|
format['style'] = get_style()
|
|
format['header'] = header(db)
|
|
format['description'] = get_description(db,board)
|
|
format['user'] = user or default_user
|
|
format['id'] = id
|
|
format['board'] = board
|
|
format['posts'] = posts
|
|
format['nav'] = get_nav(db,board,id=id)
|
|
return thread_page.render(**format)
|
|
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>/page/<page:int>/", "POST")
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>/page/<page:int>", "POST")
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>/", "POST")
|
|
@app.route("/.<board:re:[a-z]+>/thread/<id:int>", "POST")
|
|
def thread_page_post(db,board,id,page=1):
|
|
setup(db,board)
|
|
user = get_user(db)
|
|
author = user or default_user
|
|
content = request.forms.get("content") or ""
|
|
files = request.files.getall("files")
|
|
time = datetime.datetime.now() + datetime.timedelta(hours=3)
|
|
time = time.timestamp()
|
|
c = db.execute(f"INSERT INTO POSTS (AUTHOR,COMMENT,TIME,BOARD,POST) values (?,?,?,?,?);", [author,content,time,board,id])
|
|
db.execute("UPDATE BOARDS SET LAST = ? WHERE NAME = ?;",[time, board])
|
|
db.execute("UPDATE POSTS SET LAST = ? WHERE ID = ?;",[time,id])
|
|
if db.execute("SELECT HIDDEN FROM BOARDS WHERE NAME = ?;",[board]).fetchone()[0]:
|
|
hide = 1
|
|
else:
|
|
hide = 0
|
|
for file in files:
|
|
ext = "." + file.filename.split(".")[-1]
|
|
if ext == "."+file.filename:
|
|
ext = ""
|
|
f = file.file.read()
|
|
fname = f"{md5(f).hexdigest()}{ext}"
|
|
db.execute("INSERT INTO FILES (NAME,POST,TYPE,FILE,HIDDEN) VALUES (?,?,?,?,?);",[fname,c.lastrowid,file.content_type,f,hide])
|
|
count = db.execute("SELECT COUNT(*) FROM POSTS WHERE POST = ?",[id]).fetchone()[0]
|
|
if count < limit:
|
|
redirect(f"/.{board}/thread/{id}#{c.lastrowid}")
|
|
else:
|
|
m = (count % limit)
|
|
p = (count - m) / limit
|
|
p = int(p) + (1 if m != 0 else 0)
|
|
redirect(f"/.{board}/thread/{id}/page/{p}#{c.lastrowid}")
|
|
|
|
# USER
|
|
@app.route("/u/<username:re:[a-zA-Z][a-zA-Z0-9-_\.]{1,23}>/", "GET")
|
|
@app.route("/u/<username:re:[a-zA-Z][a-zA-Z0-9-_\.]{1,23}>", "GET")
|
|
def user_index(db,username):
|
|
setup(db)
|
|
user = get_user(db)
|
|
u = db.execute("SELECT * FROM USERS WHERE USERNAME LIKE ?;",[username]).fetchone()
|
|
if not u:
|
|
abort(404)
|
|
page = render(u['PAGE'] or "")
|
|
owner = user == u["USERNAME"]
|
|
format = {}
|
|
if owner:
|
|
format['login'] = signout_link
|
|
else:
|
|
format['login'] = get_login(user)
|
|
format['header'] = header(db)
|
|
format['page'] = user_content.render(page=page or default_content)
|
|
if owner:
|
|
format['page'] += user_form.render(page=u["PAGE"] or "")
|
|
format['style'] = get_style()
|
|
format['user'] = user or default_user
|
|
format['title'] = u["USERNAME"]
|
|
return user_page.render(**format)
|
|
|
|
@app.route("/u/<username:re:[a-zA-Z][a-zA-Z0-9-_\.]{1,23}>/", "POST")
|
|
@app.route("/u/<username:re:[a-zA-Z][a-zA-Z0-9-_\.]{1,23}>", "POST")
|
|
def user_index(db,username):
|
|
setup(db)
|
|
user = get_user(db)
|
|
u = db.execute("SELECT * FROM USERS WHERE USERNAME LIKE ?;",[username]).fetchone()
|
|
if not u:
|
|
abort(404)
|
|
owner = user == u["USERNAME"]
|
|
if not owner:
|
|
abort(401)
|
|
content = request.forms.getunicode("content")
|
|
db.execute("UPDATE USERS SET PAGE = ? WHERE USERNAME LIKE ?;", [content, user])
|
|
redirect(f"/u/{user}")
|
|
|
|
|
|
@app.route("/u/Anonymous/")
|
|
@app.route("/u/Anonymous")
|
|
def anonymous():
|
|
abort(406)
|
|
|
|
# LOGIN
|
|
|
|
@app.route("/login", "GET")
|
|
def login_get(db):
|
|
setup(db)
|
|
user = get_user(db)
|
|
format = {}
|
|
format['nickname'] = ""
|
|
format['header'] = header(db)
|
|
format['style'] = get_style()
|
|
format['user'] = user or default_user
|
|
format['error'] = ""
|
|
return signin_page.render(**format)
|
|
|
|
@app.route("/login", "POST")
|
|
def login_post(db):
|
|
setup(db)
|
|
user = get_user(db)
|
|
nickname = request.forms.getunicode("user")
|
|
password = request.forms.getunicode("password")
|
|
|
|
if not check_user.match(nickname):
|
|
format = {}
|
|
format['nickname'] = nickname
|
|
format['header'] = header(db)
|
|
format['style'] = get_style()
|
|
format['user'] = user or default_user
|
|
format['error'] = "Invalid Nickname!"
|
|
return signin_page.render(**format)
|
|
|
|
u = db.execute("SELECT * FROM USERS WHERE USERNAME LIKE ?;",[nickname]).fetchone()
|
|
|
|
if not u:
|
|
format = {}
|
|
format['nickname'] = nickname
|
|
format['header'] = header(db)
|
|
format['style'] = get_style()
|
|
format['user'] = user or default_user
|
|
format['error'] = "Nickname doesn't exists!"
|
|
return signin_page.render(**format)
|
|
|
|
#time = datetime.datetime.fromtimestamp(u['time'])
|
|
hash = md5((password+str(u['TIME'])).encode()).hexdigest()
|
|
if hash == u['PASSWORD']:
|
|
session_id = str(uuid.uuid4())
|
|
time = datetime.datetime.now() + datetime.timedelta(hours=3)
|
|
response.set_cookie("session_id", session_id, secret=secret)
|
|
db.execute("INSERT INTO SESSIONS(USERNAME, TIME, KEY) VALUES (?,?,?) ON CONFLICT (USERNAME) DO UPDATE SET TIME = ?, KEY = ? ;",[nickname, time.timestamp(), session_id,time.timestamp(), session_id])
|
|
redirect(f"/u/{nickname}")
|
|
else:
|
|
format = {}
|
|
format['nickname'] = nickname
|
|
format['header'] = header(db)
|
|
format['style'] = get_style()
|
|
format['user'] = user or default_user
|
|
format['error'] = "Wrong password!"
|
|
return signin_page.render(**format)
|
|
|
|
# LOGOUT
|
|
|
|
@app.route("/logout", "GET")
|
|
def logout_get(db):
|
|
setup(db)
|
|
user = get_user(db)
|
|
if not user:
|
|
abort(401)
|
|
db.execute("DELETE FROM SESSIONS WHERE USERNAME = ?",[user])
|
|
redirect("/")
|
|
|
|
|
|
# SIGNUP
|
|
|
|
@app.route("/signup", "GET")
|
|
def signup(db):
|
|
setup(db)
|
|
user = get_user(db)
|
|
|
|
format = {}
|
|
format['nickname'] = ""
|
|
format['header'] = header(db)
|
|
format['style'] = get_style()
|
|
format['user'] = user or default_user
|
|
format['error'] = ""
|
|
return signup_page.render(**format)
|
|
|
|
@app.route("/signup", "POST")
|
|
def signup(db):
|
|
setup(db)
|
|
user = get_user(db)
|
|
|
|
nickname = request.forms.getunicode("user")
|
|
password = request.forms.getunicode("password")
|
|
confirmation = request.forms.getunicode("confirmation")
|
|
|
|
format = {}
|
|
format['nickname'] = nickname
|
|
format['header'] = header(db)
|
|
format['style'] = get_style()
|
|
format['user'] = user or default_user
|
|
|
|
if not check_user.match(nickname):
|
|
format['error'] = "Invalid Nickname!"
|
|
return signup_page.render(**format)
|
|
|
|
if db.execute("SELECT * FROM USERS WHERE USERNAME LIKE ?;",[nickname]).fetchone() or nickname.lower() in invalid_nicknames:
|
|
format['error'] = "Nickname already in use!"
|
|
return signup_page.render(**format)
|
|
|
|
if password != confirmation:
|
|
format['error'] = "Passwords don't match!"
|
|
return signup_page.render(**format)
|
|
|
|
time = datetime.datetime.now() + datetime.timedelta(hours=3)
|
|
hash = md5((password+str(time.timestamp())).encode()).hexdigest()
|
|
db.execute("INSERT INTO USERS (USERNAME, TIME, PASSWORD) VALUES (?,?,?);",[nickname,time.timestamp(),hash])
|
|
session_id = str(uuid.uuid4())
|
|
response.set_cookie("session_id", session_id, secret=secret)
|
|
db.execute("INSERT INTO SESSIONS(USERNAME, TIME, KEY) VALUES (?,?,?) ON CONFLICT (USERNAME) DO UPDATE SET TIME = ?, KEY = ? ;",[nickname, time.timestamp(), session_id,time.timestamp(), session_id])
|
|
redirect(f"/u/{nickname}")
|
|
|
|
# ERROR
|
|
|
|
@app.error(401)
|
|
@app.error(404)
|
|
@app.error(405)
|
|
@app.error(406)
|
|
@app.error(500)
|
|
def error_redirect(error):
|
|
return error_page.render(style=get_style(),error=error.status_code,header=motd())
|
|
|
|
|
|
# FILES
|
|
|
|
@app.route("/files/<filename>/")
|
|
@app.route("/files/<filename>")
|
|
def files(db,filename):
|
|
file = db.execute("SELECT * FROM FILES WHERE NAME = ?;",[filename]).fetchone()
|
|
if not file:
|
|
abort(404)
|
|
response.set_header("content-type", file["TYPE"])
|
|
return file["FILE"]
|
|
|
|
# THUMBS
|
|
|
|
@app.route("/thumbs/<filename>/")
|
|
@app.route("/thumbs/<filename>")
|
|
def thumbs(db,filename):
|
|
file = db.execute("SELECT * FROM THUMBS WHERE NAME = ?;",[filename]).fetchone()
|
|
if file:
|
|
response.set_header("content-type", file["TYPE"])
|
|
return file["FILE"]
|
|
file = db.execute("SELECT * FROM FILES WHERE NAME = ?;",[filename]).fetchone()
|
|
if not file:
|
|
abort(404)
|
|
if file["TYPE"] == "application/pdf":
|
|
image = convert_from_bytes(file["FILE"], first_page=1,last_page=1,fmt="jpg")
|
|
image = image[0]
|
|
file = io.BytesIO()
|
|
response.set_header("content-type", "image/jpeg")
|
|
image.save(file,format='JPEG')
|
|
db.execute("INSERT INTO THUMBS (NAME,TYPE,FILE) VALUES (?,?,?);",[filename,"image/jpeg",file.getvalue()])
|
|
return file.getvalue()
|
|
else:
|
|
return static_file("thumbs/file.png",root=root)
|
|
|
|
# STATIC
|
|
|
|
# BANNER
|
|
@app.route("/s/banner/", "GET")
|
|
@app.route("/s/banner", "GET")
|
|
def banner():
|
|
b = random.choice(os.listdir(banner_dir))
|
|
response = static_file(b,root=banner_dir)
|
|
response.set_header('cache-control','no-cache, no-store, must-revalidate')
|
|
return response
|
|
|
|
# ICON
|
|
@app.route("/favicon.ico", "GET")
|
|
def favicon():
|
|
return static_file("static/favicon.ico",root=root)
|
|
|
|
# FONT
|
|
@app.route("/s/font/", "GET")
|
|
@app.route("/s/font", "GET")
|
|
def font():
|
|
return static_file('static/ForcedSquare.woff',root=root)
|