new backend

This commit is contained in:
Flummi
2021-12-04 12:19:47 +01:00
parent d885dd8e4e
commit 43665884f6
42 changed files with 946 additions and 1226 deletions

View File

@ -1,4 +1,4 @@
import config from "../../config.json";
import config from "./config.mjs";
export const getLevel = user => {
let ret = {

View File

@ -1,4 +1,4 @@
import _config from "../../config.json";
import _config from "../../config.json" assert { type: "json" };
let config = JSON.parse(JSON.stringify(_config));

View File

@ -1,4 +1,8 @@
import crypto from "crypto";
import util from "util";
import sql from "./sql.mjs";
const scrypt = util.promisify(crypto.scrypt);
const epochs = [
["year", 31536000],
@ -21,15 +25,63 @@ const getDuration = timeAgoInSeconds => {
export default new class {
formatSize(size, i = ~~(Math.log(size) / Math.log(1024))) {
return (size / Math.pow(1024, i)).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][i];
}
};
calcSpeed(b, s) {
return (Math.round((b * 8 / s / 1e6) * 1e4) / 1e4);
}
};
timeAgo(date) {
const { interval, epoch } = getDuration(~~((new Date() - new Date(date)) / 1e3));
return `${interval} ${epoch}${interval === 1 ? "" : "s"} ago`;
}
};
md5(str) {
return crypto.createHash('md5').update(str).digest("hex");
}
};
getMode(mode) {
let tmp;
switch(mode) {
case 1: // nsfw
tmp = "id in (select item_id from tags_assign where tag_id = 2 group by item_id)";
break;
case 2: // untagged
tmp = "id not in (select item_id from tags_assign group by item_id)";
break;
case 3: // all
tmp = "";
break;
default: // sfw
tmp = "id in (select item_id from tags_assign where tag_id = 1 group by item_id)";
break;
}
return tmp;
};
createID() {
return crypto.randomBytes(16).toString("hex") + Date.now().toString(24);
};
// async funcs
async countf0cks() {
const tagged = (await sql("items").whereRaw("id in (select item_id from tags_assign group by item_id)").count("* as total"))[0].total;
const untagged = (await sql("items").whereRaw("id not in (select item_id from tags_assign group by item_id)").count("* as total"))[0].total;
const sfw = (await sql("items").whereRaw("id in (select item_id from tags_assign where tag_id = 1 group by item_id)").count("* as total"))[0].total;
const nsfw = (await sql("items").whereRaw("id in (select item_id from tags_assign where tag_id = 2 group by item_id)").count("* as total"))[0].total;
return {
tagged,
untagged,
total: tagged + untagged,
sfw,
nsfw
};
};
async hash(str) {
const salt = crypto.randomBytes(16).toString("hex");
const derivedKey = await scrypt(str, salt, 64);
return "$f0ck$" + salt + ":" + derivedKey.toString("hex");
};
async verify(str, hash) {
const [ salt, key ] = hash.substring(6).split(":");
const keyBuffer = Buffer.from(key, "hex");
const derivedKey = await scrypt(str, salt, 64);
return crypto.timingSafeEqual(keyBuffer, derivedKey);
};
};

View File

@ -1,11 +0,0 @@
export const auth = (req, res) => {
if(!req.session) {
return {
success: false,
redirect: "/"
};
}
return {
success: true
};
};

View File

