master
Kyle Czar 2020-07-20 00:48:53 -03:00
commit 51d83fcd23
45 changed files with 218105 additions and 0 deletions

BIN
banners/alright.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

BIN
banners/apple.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
banners/can.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

BIN
banners/cow.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

BIN
banners/exodia.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
banners/fury.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

BIN
banners/guys.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

BIN
banners/hack.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

BIN
banners/king.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

BIN
banners/lolita.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
banners/matrix.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
banners/one.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
banners/pd.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
banners/pill.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

BIN
banners/pokemon.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
banners/pope.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

BIN
banners/robot.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 KiB

BIN
banners/society.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

BIN
banners/your.gif 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

33
config.py 100644
View File

@ -0,0 +1,33 @@
from bottle import Bottle
from bottle.ext import sqlite
import os
root = os.path.dirname(os.path.realpath(__file__))
default_user = "Anonymous"
invalid_nicknames = [default_user.lower(), "admin"]
secret = "fnord"
limit = 10
boards = {
"technology":"My printer don't work can you help me?",
"programming":"Hello World!",
"games":"Wait A Second Dingus",
"media":"General Media Sharing",
"library":"Hidden Boards Book Collection",
"gallery":"Hidden Boards Visual Media Collection",
"random":f"{limit} Random Threads",
"nsfw":"Nylon Sheets Feel Weird",
"requests":"Sugestions and Complaints"
}
host = '0.0.0.0'
port = 8080
db_path = f"{root}/.hidden/hiddenboards.db"
cert_path = f"{root}/.hidden/cert.pem"
key_path = f"{root}/.hidden/key.pem"
banner_dir = f"{root}/banners"
app = Bottle()
app.install(sqlite.Plugin(dbfile=db_path))

315
css/style.css 100644
View File

@ -0,0 +1,315 @@
@font-face {
font-family:hiddenfont;
src:url(/s/font) format('woff');
font-weight:lighter;
font-style:normal;
font-size: x-large;
}
* {
border-radius: 2px;
}
.comment{
margin: 10px;
}
.thread h1{
font-size: 20px;
}
header {
border-radius: 0;
}
.thread img {
max-width:100%;
}
.nav{
float:left;
/* background:black; */
text-align: center;
padding: 10px;
padding-top:0;
padding-bottom: 0;
margin: 5px;
margin-top: 0;
width: calc(100% - 32px);
}
.block {
padding:10px;
margin:5px;
background:black;
}
.file {
float:left;
box-sizing:border-box;
}
.file img {
/* width: 100%; */
/* max-height:500px; */
}
.files {
width:100%;
float:left;
}
.box{
width:100%;
}
.thumb{
width:33%;
height:270px;
margin-bottom:1px;
margin-top:1px;
margin-right: 1px;
margin-left: 1px;
padding: 5px;
}
.thumb img{
max-height:200px;
width: auto;
}
.thumb a {
line-break:anywhere;
}
.toggle{
position: absolute;
left: -100vw;
}
:checked ~ .thumb{
width:100%;
height: 100%;
max-height:100%;
padding:0;
}
:checked ~ .thumb p{
visibility: hidden;
height:0px;
margin:0px;
}
:checked ~ .file img{
max-width: 100%;
max-height:100%;
}
.codetable pre{
padding-left: 10px;
color:black;
font-family:hiddenfont;
}
.codetable {
width:100%;
height: auto;
overflow:auto;
}
pre {
font-family:hiddenfont;
}
body {
color:#ACA4F6;
font-family:hiddenfont;
margin:0;
margin-top:50px;
background:#0C0C0C;
}
.titlebar h2{
font-family:hiddenfont;
color:#7352E6;
margin-bottom: 0;
margin-top:0;
}
blockquote {
font-family:hiddenfont;
border-left: 2px solid gray;
padding:5px;
}
input,textarea {
font-family:hiddenfont;
width:100%;
box-sizing:border-box;
}
input[type=submit] {
font-family:hiddenfont;
width:100%;
background: #0C0C0C;
}
input[type=submit]:hover {
font-family:hiddenfont;
background: #080808;
color: #FFE620;
}
table {
font-family:hiddenfont;
border:none;
width:600px;
margin:0 auto;
border-radius:5px;
background:#000000;
}
i {
font-family:hiddenfont;
font-style:normal;
margin-left:2.5px;
border-radius:10px;
padding:2px;
}
small {
font-family:hiddenfont;
padding-left:2.5px;
}
form {
color:#ACA4F6;
font-family:hiddenfont;
margin:0 0 30px 0;
}
strong {
font-family:hiddenfont;
}
#replytext {
font-family:hiddenfont;
margin-top:-20px;
font-size:10pt;
margin-bottom:15px;
}
textarea {
color:#ACA4F6;
font-family:hiddenfont;
height:100px;
border: none;
background: #0C0C0C;
}
h1 {
font-family:hiddenfont;
text-align:center;
margin:10px 0;
color:#7352E6;
}
#banner {
font-family:hiddenfont;
display:block;
height:150px;
margin:10px auto;
border:1px solid #FFE620;
}
.thread {
font-family: hiddenfont;
display:inline-block;
margin:15px 3px 3px 3px;
padding:10px;
/* width:calc(100% - 32px); */
max-width:calc(100% - 32px);
float:left;
clear:left;
text-align:left;
font-family:hiddenfont;
margin-top:1px;
width:100%;
margin:5px;
background:black;
}
/* .thread img {
float:left;
max-height:250px;
max-width:250px;
margin-right:10px;
} */
/* .thread div {
width:100%;
word-wrap:break-word;
} */
hr {
font-family:hiddenfont;
border:1px solid #FFE620;
}
header {
font-family: hiddenfont;
top:0;
width:100%;
position:absolute;
text-align:center;
padding:5px 0;
z-index:23;
color:#ACA4F6;
background:#000000;
border-bottom:1px solid #FFE620;
}
footer {
font-family: hiddenfont;
bottom:0;
width:100%;
position:absolute;
text-align:center;
padding:5px 0;
z-index:23;
color:#ACA4F6;
background:#000000;
border-top:1px solid #FFE620;
}
.home-info-box {
font-family:hiddenfont;
width:800px;
text-align:center;
margin:0 auto;
}
input {
color:#ACA4F6;
font-family:hiddenfont;
background: #0C0C0C;
border: none;
}
.spoiler {
padding-left:10px;
font-family:hiddenfont;
background:grey;
color:grey;
}
.spoiler:hover {
color:white;
}
.coincidence {
font-family:hiddenfont;
background:#000263;
color:#00D7FF;
}
b {
font-family:hiddenfont;
color:#7352E6;
}
a {
font-family:hiddenfont;
color:#FFE620;
outline:none;
text-decoration:none;
}

