new backend
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import config from "../../config.json";
|
||||
import config from "./config.mjs";
|
||||
|
||||
export const getLevel = user => {
|
||||
let ret = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import _config from "../../config.json";
|
||||
import _config from "../../config.json" assert { type: "json" };
|
||||
|
||||
let config = JSON.parse(JSON.stringify(_config));
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -1,11 +0,0 @@
|
||||
export const auth = (req, res) => {
|
||||
if(!req.session) {
|
||||
return {
|
||||
success: false,
|
||||
redirect: "/"
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true
|
||||
};
|
||||
};
|
@ -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];
|
||||
};
|
@ -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;
|
||||
};
|
||||
|
@ -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 : [])
|
||||
});
|
||||
});
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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\//
|
||||
});
|
||||
};
|
||||
|
@ -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>"
|
||||
});
|
||||
});
|
@ -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 :(" });
|
||||
}
|
||||
});
|
@ -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();
|
||||
});
|
@ -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";
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
@ -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";
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import cfg from "../../../config.json";
|
||||
import cfg from "../config.mjs";
|
||||
import sql from "../sql.mjs";
|
||||
import lib from "../lib.mjs";
|
||||
|
||||
|
@ -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 => {
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
})();
|
||||
|
109
src/websrv.mjs
109
src/websrv.mjs
@ -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));
|
||||
})();
|
Reference in New Issue
Block a user