@ -1,98 +0,0 @@
import fs from "fs";
import path from "path";
export default new class Router {
#mimes;
constructor() {
this.routes = new Map();
};
route(method, args) {
// args[0]: route
// args[1]: func || meddlware
const route = args[0];
let func;
let meddlware = null;
if(args.length === 2) {
func = args[1];
}
else {
meddlware = args[1];
func = args[2];
}
this.routes.set(route, { method: method, meddlware: meddlware, f: func });
console.info("route set", method, route);
};
get() {
this.route("GET", arguments);
};
post() {
this.route("POST", arguments);
};
async static({ dir = path.resolve() + "/public", route = /^\/public/ }) {
if(!this.#mimes) {
this.#mimes = new Map();
(await fs.promises.readFile("/etc/mime.types", "utf-8"))
.split("\n")
.filter(e => !e.startsWith("#") && e)
.map(e => e.split(/\s{2,}/))
.filter(e => e.length > 1)
.forEach(m => m[1].split(" ").forEach(ext => this.#mimes.set(ext, m[0])));
}
this.get(route, async (req, res) => {
try {
const mime = this.#mimes.get(req.url.path.split(".").pop());
const file = path.join(dir, req.url.path.replace(route, ""));
let stat;
try {
stat = await fs.promises.stat(file);
} catch {
return res.reply({
code: 404,
body: "404 - file not found."
});
}
if(!mime.startsWith("video") && !mime.startsWith("audio")) {
return res.reply({
type: this.#mimes.get(req.url.path.split(".").pop()).toLowerCase(),
body: await fs.promises.readFile(path.join(dir, req.url.path.replace(route, "")))
});
}
if(req.headers.range) {
const parts = req.headers.range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : stat.size - 1;
res.writeHead(206, {
"Content-Range": `bytes ${start}-${end}/${stat.size}`,
"Accept-Ranges": "bytes",
"Content-Length": (end - start) + 1,
"Content-Type": mime,
});
const stream = fs.createReadStream(file, { start: start, end: end })
.on("open", () => stream.pipe(res))
.on("error", err => res.end(err));
}
else {
res.writeHead(200, {
"Content-Length": stat.size,
"Content-Type": mime,
});
fs.createReadStream(file).pipe(res);
}
} catch(err) {
console.error(err);
return res.reply({
code: 500,
body: "500 - f0ck hat keinen b0ck"
});
}
});
};
};
Map.prototype.getRoute = function(path, method, tmp) {
return (!(tmp = [...this.entries()].filter(r => ( r[0] === path || r[0].exec?.(path) ) && r[1].method.includes(method) )[0])) ? false : tmp[1];
};

View File

@ -1,151 +1,131 @@
import router from "../router.mjs";
import sql from "../sql.mjs";
import tpl from "../tpl.mjs";
import lib from "../lib.mjs";
import util from "util";
import crypto from "crypto";
import { auth } from "../meddlware.mjs";
import { exec } from "child_process";
import search from "./inc/search.mjs";
const scrypt = util.promisify(crypto.scrypt);
const hash = async str => {
const salt = crypto.randomBytes(16).toString("hex");
const derivedKey = await scrypt(str, salt, 64);
return "$f0ck$" + salt + ":" + derivedKey.toString("hex");
};
const verify = async (str, hash) => {
const [ salt, key ] = hash.substring(6).split(":");
const keyBuffer = Buffer.from(key, "hex");
const derivedKey = await scrypt(str, salt, 64);
return crypto.timingSafeEqual(keyBuffer, derivedKey);
};
const createID = () => crypto.randomBytes(16).toString("hex") + Date.now().toString(24);
router.get(/^\/login(\/)?$/, async (req, res) => {
if(req.cookies.session)
return res.reply({ body: "du bist schon eingeloggt lol<pre>"+util.inspect(req.session)+"</pre>" });
res.reply({
body: tpl.render("views/login", {}, req)
});
});
router.post(/^\/login(\/)?$/, async (req, res) => {
const user = await sql("user").where("login", req.post.username.toLowerCase()).limit(1);
if(user.length === 0)
return res.reply({ body: "user doesn't exist or wrong password" });
if(!(await verify(req.post.password, user[0].password)))
return res.reply({ body: "user doesn't exist or wrong password" });
const stamp = Date.now() / 1e3;
const session = lib.md5(createID());
await sql("user_sessions").insert({
user_id: user[0].id,
session: lib.md5(session),
browser: req.headers["user-agent"],
created_at: stamp,
last_used: stamp,
last_action: "/login"
});
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Set-Cookie": `session=${session}; Path=/; Expires=Fri, 31 Dec 9999 23:59:59 GMT`,
"Location": "/"
}).end();
});
router.get(/^\/logout$/, auth, async (req, res) => {
const usersession = await sql("user_sessions").where("id", req.session.sess_id);
if(usersession.length === 0)
return res.reply({ body: "nope 2" });
await sql("user_sessions").where("id", req.session.sess_id).del();
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Set-Cookie": "session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT",
"Location": "/login"
}).end();
});
router.get(/^\/login\/pwdgen$/, async (req, res) => {
res.reply({
body: "<form action=\"/login/pwdgen\" method=\"post\"><input type=\"text\" name=\"pwd\" placeholder=\"pwd\" /><input type=\"submit\" value=\"f0ck it\" /></form>"
});
});
router.post(/^\/login\/pwdgen$/, async (req, res) => {
res.reply({
body: await hash(req.post.pwd)
});
});
router.get(/^\/login\/test$/, async (req, res) => {
res.reply({
body: "<pre>" + util.inspect(req) + "</pre>"
});
});
router.get(/^\/admin(\/)?$/, auth, async (req, res) => { // frontpage
const totals = await sql("items")
.select(
sql.raw("(select count(*) from items) total"),
sql.raw("sum(if(ifnull(tags_assign.item_id, 0) = 0, 1, 0)) untagged"),
sql.raw("sum(if(ifnull(tags_assign.item_id, 1) = 1, 0, 1)) tagged"))
.leftJoin("tags_assign", "tags_assign.item_id", "items.id");
res.reply({
body: tpl.render("views/admin", { totals: totals[0] }, req)
});
});
router.get(/^\/admin\/sessions(\/)?$/, auth, async (req, res) => {
const rows = await sql("user_sessions")
.leftJoin("user", "user.id", "user_sessions.user_id")
.select("user_sessions.*", "user.user")
.orderBy("user.id");
const totals = await sql("items")
.select(
sql.raw("(select count(*) from items) total"),
sql.raw("sum(if(ifnull(tags_assign.item_id, 0) = 0, 1, 0)) untagged"),
sql.raw("sum(if(ifnull(tags_assign.item_id, 1) = 1, 0, 1)) tagged"))
.leftJoin("tags_assign", "tags_assign.item_id", "items.id");
res.reply({
body: tpl.render("views/admin_sessions", {
sessions: rows,
totals: totals[0]
}, req)
});
});
router.get(/^\/admin\/test(\/)?$/, auth, async (req, res) => {
let ret;
const totals = await sql("items")
.select(
sql.raw("(select count(*) from items) total"),
sql.raw("sum(if(ifnull(tags_assign.item_id, 0) = 0, 1, 0)) untagged"),
sql.raw("sum(if(ifnull(tags_assign.item_id, 1) = 1, 0, 1)) tagged"))
.leftJoin("tags_assign", "tags_assign.item_id", "items.id");
if(Object.keys(req.url.qs).length > 0) {
const tag = req.url.qs.tag;
const rows = await sql("tags")
.select("items.id", "items.username", "tags.tag")
.leftJoin("tags_assign", "tags_assign.tag_id", "tags.id")
.leftJoin("items", "items.id", "tags_assign.item_id")
.where("tags.tag", "regexp", tag);
ret = search(rows, tag);
const auth = async (req, res, next) => {
if(!req.session) {
return res.reply({
code: 401,
body: "401 - Unauthorized"
});
}
return next();
};
res.reply({
body: tpl.render("views/admin_search", {
result: ret,
totals: totals[0]
}, req)
export default (router, tpl) => {
router.get(/^\/login(\/)?$/, async (req, res) => {
if(req.cookies.session)
return res.reply({ body: "du bist schon eingeloggt lol" });
res.reply({
body: tpl.render("login")
});
});
});
router.post(/^\/login(\/)?$/, async (req, res) => {
const user = await sql("user").where("login", req.post.username.toLowerCase()).limit(1);
if(user.length === 0)
return res.reply({ body: "user doesn't exist or wrong password" });
if(!(await lib.verify(req.post.password, user[0].password)))
return res.reply({ body: "user doesn't exist or wrong password" });
const stamp = Date.now() / 1e3;
const session = lib.md5(lib.createID());
await sql("user_sessions").insert({
user_id: user[0].id,
session: lib.md5(session),
browser: req.headers["user-agent"],
created_at: stamp,
last_used: stamp,
last_action: "/login"
});
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Set-Cookie": `session=${session}; Path=/; Expires=Fri, 31 Dec 9999 23:59:59 GMT`,
"Location": "/"
}).end();
});
router.get(/^\/logout$/, auth, async (req, res) => {
const usersession = await sql("user_sessions").where("id", req.session.sess_id);
if(usersession.length === 0)
return res.reply({ body: "nope 2" });
await sql("user_sessions").where("id", req.session.sess_id).del();
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Set-Cookie": "session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT",
"Location": "/"
}).end();
});
router.get(/^\/login\/pwdgen$/, async (req, res) => {
res.reply({
body: "<form action=\"/login/pwdgen\" method=\"post\"><input type=\"text\" name=\"pwd\" placeholder=\"pwd\" /><input type=\"submit\" value=\"f0ck it\" /></form>"
});
});
router.post(/^\/login\/pwdgen$/, async (req, res) => {
res.reply({
body: await lib.hash(req.post.pwd)
});
});
router.get(/^\/admin(\/)?$/, auth, async (req, res) => { // frontpage
res.reply({
body: tpl.render("admin", { totals: await lib.countf0cks(), session: req.session }, req)
});
});
router.get(/^\/admin\/sessions(\/)?$/, auth, async (req, res) => {
const rows = await sql("user_sessions")
.leftJoin("user", "user.id", "user_sessions.user_id")
.select("user_sessions.*", "user.user")
.orderBy("user.id");
res.reply({
body: tpl.render("admin/sessions", {
session: req.session,
sessions: rows,
totals: await lib.countf0cks()
}, req)
});
});
router.get(/^\/admin\/test(\/)?$/, auth, async (req, res) => {
let ret;
if(Object.keys(req.url.qs).length > 0) {
const tag = req.url.qs.tag;
const rows = await sql("tags")
.select("items.id", "items.username", "tags.tag")
.leftJoin("tags_assign", "tags_assign.tag_id", "tags.id")
.leftJoin("items", "items.id", "tags_assign.item_id")
.where("tags.tag", "regexp", tag);
ret = search(rows, tag);
}
res.reply({
body: tpl.render("admin/search", {
result: ret,
totals: await lib.countf0cks(),
session: req.session
}, req)
});
});
router.get(/^\/admin\/log(\/)?$/, auth, async (req, res) => {
exec("journalctl -xu f0ck", (err, stdout) => {
res.reply({
body: tpl.render("admin/log", {
log: stdout.split("\n").slice(-500)
}, req)
});
});
});
return router;
};