52
html/board.html 100644
View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>{{!title}}</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">{{!login}}</b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<a href="/.{{!board}}"><h1 style="margin-top:20px;text-transform:uppercase;">{{!board}}</h1></a>
<h3 style="margin-bottom:20px;color:#ACA4F6;">{{description}}</h3>
<form method="post" enctype="multipart/form-data" autocomplete="off">
<table>
<tr>
<td>Name:</td>
<td><input type="text" name="name" readonly placeholder="{{!user}}"></td>
</tr>
<tr>
<td>Subject:</td>
<td><input name="title" required /></td>
</tr>
<tr>
<td>Comment:</td>
<td><textarea name="content" required ></textarea></td>
</tr>
<tr>
<td>Files:</td>
<td><input type="file" name="files" multiple ></td>
</tr>
<tr>
<td align="center" colspan="2"><input type="submit"/></td>
</tr>
</table>
</form>
<div id="feed">
{{!posts}}
</div>
{{!nav}}
</div>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>{{!title}}</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">{{!login}}</b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<a href="/.{{!board}}"><h1 style="margin-top:20px;text-transform:uppercase;">{{!board}}</h1></a>
<h3 style="margin-bottom:20px;color:#ACA4F6;">{{!description}}</h3>
<div class="thread">
{{!posts}}
</div>
{{!nav}}
</div>
</body>
</html>

20
html/error.html 100644
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>{{!error}}</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
{{!header}}
</header>
<a href="/"><img id="banner" src="/s/banner"></a>
<div class="home-info-box">
<h1>Error {{!error}}!</h1>
</div>
</body>
</html>

37
html/index.html 100644
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>{{!title}}</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">{{!login}}</b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<h1 style="margin-top:20px;text-transform:uppercase;">{{!title}}</h1>
<h3 style="margin-bottom:20px;color:#ACA4F6;">{{!motd}}</h3>
<div class="block titlebar"><h2>Active Boards</h2></div>
<div id="feed" style="text-align:left;">
{{!boards}}
</div>
<!-- <div class="titlebar">Rules</div>
<div style="text-align:center;margin-left:10px;padding:10px 0;">
1. We don't talk about HIDDEN BOARDS<br/>
2. No spam, no flood and no unrelated ads<br/>
3. No child pornography<br/>
4. No doxxing<br/>
5. Everything should follow the law of fives
</div> -->
</div>
</body>
</html>

28
html/random.html 100644
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>{{!title}}</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">{{!login}}</b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<a href="/.{{!board}}"><h1 style="margin-top:20px;text-transform:uppercase;">{{!board}}</h1></a>
<h3 style="margin-bottom:20px;color:#ACA4F6;">{{description}}</h3>
<div id="feed">
{{!posts}}
</div>
</div>
</body>
</html>

