boards/routes.py

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)