View File

@ -1,73 +0,0 @@
import router from "../router.mjs";
import sql from "../sql.mjs";
const allowedMimes = [ "audio", "image", "video", "%" ];
router.get("/api/v1", (req, res) => {
res.end("api lol");
});
router.get(/^\/api\/v1\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => {
const user = req.url.split[3] === "user" ? req.url.split[4] : "%";
const mime = (allowedMimes.filter(n => req.url.split[3]?.startsWith(n))[0] ? req.url.split[3] : "") + "%";
const rows = await sql("items").orderByRaw("rand()").limit(1).where("mime", "like", mime).andWhere("username", "like", user);
res
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(rows.length > 0 ? rows[0] : []), "utf-8");
});
router.get(/^\/api\/v1\/p\/([0-9]+)/, async (req, res) => { // legacy
let eps = 100;
let id = +req.url.split[3];
const rows = await sql("items").where("id", "<", id).orderBy("id", "desc").limit(eps);
const items = {
items: rows,
last: rows[rows.length - 1].id
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(items), "utf-8");
});
router.get(/^\/api\/v1\/item\/[0-9]+$/, async (req, res) => {
const id = +req.url.split[3];
const item = await sql("items").where("id", id).limit(1);
const next = await sql("items").select("id").where("id", ">", id).orderBy("id").limit(1);
const prev = await sql("items").select("id").where("id", "<", id).orderBy("id", "desc").limit(1);
if(item.length === 0)
return "nope";
const rows = {
...item[0],
...{
next: next[0]?.id ?? null,
prev: prev[0]?.id ?? null
}
};
res.reply({
type: "application/json",
body: JSON.stringify(rows)
});
});
router.get(/^\/api\/v1\/user\/.*(\/[0-9]+)?$/, async (req, res) => { // auf qs umstellen
const user = req.url.split[3];
const eps = +req.url.split[4] || 50;
const rows = await sql("items")
.select("id", "mime", "size", "src", "stamp", "userchannel", "username", "usernetwork")
.where("username", user)
.orderBy("stamp", "desc")
.limit(eps);
res.reply({
type: "application/json",
body: JSON.stringify(rows.length > 0 ? rows : [])
});
});

View File