44
html/signin.html 100644
View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>LOGIN</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">New account? <a href="/signup">SIGNUP</a></b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<h1>SIGNIN</h1>
<form method="post" autocomplete="off">
<table>
<tr>
<td>Nickname:</td>
<td><input type="text" name="user" value="{{!nickname}}" pattern="[a-zA-Z][a-zA-Z0-9-_\.]{1,23}" required ></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" required /></td>
</tr>
<tr>
<td align="center" colspan="2"><input type="submit"/></td>
</tr>
</table>
</form>
<p>{{!error}}</p>
</div>
</body>
</html>

45
html/signup.html 100644
View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>SIGNUP</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">Have an account? <a href="/login">LOGIN</a></b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<h1>SIGNUP</h1>
<form method="post" autocomplete="off">
<table>
<tr>
<td>Nickname:</td>
<td><input type="text" name="user" value="{{!nickname}}" pattern="[a-zA-Z][a-zA-Z0-9-_\.]{1,23}" required ></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" required /></td>
</tr>
<tr>
<td>Confirm Password:</td>
<td><input type="password" name="confirmation" required /></td>
</tr>
<tr>
<td align="center" colspan="2"><input type="submit"/></td>
</tr>
</table>
</form>
<p>{{!error}}</p>
</div>
</body>
</html>

51
html/thread.html 100644
View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>{{!title}}</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">{{!login}}</b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<a href="/.{{!board}}"><h1 style="margin-top:20px;text-transform:uppercase;">{{!board}}</h1></a>
<h3 style="margin-bottom:20px;color:#ACA4F6;">{{description}}</h3>
<div class="titlebar" style="background:none;"><h1 style="align:center;"><a href="/.{{!board}}/thread/{{!id}}">{{title}}</a></h1></div>
<div id="feed">
{{!posts}}
</div>
{{!nav}}
<form method="post" enctype="multipart/form-data" autocomplete="off">
<table>
<tr>
<td>Name:</td>
<td><input type="text" name="name" readonly placeholder={{!user}}></td>
</tr>
<tr>
<td>Comment:</td>
<td><textarea name="content" required ></textarea></td>
</tr>
<tr>
<td>Files:</td>
<td><input type="file" name="files" multiple ></td>
</tr>
<tr>
<td align="center" colspan="2"><input type="submit"/></td>
</tr>
</table>
</form>
</div>
</body>
</html>

26
html/user.html 100644
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="robots" content="noindex,nofollow"/>
<title>{{!title}}</title>
<style>
{{!style}}
</style>
</head>
<body>
<header>
<a style="float:left;margin-left:5px;" href="/">HIDDEN BOARDS</a>
<span style="position: absolute;top: 50%;left: 50%;margin-right: -50%;transform: translate(-50%, -50%)">
{{!header}}
</span>
<b style="float:right;margin-right:5px;">{{!login}}</b>
</header>
<div class="home-info-box">
<a href="/"><img id="banner" src="/s/banner"></a>
<a href="/u/{{!user}}"><h1 style="margin-top:20px;">{{!title}}</h1></a>
{{!page}}
</div>
</body>
</html>

216614
index.html 100644

File diff suppressed because it is too large Load Diff

6
main.py 100644
View File

@ -0,0 +1,6 @@
from config import app, host, port
from util import SSLServer
import routes
app.run(host=host, port=port, server=SSLServer)

483
routes.py 100644
View File

@ -0,0 +1,483 @@
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)

Binary file not shown.

BIN
static/favicon.ico 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

116
templates.py 100644
View File

@ -0,0 +1,116 @@
from bottle import SimpleTemplate
from config import root
index_page = SimpleTemplate(open(f"{root}/html/index.html").read())
board_page = SimpleTemplate(open(f"{root}/html/board.html").read())
random_page = SimpleTemplate(open(f"{root}/html/random.html").read())
collection_page = SimpleTemplate(open(f"{root}/html/collection.html").read())
thread_page = SimpleTemplate(open(f"{root}/html/thread.html").read())
user_page = SimpleTemplate(open(f"{root}/html/user.html").read())
signin_page = SimpleTemplate(open(f"{root}/html/signin.html").read())
signup_page = SimpleTemplate(open(f"{root}/html/signup.html").read())
error_page = SimpleTemplate(open(f"{root}/html/error.html").read())
account = SimpleTemplate("""<a href="/u/{{!user}}"><b>{{!user}}</b></a>""")
board_box = SimpleTemplate("""
<div class="block">
<a href="/.{{board}}">{{board.upper()}}</a>
<span style="float:right;">