@ -1,129 +1,15 @@
import router from "../router.mjs";
import sql from "../sql.mjs";
import { auth } from "../meddlware.mjs";
import util from "util";
const allowedMimes = [ "audio", "image", "video", "%" ];
router.get("/api/v2", (req, res) => {
res.end("api lol");
});
router.get(/^\/api\/v2\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => {
const user = req.url.split[3] === "user" ? req.url.split[4] : "%";
const mime = (allowedMimes.filter(n => req.url.split[3]?.startsWith(n))[0] ? req.url.split[3] : "") + "%";
const rows = await sql("items").orderByRaw("rand()").limit(1).where("mime", "like", mime).andWhere("username", "like", user);
res
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(rows.length > 0 ? rows[0] : []), "utf-8");
});
router.get(/^\/api\/v2\/p\/([0-9]+)/, async (req, res) => { // legacy
let eps = 100;
let id = +req.url.split[3];
const rows = await sql("items").where("id", "<", id).orderBy("id", "desc").limit(eps);
const items = {
items: rows,
last: rows[rows.length - 1].id
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(items), "utf-8");
});
router.get(/^\/api\/v2\/item\/[0-9]+$/, async (req, res) => {
const id = +req.url.split[3];
const item = await sql("items").where("id", id).limit(1);
const next = await sql("items").select("id").where("id", ">", id).orderBy("id").limit(1);
const prev = await sql("items").select("id").where("id", "<", id).orderBy("id", "desc").limit(1);
if(item.length === 0)
return "nope";
const rows = {
...item[0],
...{
next: next[0]?.id ?? null,
prev: prev[0]?.id ?? null
}
};
res.reply({
type: "application/json",
body: JSON.stringify(rows)
});
});
router.get(/^\/api\/v2\/user\/.*(\/\d+)?$/, async (req, res) => { // auf qs umstellen
const user = req.url.split[3];
const eps = +req.url.split[4] || 50;
const rows = await sql("items")
.select("id", "mime", "size", "src", "stamp", "userchannel", "username", "usernetwork")
.where("username", user)
.orderBy("stamp", "desc")
.limit(eps);
res.reply({
type: "application/json",
body: JSON.stringify(rows.length > 0 ? rows : [])
});
});
// adminzeugs
router.post(/^\/api\/v2\/admin\/tags\/add$/, auth, async (req, res) => {
if(!req.post.postid || !req.post.tag) {
return res.reply({ body: JSON.stringify({
success: false,
msg: "missing postid or tag"
})});
}
const postid = +req.post.postid;
const tag = req.post.tag;
if(tag.length >= 45) {
return res.reply({ body: JSON.stringify({
success: false,
msg: "tag is too long!"
})});
}
try {
let tagid;
const tag_exists = await sql("tags").select("id", "tag").where("tag", tag);
if(tag_exists.length === 0) { // create new tag
tagid = (await sql("tags").insert({
tag: tag
}))[0];
}
else {
tagid = tag_exists[0].id;
}
await sql("tags_assign").insert({
tag_id: tagid,
item_id: postid,
prefix: `${req.session.user}@webinterface`
const auth = async (req, res, next) => {
if(!req.session) {
return res.reply({
code: 401,
body: "401 - Unauthorized"
});
} catch(err) {
return res.reply({ body: JSON.stringify({
success: false,
msg: err.message,
tags: await getTags(postid)
})});
}
return res.reply({ body: JSON.stringify({
success: true,
postid: req.post.postid,
tag: req.post.tag,
tags: await getTags(postid)
})});
});
return next();
};
const getTags = async itemid => await sql("tags_assign")
.leftJoin("tags", "tags.id", "tags_assign.tag_id")
@ -144,61 +30,189 @@ const cleanTags = async () => {
await deleteTag.del();
};
router.post(/^\/api\/v2\/admin\/tags\/delete$/, auth, async (req, res) => {
if(!req.post.postid || !req.post.tag) {
return res.reply({ body: JSON.stringify({
success: false,
msg: "missing postid or tag"
})});
}
export default (router, tpl) => {
router.group(/^\/api\/v2/, group => {
const postid = +req.post.postid;
const tag = req.post.tag;
const tags = await getTags(postid);
const tagid = tags.filter(t => t.tag === tag)[0]?.id ?? null;
if(!tagid || tagid?.length === 0) {
return res.reply({ body: JSON.stringify({
success: false,
tag: tag,
msg: "tag is not assigned",
tags: await getTags(postid)
})});
}
group.get(/$/, (req, res) => {
res.end("api lol");
});
let q = sql("tags_assign").where("tag_id", tagid).andWhere("item_id", postid).del();
if(req.session.level < 50)
q = q.andWhere("prefix", `${req.session.user}@webinterface`);
const reply = !!(await q);
group.get(/\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => {
const user = req.url.split[3] === "user" ? req.url.split[4] : "%";
const mime = (allowedMimes.filter(n => req.url.split[3]?.startsWith(n))[0] ? req.url.split[3] : "") + "%";
const rows = await sql("items").orderByRaw("rand()").limit(1).where("mime", "like", mime).andWhere("username", "like", user);
res
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(rows.length > 0 ? rows[0] : []), "utf-8");
});
group.get(/\/p\/([0-9]+)/, async (req, res) => { // legacy
let eps = 100;
let id = +req.url.split[3];
const rows = await sql("items").where("id", "<", id).orderBy("id", "desc").limit(eps);
const items = {
items: rows,
last: rows[rows.length - 1].id
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(items), "utf-8");
});
group.get(/\/item\/[0-9]+$/, async (req, res) => {
const id = +req.url.split[3];
const item = await sql("items").where("id", id).limit(1);
const next = await sql("items").select("id").where("id", ">", id).orderBy("id").limit(1);
const prev = await sql("items").select("id").where("id", "<", id).orderBy("id", "desc").limit(1);
if(item.length === 0)
return "nope";
const rows = {
...item[0],
...{
next: next[0]?.id ?? null,
prev: prev[0]?.id ?? null
}
};
res.reply({
type: "application/json",
body: JSON.stringify(rows)
});
});
group.get(/\/user\/.*(\/\d+)?$/, async (req, res) => { // auf qs umstellen
const user = req.url.split[3];
const eps = +req.url.split[4] || 50;
const rows = await sql("items")
.select("id", "mime", "size", "src", "stamp", "userchannel", "username", "usernetwork")
.where("username", user)
.orderBy("stamp", "desc")
.limit(eps);
res.reply({
type: "application/json",
body: JSON.stringify(rows.length > 0 ? rows : [])
});
});
// adminzeugs
group.post(/\/admin\/tags\/add$/, auth, async (req, res) => {
if(!req.post.postid || !req.post.tag) {
return res.reply({ body: JSON.stringify({
success: false,
msg: "missing postid or tag"
})});
}
const postid = +req.post.postid;
const tag = req.post.tag;
if(tag.length >= 45) {
return res.reply({ body: JSON.stringify({
success: false,
msg: "tag is too long!"
})});
}
try {
let tagid;
const tag_exists = await sql("tags").select("id", "tag").where("tag", tag);
if(tag_exists.length === 0) { // create new tag
tagid = (await sql("tags").insert({
tag: tag
}))[0];
}
else {
tagid = tag_exists[0].id;
}
await sql("tags_assign").insert({
tag_id: tagid,
item_id: postid,
prefix: `${req.session.user}@webinterface`
});
} catch(err) {
return res.reply({ body: JSON.stringify({
success: false,
msg: err.message,
tags: await getTags(postid)
})});
}
return res.reply({ body: JSON.stringify({
success: true,
postid: req.post.postid,
tag: req.post.tag,
tags: await getTags(postid)
})});
});
group.post(/\/admin\/tags\/delete$/, auth, async (req, res) => {
if(!req.post.postid || !req.post.tag) {
return res.reply({ body: JSON.stringify({
success: false,
msg: "missing postid or tag"
})});
}
const postid = +req.post.postid;
const tag = req.post.tag;
const tags = await getTags(postid);
const tagid = tags.filter(t => t.tag === tag)[0]?.id ?? null;
if(!tagid || tagid?.length === 0) {
return res.reply({ body: JSON.stringify({
success: false,
tag: tag,
msg: "tag is not assigned",
tags: await getTags(postid)
})});
}
let q = sql("tags_assign").where("tag_id", tagid).andWhere("item_id", postid).del();
if(req.session.level < 50)
q = q.andWhere("prefix", `${req.session.user}@webinterface`);
const reply = !!(await q);
//await cleanTags();
return res.reply({ body: JSON.stringify({
success: reply,
tag: tag,
tagid: tagid,
tags: await getTags(postid)
})});
});
group.get(/\/admin\/tags\/get\/\d+$/, auth, async (req, res) => {
return res.reply({ body: JSON.stringify({
tags: await getTags(+req.url.split[5])
})});
});
group.get(/\/admin\/deletepost\/\d+$/, auth, async (req, res) => {
if(!req.url.split[4]) {
return res.reply({ body: JSON.stringify({
success: true,
msg: "no postid"
})});
}
const postid = +req.url.split[4];
await sql("items").where("id", postid).del();
res.reply({ body: JSON.stringify({
success: true
})});
});
//await cleanTags();
});
return res.reply({ body: JSON.stringify({
success: reply,
tag: tag,
tagid: tagid,
tags: await getTags(postid)
})});
});
router.get(/^\/api\/v2\/admin\/tags\/get\/\d+$/, auth, async (req, res) => {
return res.reply({ body: JSON.stringify({
tags: await getTags(+req.url.split[5])
})});
});
router.get(/^\/api\/v2\/admin\/deletepost\/\d+$/, auth, async (req, res) => {
if(!req.url.split[4]) {
return res.reply({ body: JSON.stringify({
success: true,
msg: "no postid"
})});
}
const postid = +req.url.split[4];
const rows = await sql("items").where("id", postid).del();
res.reply({ body: JSON.stringify({
success: true
})});
});
return router;
};

View File

@ -1,140 +1,174 @@
import router from "../router.mjs";
import cfg from "../../../config.json";
import url from "url";
import fs from "fs";
import sql from "../sql.mjs";
import lib from "../lib.mjs";
import tpl from "../tpl.mjs";
tpl.readdir("views");
const allowedMimes = [ "audio", "image", "video", "%" ];
router.get(/^\/(audio\/?|image\/?|video\/?)?(p\/\d+)?$/, async (req, res) => {
const tmpmime = (allowedMimes.filter(n => req.url.split[0].startsWith(n))[0] ? req.url.split[0] : "");
const mime = tmpmime + "%";
const tmp = req.cookies.session ? "" : "id in (select item_id from tags_assign where tag_id = 1 group by item_id)";
const total = (
await sql("items")
.whereRaw(tmp)
.andWhere("items.mime", "like", mime)
.count("* as total")
)[0].total;
const pages = +Math.ceil(total / cfg.websrv.eps);
const page = Math.min(pages, +req.url.split[tmpmime.length > 0 ? 2 : 1] || 1);
const offset = (page - 1) * cfg.websrv.eps;
const rows = await sql("items")
.select("items.id", "items.mime")
.whereRaw(tmp)
.andWhere("items.mime", "like", mime)
.orderBy("items.id", "desc")
.offset(offset)
.limit(cfg.websrv.eps);
let cheat = [];
for(let i = Math.max(1, page - 3); i <= Math.min(page + 3, pages); i++)
cheat.push(i);
rows.forEach(e => {
if(!fs.existsSync(`public/t/${e.id}.png`))
fs.copyFileSync("public/s/img/broken.png", `public/t/${e.id}.png`);
});
const data = {
items: rows,
pagination: {
start: 1,
end: pages,
prev: (page > 1) ? page - 1 : null,
next: (page < pages) ? page + 1 : null,
page: page,
cheat: cheat,
link: `/${tmpmime ? tmpmime + "/" : ""}p/`
},
last: rows[rows.length - 1].id,
filter: tmpmime ? tmpmime : undefined
};
res.reply({ body: tpl.render("views/index", data, req) });
});
router.get(/^\/((audio\/|video\/|image\/)?[0-9]+)$/, async (req, res) => {
let id = false;
let mime = "";
let tmpmime = false;
const tmp = req.cookies.session ? "" : "id in (select item_id from tags_assign where tag_id = 1 group by item_id)";
if(allowedMimes.filter(n => req.url.split[0].startsWith(n))[0] ? req.url.split[0] : "") {
mime = tmpmime = req.url.split[0];
id = +req.url.split[1];
}
else {
mime = "%";
id = +req.url.split[0];
}
mime += "/%";
const query = (await sql("items").whereRaw(tmp).andWhere("id", id).andWhere("mime", "like", mime).limit(1))?.shift();
if(!query?.id)
return res.redirect("/404");
const tags = await sql("tags_assign").leftJoin("tags", "tags.id", "tags_assign.tag_id").where("tags_assign.item_id", id);
const qmin = await sql("items").select("id").whereRaw(tmp).andWhere("mime", "like", mime).orderBy("id").limit(1);
const qmax = await sql("items").select("id").whereRaw(tmp).andWhere("mime", "like", mime).orderBy("id", "desc").limit(1);
const qnext = (await sql("items").select("id").whereRaw(tmp).andWhere("id", ">", id).andWhere("mime", "like", mime).orderBy("id").limit(3)).reverse();
const qprev = await sql("items").select("id").whereRaw(tmp).andWhere("id", "<", id).andWhere("mime", "like", mime).orderBy("id", "desc").limit(3);
const cheat = qnext.concat([{ id: id }].concat(qprev)).map(e => +e.id);
const next = qnext[qnext.length - 1] ? qnext[qnext.length - 1].id : false;
const prev = qprev[0] ? qprev[0].id : false;
for(let t = 0; t < tags.length; t++)
tags[t].tag = tags[t].tag.replace(/[\u00A0-\u9999<>\&]/g, i => '&#'+i.charCodeAt(0)+';');
const data = {
user: {
name: query.username,
channel: query.userchannel,
network: query.usernetwork
},
item: {
id: query.id,
src: {
long: query.src,
short: url.parse(query.src).hostname,
},
thumbnail: `${cfg.websrv.paths.thumbnails}/${query.id}.png`,
coverart: `${cfg.websrv.paths.coverarts}/${query.id}.png`,
dest: `${cfg.websrv.paths.images}/${query.dest}`,
mime: query.mime,
size: lib.formatSize(query.size),
timestamp: lib.timeAgo(new Date(query.stamp * 1e3).toISOString()),
tags: tags
},
title: `${query.id} - f0ck.me`,
pagination: {
start: qmax[0].id,
end: qmin[0].id,
prev: next,
next: prev,
page: query.id,
cheat: cheat,
link: `/${tmpmime ? tmpmime + "/" : ""}`
},
filter: tmpmime ? tmpmime : undefined,
lul: cfg.websrv.phrases[~~(Math.random() * cfg.websrv.phrases.length)]
};
res.reply({ body: tpl.render("views/item", data, req) });
});
router.get(/^\/(about)$/, (req, res) => {
res.reply({
body: tpl.render(`views/${req.url.split[0]}`, {}, req)
});
});
import cfg from "../../../config.json" assert { type: "json" };
import sql from "../sql.mjs";
import lib from "../lib.mjs";
import fs from "fs";
import url from "url";
const allowedMimes = [ "audio", "image", "video", "%" ];
const auth = async (req, res, next) => {
if(!req.session)
return res.redirect("/login");
return next();
};
export default (router, tpl) => {
router.get(/^\/(audio\/?|image\/?|video\/?)?(p\/\d+)?$/, async (req, res) => {
const tmpmime = (allowedMimes.filter(n => req.url.split[0].startsWith(n))[0] ? req.url.split[0] : "");
const mime = tmpmime + "%";
const tmp = tmpmime == "audio" ? lib.getMode(0) : lib.getMode(req.session.mode ?? 0);
const total = (
await sql("items")
.whereRaw(tmp)
.andWhere("items.mime", "like", mime)
.count("* as total")
)[0].total;
const pages = +Math.ceil(total / cfg.websrv.eps);
const page = Math.min(pages, +req.url.split[tmpmime.length > 0 ? 2 : 1] || 1);
const offset = (page - 1) * cfg.websrv.eps;
const rows = await sql("items")
.select("items.id", "items.mime", "tags_assign.tag_id")
.joinRaw("left join tags_assign on tags_assign.item_id = items.id and (tags_assign.tag_id = 1 or tags_assign.tag_id = 2)")
.whereRaw(tmp)
.andWhere("items.mime", "like", mime)
.orderBy("items.id", "desc")
.offset(offset)
.limit(cfg.websrv.eps);
let cheat = [];
for(let i = Math.max(1, page - 3); i <= Math.min(page + 3, pages); i++)
cheat.push(i);
rows.forEach(e => {
if(!fs.existsSync(`public/t/${e.id}.png`))
fs.copyFileSync("public/s/img/broken.png", `public/t/${e.id}.png`);
});
const data = {
items: rows,
pagination: {
start: 1,
end: pages,
prev: (page > 1) ? page - 1 : null,
next: (page < pages) ? page + 1 : null,
page: page,
cheat: cheat,
link: `/${tmpmime ? tmpmime + "/" : ""}p/`
},
last: rows[rows.length - 1].id,
filter: tmpmime ? tmpmime : undefined
};
res.reply({ body: tpl.render("index", data, req) });
});
router.get(/^\/((audio\/|video\/|image\/)?[0-9]+)$/, async (req, res) => {
let id = false;
let mime = "";
let tmpmime = false;
if(allowedMimes.filter(n => req.url.split[0].startsWith(n))[0] ? req.url.split[0] : "") {
mime = tmpmime = req.url.split[0];
id = +req.url.split[1];
}
else {
mime = "%";
id = +req.url.split[0];
}
mime += "/%";
let tmp = tmpmime == "audio" ? lib.getMode(0) : lib.getMode(req.session.mode ?? 0);
if(id == 404)
tmp = "";
const query = (await sql("items")
.whereRaw(tmp)
.andWhere("id", id)
.andWhere("mime", "like", mime)
.limit(1))
?.shift();
if(!query?.id)
return res.redirect("/404");
const tags = await sql("tags_assign").leftJoin("tags", "tags.id", "tags_assign.tag_id").where("tags_assign.item_id", id);
const qmin = await sql("items").select("id").whereRaw(tmp).andWhere("mime", "like", mime).orderBy("id").limit(1);
const qmax = await sql("items").select("id").whereRaw(tmp).andWhere("mime", "like", mime).orderBy("id", "desc").limit(1);
const qnext = (await sql("items").select("id").whereRaw(tmp).andWhere("id", ">", id).andWhere("mime", "like", mime).orderBy("id").limit(3)).reverse();
const qprev = await sql("items").select("id").whereRaw(tmp).andWhere("id", "<", id).andWhere("mime", "like", mime).orderBy("id", "desc").limit(3);
const cheat = qnext.concat([{ id: id }].concat(qprev)).map(e => +e.id);
const next = qnext[qnext.length - 1] ? qnext[qnext.length - 1].id : false;
const prev = qprev[0] ? qprev[0].id : false;
for(let t = 0; t < tags.length; t++)
tags[t].tag = tags[t].tag.replace(/[\u00A0-\u9999<>\&]/g, i => '&#'+i.charCodeAt(0)+';');
const data = {
user: {
name: query.username,
channel: query.userchannel,
network: query.usernetwork
},
item: {
id: query.id,
src: {
long: query.src,
short: url.parse(query.src).hostname,
},
thumbnail: `${cfg.websrv.paths.thumbnails}/${query.id}.png`,
coverart: `${cfg.websrv.paths.coverarts}/${query.id}.png`,
dest: `${cfg.websrv.paths.images}/${query.dest}`,
mime: query.mime,
size: lib.formatSize(query.size),
timestamp: lib.timeAgo(new Date(query.stamp * 1e3).toISOString()),
tags: tags
},
title: `${query.id} - f0ck.me`,
pagination: {
start: qmax[0].id,
end: qmin[0].id,
prev: next,
next: prev,
page: query.id,
cheat: cheat,
link: `/${tmpmime ? tmpmime + "/" : ""}`
},
filter: tmpmime ? tmpmime : undefined
};
res.reply({ body: tpl.render("item", data, req) });
});
router.get(/^\/(about)$/, (req, res) => {
res.reply({
body: tpl.render(req.url.split[0], {}, req)
});
});
router.get(/^\/mode\/(\d)/, auth, async (req, res) => {
const mode = +req.url.split[1];
let referertmp = req.headers.referer?.split("/");
let referer = "";
if(referertmp.length && ['image','audio','video'].includes(referertmp[3]))
referer = referertmp[3];
if(cfg.allowedModes[mode]) {
await sql("user_options")
.insert({
user_id: req.session.id,
mode: mode,
theme: req.theme ?? "f0ck"
})
.onConflict("user_id")
.merge();
}
res.redirect(`/${referer}`);
});
return router;
};

View File

@ -1,12 +1,15 @@
import router from "../router.mjs";
import sql from "../sql.mjs";
import lib from "../lib.mjs";
const allowedMimes = [ "audio", "image", "video", "%" ];
router.get(/^\/random(\/image|\/video|\/audio)?$/, async (req, res) => {
const mime = (allowedMimes.filter(n => req.url.split[1]?.startsWith(n))[0] ? req.url.split[1] : "") + "%";
const tmp = req.cookies.session ? "" : "id in (select item_id from tags_assign where tag_id = 1 group by item_id)";
const rows = await sql("items").select("id").whereRaw(tmp).andWhere("mime", "like", mime).orderByRaw("rand()").limit(1);
res.redirect(`/${req.url.split[1] ? req.url.split[1] + "/" : ""}${rows[0].id}`);
});
export default (router, tpl) => {
router.get(/^\/random(\/image|\/video|\/audio)?$/, async (req, res) => {
const tmp = lib.getMode(req.session.mode ?? 0);
const mime = (allowedMimes.filter(n => req.url.split[1]?.startsWith(n))[0] ? req.url.split[1] : "") + "%";
const rows = await sql("items").select("id").whereRaw(tmp).andWhere("mime", "like", mime).orderByRaw("rand()").limit(1);
res.redirect(`/${req.url.split[1] ? req.url.split[1] + "/" : ""}${rows[0].id}`);
});
return router;
};

View File

@ -1,22 +1,23 @@
import path from "path";
import router from "../router.mjs";
router.static({
dir: path.resolve() + "/public/b",
route: /^\/b\//
});
router.static({
dir: path.resolve() + "/public/s",
route: /^\/s\//
});
router.static({
dir: path.resolve() + "/public/t",
route: /^\/t\//
});
router.static({
dir: path.resolve() + "/public/ca",
route: /^\/ca\//
});
export default (router, tpl) => {
router.static({
dir: path.resolve() + "/public/b",
route: /^\/b\//
});
router.static({
dir: path.resolve() + "/public/s",
route: /^\/s\//
});
router.static({
dir: path.resolve() + "/public/t",
route: /^\/t\//
});
router.static({
dir: path.resolve() + "/public/ca",
route: /^\/ca\//
});
};

View File

@ -1,18 +0,0 @@
import router from "../router.mjs";
import url from "url";
import util from "util";
import sql from "../sql.mjs";
router.get("/stats", async (req, res) => {
const rows = await sql("items").select("src");
let hosts = {};
rows.forEach(e => {
const host = url.parse(e.src).hostname;
hosts[host] ? hosts[host]++ : hosts[host] = 1;
});
const data = util.inspect(Object.keys(hosts).sort((a, b) => hosts[b] - hosts[a]).map(k => ({ [k]: hosts[k] })).reduce((a, b) => ({ ...a, ...b })));
res.reply({
body: "<pre>" + data + "</pre>"
});
});

View File

@ -1,51 +0,0 @@
import router from "../router.mjs";
import sql from "../sql.mjs";
import tpl from "../tpl.mjs";
tpl.readdir("views");
router.get(/^\/tags(\/\w+)?/, async (req, res) => {
try {
const tag = req.url;
console.log(tag);
return res.reply({ body: "wip" });
/*const total = (await sql.query("select count(*) as total from items" + (mime ? q : "")))[0].total;
const limit = 299;
const pages = +Math.ceil(total / limit);
const page = Math.min(pages, +req.url.split[mime ? 2 : 1] || 1);
const offset = (page - 1) * limit;
const query = await sql.query(`select id, mime from items ${mime ? q : ""} order by id desc limit ?, ?`, [ offset, limit ]);
let cheat = [];
for(let i = Math.max(1, page - 3); i <= Math.min(page + 3, pages); i++)
cheat.push(i);
query.forEach(e => {
if(!fs.existsSync(`public/t/${e.id}.png`))
fs.copyFileSync("public/s/img/broken.png", `public/t/${e.id}.png`);
});
const data = {
items: query,
pagination: {
start: 1,
end: pages,
prev: (page > 1) ? page - 1 : null,
next: (page < pages) ? page + 1 : null,
page: page,
cheat: cheat,
link: `/${mime ? mime + "/" : ""}p/`
},
last: query[query.length - 1].id,
filter: mime ? mime : undefined
};
res.reply({ body: tpl.render("views/index", data) });*/
} catch(err) {
console.log(err);
res.reply({ body: "sorry bald wieder da lol :(" });
}
});

View File

@ -1,14 +0,0 @@
import router from "../router.mjs";
import cfg from "../../../config.json";
router.get(/^\/theme\//, async (req, res) => {
let theme = req.url.split[1] ?? cfg.websrv.themes[0];
if(!cfg.websrv.themes.includes(theme))
theme = cfg.websrv.themes[0];
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Set-Cookie": `theme=${theme}; Path=/`,
"Location": req.headers.referer ?? "/"
}).end();
});

View File

@ -1,7 +1,7 @@
import knex from "knex";
import callback from "mariadb/callback.js";
import Client_MySQL from "knex/lib/dialects/mysql/index.js";
import cfg from "../../config.json";
import cfg from "./config.mjs";
class Client_MariaDB extends Client_MySQL {
driverName = "mariadb";

View File

@ -1,49 +0,0 @@
import fs from "fs";
import path from "path";
import cfg from "./config.mjs";
export default new class {
#templates = {};
#syntax = [
[ "each", (t, _, args = t.slice(4).trim().split(" ")) => `util.forEach(${args[0]},(${(args[1] === "as" && args[2]) ? args[2] : "value"},key)=>{` ],
[ "/each", () => "});" ],
[ "if", t => `if(${t.slice(2).trim()}){` ],
[ "elseif", t => `}else if(${t.slice(6).trim()}){` ],
[ "else", () => "}else{" ],
[ "/if", () => "}" ],
[ "include", (t, data) => `html+='${this.render(cfg.websrv.cache ? t.slice(7).trim() : `views/${t.slice(7).trim()}`, data)}';` ],
[ "=", t => `html+=${t.slice(1).trim()};` ]
];
readdir(dir, root = dir, rel = dir.replace(`${root}/`, "")) {
for(const d of fs.readdirSync(`${path.resolve()}/${dir}`))
d.endsWith(".html")
? this.#templates[`${rel}/${d.split(".")[0]}`] = this.readtpl(dir, d)
: this.readdir(`${dir}/${d}`, root);
}
readtpl(dir, d) {
return fs.readFileSync(`${dir}/${d}`, "utf-8").replace(/\'/g, "\\'");
}
forEach(o, f) {
if(Array.isArray(o))
o.forEach(f);
else if(typeof o === "object")
Object.keys(o).forEach(k => f.call(null, o[k], k));
else
throw new Error(`${o} is not a iterable object`);
}
render(tpl, data = {}, req = false, f) {
if(req) { // globals
data.themes = cfg.websrv.themes;
data.theme = (typeof req.cookies.theme !== "undefined" && cfg.websrv.themes.includes(req.cookies.theme)) ? req.cookies.theme : cfg.websrv.themes[0];
data.session = req.session ? req.session : false;
}
return new Function("util", "data", "let html = \"\";with(data){" + (cfg.websrv.cache ? this.#templates[tpl] : this.readtpl(path.resolve(), `${tpl}.html`))
.trim()
.replace(/[\n\r]/g, "")
.split(/{{\s*([^}]+)\s*}}/)
.filter(Boolean)
.map(t => !(f = this.#syntax.filter(s => t.startsWith(s[0]))[0]) ? `html+='${t}';` : f[1](t, data))
.join("") + "}return html.trim().replace(/>[\\n\\r\\s]*?</g, '><')"
).bind(null, { forEach: this.forEach })(data);
}
};

View File

@ -1,6 +1,6 @@
import { promises as fs } from "fs";
import { exec } from "child_process";
import cfg from "../../inc/config.mjs";
import cfg from "../config.mjs";
import sql from "../sql.mjs";
import lib from "../lib.mjs";

View File

@ -1,4 +1,4 @@
import cfg from "../../../config.json";
import cfg from "../config.mjs";
import sql from "../sql.mjs";
import lib from "../lib.mjs";

View File

@ -1,6 +1,6 @@
import sql from "../sql.mjs";
import lib from "../lib.mjs";
import cfg from "../../../config.json";
import cfg from "../config.mjs";
export default async bot => {

View File

@ -1,4 +1,4 @@
import cfg from "../../../config.json";
import cfg from "../config.mjs";
import sql from "../sql.mjs";
import lib from "../lib.mjs";
import { getLevel } from "../admin.mjs";

View File

@ -1,8 +1,9 @@
import cfg from "../config.json";
import cfg from "./inc/config.mjs";
import sql from "./inc/sql.mjs";
import lib from "./inc/lib.mjs";
import cuffeo from "cuffeo";
import { promises as fs } from "fs";
import "./websrv.mjs";
import flummpress from "flummpress";
(async () => {
const self = {
@ -49,4 +50,64 @@ import "./websrv.mjs";
console.timeEnd("loading");
// websrv
const app = new flummpress();
const router = app.router;
const tpl = app.tpl;
app.use(async (req, res) => {
// sessionhandler
req.session = false;
if(req.url.pathname.match(/^\/(s|b|t|ca)/))
return;
req.theme = req.cookies.theme ?? 'f0ck';
if(req.cookies.session) {
const user = await sql("user_sessions") // get user
.select("user.id", "user.login", "user.user", "user.level", "user_sessions.id as sess_id", "user_options.mode", "user_options.theme")
.where("user_sessions.session", lib.md5(req.cookies.session))
.leftJoin("user", "user.id", "user_sessions.user_id")
.leftJoin("user_options", "user_options.user_id", "user_sessions.user_id")
.limit(1);
if(user.length === 0) {
return res.writeHead(307, { // delete session
"Cache-Control": "no-cache, public",
"Set-Cookie": "session=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT",
"Location": req.url.pathname
}).end();
}
req.session = user[0];
await sql("user_sessions") // log last action
.update("last_used", (Date.now() / 1e3))
.update("last_action", req.url.pathname)
.where("id", user[0].sess_id);
// update userprofile
await sql("user_options")
.insert({
user_id: user[0].id,
mode: user[0].mode ?? 0,
theme: req.session.theme ?? "f0ck"
})
.onConflict("user_id")
.merge();
}
});
tpl.views = "views";
tpl.debug = true;
tpl.cache = false;
tpl.globals = {
lul: cfg.websrv.lul,
themes: cfg.websrv.themes,
modes: cfg.allowedModes
};
router.use(tpl);
await router.importRoutesFromPath("src/inc/routes", tpl);
app.listen(cfg.websrv.port);
})();

View File

@ -1,109 +0,0 @@
import http from "http";
import url from "url";
import { promises as fs } from "fs";
import querystring from "querystring";
import cfg from "../config.json";
import sql from "./inc/sql.mjs";
import lib from "./inc/lib.mjs";
import router from "./inc/router.mjs";
(async () => {
await Promise.all((await fs.readdir("./src/inc/routes"))
.filter(r => r.endsWith(".mjs"))
.map(r => import(`./inc/routes/${r}`)));
http.createServer(async (req, res, r) => {
const t_start = process.hrtime();
req.url = url.parse(req.url.replace(/(?!^.)(\/+)?$/, ''));
req.url.split = req.url.pathname.split("/").slice(1);
req.url.qs = {...querystring.parse(req.url.query)};
req.post = await new Promise((resolve, _, data = "") => req
.on("data", d => void (data += d))
.on("end", () => {
if(req.headers['content-type'] == "application/json") {
try {
return void resolve(JSON.parse(data));
} catch(err) {}
}
void resolve(Object.fromEntries(Object.entries(querystring.parse(data)).map(([k, v]) => {
try {
return [k, decodeURIComponent(v)];
} catch(err) {
return [k, v];
}
})));
}));
res.reply = ({
code = 200,
type = "text/html",
body
}) => res.writeHead(code, { "Content-Type": `${type}; charset=utf-8` }).end(body);
res.redirect = target => res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Location": target
}).end();
req.cookies = {};
if(req.headers.cookie) {
req.headers.cookie.split("; ").forEach(c => {
const parts = c.split('=');
req.cookies[parts.shift().trim()] = decodeURI(parts.join('='));
});
}
req.session = false;
if(req.cookies.session) {
const user = await sql("user_sessions")
.select("user.id", "user.login", "user.user", "user.level", "user_sessions.id as sess_id")
.where("user_sessions.session", lib.md5(req.cookies.session))
.leftJoin("user", "user.id", "user_sessions.user_id")
.limit(1);
if(user.length > 0) {
req.session = user[0];
let q = sql("user_sessions")
.update("last_used", (Date.now() / 1e3))
.where("id", user[0].sess_id);
if(!req.url.pathname.match(/^\/(s|b|t|ca)/))
q = q.update("last_action", req.url.pathname)
await q;
}
else { // delete session
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Set-Cookie": "session=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT",
"Location": req.url.path
}).end();
}
}
console.log([
`[${(new Date()).toLocaleTimeString()}]`,
`${(process.hrtime(t_start)[1] / 1e6).toFixed(2)}ms`,
`${req.method} ${res.statusCode}`,
req.url.pathname
].map(e => e.toString().padEnd(15)).join(""));
if(r = router.routes.getRoute(req.url.pathname, req.method)) {
if(r.meddlware) {
const tmp = r.meddlware(req, res);
if(tmp.redirect)
return res.redirect(tmp.redirect);
if(!tmp.success)
return res.reply({ body: tmp.msg });
}
await r.f(req, res);
}
else {
res
.writeHead(404)
.end(`404 - ${req.method} ${req.url.pathname}`);
}
}).listen(cfg.websrv.port, () => setTimeout(() => {
console.log(`f0ck is listening on port ${cfg.websrv.port}.`);
}, 500));
})();