new backend
This commit is contained in:
parent
d885dd8e4e
commit
43665884f6
16
package-lock.json
generated
16
package-lock.json
generated
@ -1,15 +1,17 @@
|
||||
{
|
||||
"name": "f0ckv2",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.0.0",
|
||||
"name": "f0ckv2",
|
||||
"version": "2.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cuffeo": "^1.0.7-3",
|
||||
"flumm-fetch-cookies": "^1.4.0",
|
||||
"flummpress": "^2.0.0",
|
||||
"knex": "^0.95.4",
|
||||
"mariadb": "^2.5.2"
|
||||
}
|
||||
@ -101,6 +103,11 @@
|
||||
"node": ">=11.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flummpress": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flummpress/-/flummpress-2.0.0.tgz",
|
||||
"integrity": "sha512-4W2mTpOJI4QMVXBO09dJNK9L9GUu+bXib/jrjZV8XxZkU2LQqZhvVc6/eIhDbWJDH0+6dIotrS9k5IKwJRFczw=="
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
@ -394,6 +401,11 @@
|
||||
"flumm-fetch": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"flummpress": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flummpress/-/flummpress-2.0.0.tgz",
|
||||
"integrity": "sha512-4W2mTpOJI4QMVXBO09dJNK9L9GUu+bXib/jrjZV8XxZkU2LQqZhvVc6/eIhDbWJDH0+6dIotrS9k5IKwJRFczw=="
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
|
@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"cuffeo": "^1.0.7-3",
|
||||
"flumm-fetch-cookies": "^1.4.0",
|
||||
"flummpress": "^2.0.0",
|
||||
"knex": "^0.95.4",
|
||||
"mariadb": "^2.5.2"
|
||||
}
|
||||
|
@ -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));
|
||||
})();
|
@ -1,4 +1,4 @@
|
||||
{{include main/header}}
|
||||
@include(main/header)
|
||||
<div class="about">
|
||||
<div>
|
||||
<a href="/48908"><img src="/s/img/loool.webp" /></a>
|
||||
@ -50,4 +50,4 @@
|
||||
<p>Logs:No for Tor - Yes for cloudflare and cloudflare probably sells your soul to the devil, however our webserver doesn't log cloudflare connecting to our webserver, if you want to lurk without being flared by the cloud, see the above tor section my man</p>
|
||||
<p>But let me tell you something about internet "privacy". At first you need to understand what it means to have "privacy", for me as a human the word privacy means that I am by myself, private not observable by others, on the internet this concept does not work, in real life you might lock your door and then no one can enter the normal way to your room and you have some good old fashioned privacy, but on the internet various applications on your computer including extensions for your browser might make connections without you knowing or even giving consent if you knew, most applications send heartbeats, store information, read files on your computer, they might even process the gained data with or without you knowing or consenting to any of it, you probably accepted in good faith the ToS of many services without ever reading them, in the end it's up to you if you give a shit about your privacy, btw a VPN wont help if you still got all the tracking cookies and shit in your browser, they will just add 1+1 and you are identified again. My honest advice for anyone who seeks total privacy without bullshit, disconnect from the internet, remove the internet from your life, it's a bulletproof solution! With that being said, have a good day!</p>
|
||||
</div>
|
||||
{{include main/footer}}
|
||||
@include(main/footer)
|
||||
|
@ -1,3 +1,3 @@
|
||||
{{include main/header_admin}}
|
||||
@include(main/header_admin)
|
||||
|
||||
{{include main/footer}}
|
||||
@include(main/footer)
|
28
views/admin/log.html
Normal file
28
views/admin/log.html
Normal file
@ -0,0 +1,28 @@
|
||||
@include(main/header_admin)
|
||||
@if(log)
|
||||
<h1>last {{ log.length }} entries:</h1>
|
||||
<div class="logwrap">
|
||||
@each(log as line)
|
||||
<p>{{ line }}</p>
|
||||
@endeach
|
||||
</div>
|
||||
<style>
|
||||
div.logwrap {
|
||||
height: 600px;
|
||||
overflow-y: scroll;
|
||||
text-align: left;
|
||||
direction: rtl;
|
||||
}
|
||||
div.logwrap > p {
|
||||
direction: ltr;
|
||||
line-height: 0;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
(() => {
|
||||
const d = document.querySelector("div.logwrap");
|
||||
d.scrollTop = d.scrollHeight;
|
||||
})();
|
||||
</script>
|
||||
@endif
|
||||
@include(main/footer)
|
27
views/admin/search.html
Normal file
27
views/admin/search.html
Normal file
@ -0,0 +1,27 @@
|
||||
@include(main/header_admin)
|
||||
<form action="/admin/test" style="margin-top: 15px;">
|
||||
<input type="text" name="tag" /><button type="submit">search</button>
|
||||
</form>
|
||||
<hr />
|
||||
@if(result)
|
||||
<h1>{{ result.length }} f0cks given</h1>
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td style="text-align: center;">Thumbnail</td>
|
||||
<td style="text-align: center;">ID</td>
|
||||
<td style="text-align: center;">Tag</td>
|
||||
<td style="text-align: center;">Username</td>
|
||||
<td style="text-align: center;">Score</td>
|
||||
</tr>
|
||||
@each(result as line)
|
||||
<tr>
|
||||
<td style="width: 128px;"><a href="/{{ line.id }}" target="_blank"><img src="/t/{{ line.id }}.png" /></a></td>
|
||||
<td style="text-align: center;"><a href="/{{ line.id }}" target="_blank">{{ line.id }}</a></td>
|
||||
<td style="text-align: center;"><a href="/admin/test?tag={{ line.tag.replace(/\s/g, "+") }}">{{ line.tag }}</a></td>
|
||||
<td style="text-align: center;">{{ line.username }}</td>
|
||||
<td style="text-align: center;">{{ line.score }}</td>
|
||||
</tr>
|
||||
@endeach
|
||||
</table>
|
||||
@endif
|
||||
@include(main/footer)
|
24
views/admin/sessions.html
Normal file
24
views/admin/sessions.html
Normal file
@ -0,0 +1,24 @@
|
||||
@include(main/header_admin)
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>userid</td>
|
||||
<td>user</td>
|
||||
<td>browser</td>
|
||||
<td>created_at</td>
|
||||
<td>last_used</td>
|
||||
<td>last_action</td>
|
||||
</tr>
|
||||
@each(sessions as session)
|
||||
<tr>
|
||||
<td>{{ session.id }}</td>
|
||||
<td>{{ session.user_id }}</td>
|
||||
<td>{{ session.user }}</td>
|
||||
<td>{{ session.browser }}</td>
|
||||
<td>{{ new Date(session.created_at * 1e3).toLocaleString("de-DE") }}</td>
|
||||
<td>{{ new Date(session.last_used * 1e3).toLocaleString("de-DE") }}</td>
|
||||
<td>{{ session.last_action }}</td>
|
||||
</tr>
|
||||
@endeach
|
||||
</table>
|
||||
@include(main/footer)
|
@ -1,27 +0,0 @@
|
||||
{{include main/header_admin}}
|
||||
<form action="/admin/test" style="margin-top: 15px;">
|
||||
<input type="text" name="tag" /><button type="submit">search</button>
|
||||
</form>
|
||||
<hr />
|
||||
{{if result}}
|
||||
<h1>{{=result.length}} f0cks given</h1>
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td style="text-align: center;">Thumbnail</td>
|
||||
<td style="text-align: center;">ID</td>
|
||||
<td style="text-align: center;">Tag</td>
|
||||
<td style="text-align: center;">Username</td>
|
||||
<td style="text-align: center;">Score</td>
|
||||
</tr>
|
||||
{{each result as line}}
|
||||
<tr>
|
||||
<td style="width: 128px;"><a href="/{{=line.id}}" target="_blank"><img src="/t/{{=line.id}}.png" /></a></td>
|
||||
<td style="text-align: center;"><a href="/{{=line.id}}" target="_blank">{{=line.id}}</a></td>
|
||||
<td style="text-align: center;"><a href="/admin/test?tag={{=line.tag.replace(/\s/g, "+")}}">{{=line.tag}}</a></td>
|
||||
<td style="text-align: center;">{{=line.username}}</td>
|
||||
<td style="text-align: center;">{{=line.score}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/if}}
|
||||
{{include main/footer}}
|
@ -1,24 +0,0 @@
|
||||
{{include main/header_admin}}
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>userid</td>
|
||||
<td>user</td>
|
||||
<td>browser</td>
|
||||
<td>created_at</td>
|
||||
<td>last_used</td>
|
||||
<td>last_action</td>
|
||||
</tr>
|
||||
{{each sessions as session}}
|
||||
<tr>
|
||||
<td>{{=session.id}}</td>
|
||||
<td>{{=session.user_id}}</td>
|
||||
<td>{{=session.user}}</td>
|
||||
<td>{{=session.browser}}</td>
|
||||
<td>{{=new Date(session.created_at * 1e3).toLocaleString("de-DE")}}</td>
|
||||
<td>{{=new Date(session.last_used * 1e3).toLocaleString("de-DE")}}</td>
|
||||
<td>{{=session.last_action}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{include main/footer}}
|
@ -1,6 +0,0 @@
|
||||
{{include main/header}}
|
||||
<div class="contact">
|
||||
<a href="/48908"><img src="/s/img/loool.webp" style="margin-right: 5px; max-width: 100%; height: auto;" /></a>
|
||||
<p style="font-size: 7pt; margin-top: 0;">thanks to our turkish fellow lol@n0xy/#f0ck for this gif <3</p>
|
||||
</div>
|
||||
{{include main/footer}}
|
@ -1,9 +1,9 @@
|
||||
{{include main/header}}
|
||||
<div class="index-container">
|
||||
<div id="posts">
|
||||
{{each items as item}}
|
||||
<a href="/{{if typeof filter !== "undefined"}}{{=filter}}/{{/if}}{{=item.id}}" data-mime="{{=item.mime}}" style="background-image: url('/t/{{=item.id}}.png')"></a>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{include main/footer}}
|
||||
@include(main/header)
|
||||
<div class="index-container">
|
||||
<div id="posts">
|
||||
@each(items as item)
|
||||
<a href="/@if(typeof filter !== "undefined"){{ filter }}/@endif{{ item.id }}" data-mime="{{ item.mime }}" data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}" style="background-image: url('/t/{{ item.id }}.png')"><p></p></a>
|
||||
@endeach
|
||||
</div>
|
||||
</div>
|
||||
@include(main/footer)
|
||||
|
156
views/item.html
156
views/item.html
@ -1,78 +1,78 @@
|
||||
{{include main/header}}
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div class="next-post">
|
||||
{{if pagination.prev}}
|
||||
<div class="arrow-next">
|
||||
<a id="next" href="{{=pagination.link}}{{=pagination.prev}}"></a>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="arrow-next">
|
||||
<a id="next" href="#" style="color: #ccc !important;"></a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="media-object">
|
||||
<div hidden class="f0ck95"><img src="/s/img/f0ck95/computer.png" alt="f0ck95" loading="lazy" /><a href="/random">Random</a></div>
|
||||
{{if item.mime.startsWith("video")}}
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<video id="my-video" class="embed-responsive-item" width="640" height="360" src="{{=item.dest}}"
|
||||
preload="auto" autoplay controls loop playsinline></video>
|
||||
</div>
|
||||
{{elseif item.mime.startsWith("audio")}}
|
||||
<div class="embed-responsive embed-responsive-16by9" style="background: url('{{if item.coverart}}{{=item.coverart}}{{else}}/s/img/200.gif{{/if}}') no-repeat center / contain black;">
|
||||
<audio id="my-video" class="embed-responsive-item" autoplay controls loop src="{{=item.dest}}" data-setup="{}" poster="{{if item.coverart}}{{=item.coverart}}{{else}}/s/img/200.gif{{/if}}" type="{{=item.mime}}"></audio>
|
||||
</div>
|
||||
{{elseif item.mime.startsWith("image")}}
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<div class="embed-responsive-image" id="image-scroll">
|
||||
<a href="{{=item.dest}}" id="elfe" target="_blank"><img id="f0ck-image" class="img-fluid" src="{{=item.dest}}" loading="lazy" decoding="async"/></a>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<h1>404 - Not f0cked</h1>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="previous-post">
|
||||
{{if pagination.next}}
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="{{=pagination.link}}{{=pagination.next}}"></a>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="#" style="color: #ccc !important;"></a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="metadata">
|
||||
<span class="badge badge-dark">
|
||||
<a href="/{{=item.id}}" style="--hover-image: url('/t/{{=item.id}}.png');" class="id-link">{{=item.id}}</a>{{if session}} (<span id="a_username">{{=user.name}}</span>){{/if}}
|
||||
</span>
|
||||
<span class="badge badge-dark">{{=user.network}} / {{=user.channel}}</span>
|
||||
<span class="badge badge-dark image-source"><a class="post_source" title="{{=item.src.long}}" href="{{=item.src.long}}" target="_blank">{{=item.src.short}}</a></span>
|
||||
<span class="badge badge-dark"><a class="dest-link" href="{{=item.dest}}" target="_blank">{{=item.mime}}</a> / {{=item.size}}</span>
|
||||
<span class="badge badge-dark"><time class="timeago" title="{{=item.timestamp}}" datetime="{{=item.timestamp}}">{{=item.timestamp}}</time></span>
|
||||
<span class="badge badge-dark">
|
||||
{{if session}}
|
||||
<a href="#" id="a_delete">delete</a>
|
||||
{{else}}
|
||||
{{=lul}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="badge badge-dark" id="tags">
|
||||
{{if typeof item.tags !== "undefined"}}
|
||||
{{each item.tags as tag}}
|
||||
<span {{if session}}title="{{=tag.prefix}}"{{/if}} class="badge {{if tag.tag[0] == "&"}}badge-greentext{{/if}} badge-{{=(tag.tag === "nsfw" ? "danger" : tag.tag === "sfw" ? "success" : "light")}} mr-2">
|
||||
{{if session}}<a href="/admin/test?tag={{=tag.tag.replace(/\s/g, "+")}}" target="_blank" style="color: inherit !important;">{{/if}}{{=tag.tag}}{{if session}}</a> <a href="#">×</a>{{/if}}
|
||||
</span>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{if session}}
|
||||
<a href="#" id="a_addtag">add tag</a>
|
||||
- <a href="#" id="a_toggle">toggle</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{include main/footer}}
|
||||
@include(main/header)
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div class="next-post">
|
||||
@if(pagination.prev)
|
||||
<div class="arrow-next">
|
||||
<a id="next" href="{{ pagination.link }}{{ pagination.prev }}"></a>
|
||||
</div>
|
||||
@else
|
||||
<div class="arrow-next">
|
||||
<a id="next" href="#" style="color: #ccc !important;"></a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="media-object">
|
||||
<div hidden class="f0ck95"><img src="/s/img/f0ck95/computer.png" alt="f0ck95" loading="lazy" /><a href="/random">Random</a></div>
|
||||
@if(item.mime.startsWith("video"))
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<video id="my-video" class="embed-responsive-item" width="640" height="360" src="{{ item.dest }}"
|
||||
preload="auto" autoplay controls loop playsinline></video>
|
||||
</div>
|
||||
@elseif(item.mime.startsWith("audio"))
|
||||
<div class="embed-responsive embed-responsive-16by9" style="background: url('@if(item.coverart)//f0ck.me{{ item.coverart }}@else/s/img/200.gif@endif') no-repeat center / contain black;"></div>
|
||||
<audio id="my-video" class="embed-responsive-item" autoplay controls loop src="{{ item.dest }}" data-setup="{}" poster="@if(item.coverart){{ item.coverart }}@else/s/img/200.gif@endif" type="{{ item.mime }}"></audio>
|
||||
</div>
|
||||
@elseif(item.mime.startsWith("image"))
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<div class="embed-responsive-image" id="image-scroll">
|
||||
<a href="{{ item.dest }}" id="elfe" target="_blank"><img id="f0ck-image" class="img-fluid" src="{{ item.dest }}" loading="lazy" decoding="async"/></a>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<h1>404 - Not f0cked</h1>
|
||||
@endif
|
||||
</div>
|
||||
<div class="previous-post">
|
||||
@if(pagination.next)
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="{{ pagination.link }}{{ pagination.next }}"></a>
|
||||
</div>
|
||||
@else
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="#" style="color: #ccc !important;"></a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="metadata">
|
||||
<span class="badge badge-dark">
|
||||
<a href="/{{ item.id }}" style="--hover-image: url('/t/{{ item.id }}.png');" class="id-link">{{ item.id }}</a>@if(session) (<span id="a_username">{{ user.name }}</span>)@endif
|
||||
</span>
|
||||
<span class="badge badge-dark">{{ user.network }} / {{ user.channel }}</span>
|
||||
<span class="badge badge-dark image-source"><a class="post_source" title="{{ item.src.long }}" href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a></span>
|
||||
<span class="badge badge-dark"><a class="dest-link" href="{{ item.dest }}" target="_blank">{{ item.mime }}</a> / {{ item.size }}</span>
|
||||
<span class="badge badge-dark"><time class="timeago" title="{{ item.timestamp }}" datetime="{{ item.timestamp }}">{{ item.timestamp }}</time></span>
|
||||
<span class="badge badge-dark">
|
||||
@if(session)
|
||||
<a href="#" id="a_delete">delete</a>
|
||||
@else
|
||||
{{ lul }}
|
||||
@endif
|
||||
</span>
|
||||
<span class="badge badge-dark" id="tags">
|
||||
@if(typeof item.tags !== "undefined")
|
||||
@each(item.tags as tag)
|
||||
<span @if(session)title="{{ tag.prefix }}"@endif class="badge @if(tag.tag[0] == "&")badge-greentext@endif badge-{{ (tag.tag === "nsfw" ? "danger" : tag.tag === "sfw" ? "success" : "light") }} mr-2">
|
||||
@if(session)<a href="/admin/test?tag={{ tag.tag.replace(/\s/g, "%20") }}" target="_blank" style="color: inherit !important;">@endif{{ tag.tag }}@if(session)</a> <a href="#">×</a>@endif
|
||||
</span>
|
||||
@endeach
|
||||
@endif
|
||||
@if(session)
|
||||
<a href="#" id="a_addtag">add tag</a>
|
||||
- <a href="#" id="a_toggle">toggle</a>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@include(main/footer)
|
@ -1,5 +1,5 @@
|
||||
<!doctype f0ck>
|
||||
<html theme="{{if typeof theme !== "undefined" }}{{=theme}}{{/if}}">
|
||||
<html theme="@if(typeof theme !== "undefined"){{ theme }}@endif">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script async src="/s/js/theme.js"></script>
|
||||
<script src="/s/js/v0ck.js"></script>
|
||||
<script src="/s/js/f0ck.js"></script>
|
||||
{{if session}}<script src="/s/js/admin.js?v=3"></script>{{/if}}
|
||||
</body>
|
||||
</html>
|
||||
@if(session)<!--<div style="position: fixed; bottom: 0; z-index: 998; background-color: var(--bg); width: 100%; height: 29px; border-top: 1px solid var(--accent)">{{ JSON.stringify(session) }}</div>-->@endif
|
||||
<script async src="/s/js/theme.js"></script>
|
||||
<script src="/s/js/v0ck.js"></script>
|
||||
<script src="/s/js/f0ck.js"></script>
|
||||
@if(session)<script src="/s/js/admin.js"></script>@endif
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,40 +0,0 @@
|
||||
<script>
|
||||
var userMenuDiv = document.getElementById("userMenu");
|
||||
var userMenu = document.getElementById("userButton");
|
||||
var navMenuDiv = document.getElementById("nav-content");
|
||||
var navMenu = document.getElementById("nav-toggle");
|
||||
document.onclick = check;
|
||||
function check(e) {
|
||||
var target = (e && e.target) || (event && event.srcElement);
|
||||
if(!checkParent(target, userMenuDiv)) {
|
||||
if(checkParent(target, userMenu)) {
|
||||
if(userMenuDiv.classList.contains("invisible"))
|
||||
userMenuDiv.classList.remove("invisible");
|
||||
else
|
||||
userMenuDiv.classList.add("invisible");
|
||||
}
|
||||
else
|
||||
userMenuDiv.classList.add("invisible");
|
||||
}
|
||||
if(!checkParent(target, navMenuDiv)) {
|
||||
if(checkParent(target, navMenu)) {
|
||||
if(navMenuDiv.classList.contains("hidden"))
|
||||
navMenuDiv.classList.remove("hidden");
|
||||
else
|
||||
navMenuDiv.classList.add("hidden");
|
||||
}
|
||||
else
|
||||
navMenuDiv.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
function checkParent(t, elm) {
|
||||
while(t.parentNode) {
|
||||
if(t == elm)
|
||||
return true;
|
||||
t = t.parentNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,21 +1,11 @@
|
||||
<!cocktype big f0ck>
|
||||
<html lang="en" theme="{{if typeof theme !== "undefined" }}{{=theme}}{{/if}}">
|
||||
<head>
|
||||
<title>{{if data.title}}{{=data.title}}{{else}}f0ck!{{/if}}</title>
|
||||
<link rel="icon" type="image/gif" href="/s/img/favicon.gif" />
|
||||
<link rel="stylesheet" href="/s/css/f0ck.css">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
{{if data.item}}
|
||||
<meta property="og:site_name" content="f0ck.me" />
|
||||
{{if item.tags}}
|
||||
<meta property="og:description" content="{{each item.tags as tag}}{{=tag.tag.replace(/[\\$'"]/g, "\\$&")}},{{/each}}f0ck" />
|
||||
<meta name="description" content="{{each item.tags as tag}}{{=tag.tag.replace(/[\\$'"]/g, "\\$&")}},{{/each}}f0ck" />
|
||||
{{/if}}
|
||||
<meta property="og:image" content="{{=item.thumbnail}}" />
|
||||
{{else}}
|
||||
<meta name="description" content="f0ck.me is the place where internet purists gather to celebrate content of all kinds">
|
||||
{{/if}}
|
||||
</head>
|
||||
<body>
|
||||
{{include snippets/navbar}}
|
||||
<!cocktype big f0ck>
|
||||
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif">
|
||||
<head>
|
||||
<title>f0ck!</title>
|
||||
<link rel="icon" type="image/gif" href="/s/img/favicon.gif" />
|
||||
<link rel="stylesheet" href="/s/css/f0ck.css">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
@include(snippets/navbar)
|
||||
|
@ -1,18 +1,18 @@
|
||||
<!cocktype big f0ck>
|
||||
<html lang="en" theme="{{if typeof theme !== "undefined" }}{{=theme}}{{/if}}">
|
||||
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif">
|
||||
<head>
|
||||
<title>{{if data.title}}{{=data.title}}{{else}}f0ck!{{/if}}</title>
|
||||
<title>@if(typeof data !== "undefined" && data.title){{ data.title }}@elsef0ck!@endif</title>
|
||||
<link rel="icon" type="image/gif" href="/s/img/favicon.gif" />
|
||||
<link rel="stylesheet" href="/s/css/f0ck.css">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="f0ck.me is the place where internet purists gather to celebrate content of all kinds">
|
||||
{{if data.item}}
|
||||
@if(typeof data !== "undefined" && data.item)
|
||||
<meta property="og:site_name" content="f0ck.me" />
|
||||
<meta property="og:description"/>
|
||||
<meta name="Description"/>
|
||||
<meta property="og:image" content="{{=item.thumbnail}}" />
|
||||
{{/if}}
|
||||
<meta property="og:image" content="{{ item.thumbnail }}" />
|
||||
@endif
|
||||
</head>
|
||||
<body>
|
||||
{{include snippets/navbar_admin}}
|
||||
@include(snippets/navbar_admin)
|
@ -1,15 +1,15 @@
|
||||
{{if typeof pagination !== "undefined"}}
|
||||
<nav class="pagination">
|
||||
<a href="{{=pagination.link}}{{=pagination.start}}" class="page-item-1 btn start{{if !pagination.prev}} disabled{{/if}}">«</a>
|
||||
<a href="{{=pagination.link}}{{=pagination.prev}}" class="page-item-2 btn prev{{if !pagination.prev}} disabled{{/if}}">‹</a>
|
||||
{{each pagination.cheat as i}}
|
||||
{{if i == pagination.page}}
|
||||
<span class="btn disabled">{{=i}}</span>
|
||||
{{else}}
|
||||
<a href="{{=pagination.link}}{{=i}}" class="pagination-int-item btn">{{=i}}</a>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<a href="{{=pagination.link}}{{=pagination.next}}" class="page-item-3 btn next{{if !pagination.next}} disabled{{/if}}">›</a>
|
||||
<a href="{{=pagination.link}}{{=pagination.end}}" class="page-item-4 btn start{{if !pagination.next}} disabled{{/if}}">»</a>
|
||||
</nav>
|
||||
{{/if}}
|
||||
@if(typeof pagination !== "undefined")
|
||||
<nav class="pagination">
|
||||
<a href="{{ pagination.link }}{{ pagination.start }}" class="page-item-1 btn start@if(!pagination.prev) disabled@endif">«</a>
|
||||
<a href="{{ pagination.link }}{{ pagination.prev }}" class="page-item-2 btn prev@if(!pagination.prev) disabled@endif">‹</a>
|
||||
@each(pagination.cheat as i)
|
||||
@if(i == pagination.page)
|
||||
<span class="btn disabled">{{ i }}</span>
|
||||
@else
|
||||
<a href="{{ pagination.link }}{{ i }}" class="pagination-int-item btn">{{ i }}</a>
|
||||
@endif
|
||||
@endeach
|
||||
<a href="{{ pagination.link }}{{ pagination.next }}" class="page-item-3 btn next@if(!pagination.next) disabled@endif">›</a>
|
||||
<a href="{{ pagination.link }}{{ pagination.end }}" class="page-item-4 btn start@if(!pagination.next) disabled@endif">»</a>
|
||||
</nav>
|
||||
@endif
|
@ -1,52 +1,64 @@
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="/"><span class="f0ck" width="" height="">F0CK</span></a>
|
||||
<div class="navigation-links">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
{{if session}}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link" href="/admin" content="{{=session.user}}" data-toggle="dropdown">Admin</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/admin">adminpanel</a></li>
|
||||
<li><a href="/admin/sessions">sessions</a></li>
|
||||
<li><a href="/admin/test">search (wip)</a></li>
|
||||
<li><a href="/logout">logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{{else}}
|
||||
<a class="nav-link" href="/about"><span class="nav-link-identifier">About</span></a>
|
||||
{{/if}}
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="themes">
|
||||
<a class="nav-link" href="#" content="{{=theme}}" data-toggle="dropdown">Theme</a>
|
||||
<ul class="dropdown-menu">
|
||||
{{each themes as t}}
|
||||
<li><a href="/theme/{{=t}}">{{=t}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</li>
|
||||
<span class="placeholder"> </span>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link" href="#"{{if typeof filter !== "undefined"}} content="{{=filter}}" data-toggle="dropdown"{{/if}}">Filter{{if typeof filter === "undefined"}} ▼{{/if}}</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/">All</a></li>
|
||||
<li><a class="dropdown-item" href="/audio">Audio</a></li>
|
||||
<li><a class="dropdown-item" href="/video">Video</a></li>
|
||||
<li><a class="dropdown-item" href="/image">Image</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a id="random" class="nav-link" href="/random{{if typeof filter !== "undefined"}}/{{=filter}}{{/if}}">
|
||||
<span class="nav-link-identifier">Random</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse show" id="navbarSupportedContent">
|
||||
<div class="pagination-container-fluid">
|
||||
<div class="pagination-wrapper">
|
||||
{{include partials/pagination}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<a class="navbar-brand" href="/"><span class="f0ck" width="" height="">F0CK</span></a>
|
||||
<div class="navigation-links">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown">
|
||||
@if(session)
|
||||
<a class="nav-link" href="/admin" content="{{ session.user }}" data-toggle="dropdown">Admin</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/admin">adminpanel</a></li>
|
||||
<li><a href="/admin/sessions">sessions</a></li>
|
||||
<li><a href="/admin/test">search (wip)</a></li>
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/logout">logout</a></li>
|
||||
</ul>
|
||||
@else
|
||||
<a class="nav-link" href="/about" data-toggle="dropdown">About</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/login">login</a></li>
|
||||
</ul>
|
||||
@endif
|
||||
</li>
|
||||
<li class="nav-item dropdown" id="themes">
|
||||
<a class="nav-link ddcontent" href="#" content="{{ theme }}" data-toggle="dropdown">Theme</a>
|
||||
<ul class="dropdown-menu">
|
||||
@each(themes as t)
|
||||
<li><a href="/theme/{{ t }}">{{ t }}</a></li>
|
||||
@endeach
|
||||
</ul>
|
||||
</li>
|
||||
<span class="placeholder"> </span>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link ddcontent" href="#"@if(typeof filter !== "undefined") content="{{ filter }}" data-toggle="dropdown"@endif>Filter@if(typeof filter === "undefined") ▼@endif</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="/">All</a></li>
|
||||
<li><a class="dropdown-item" href="/audio">Audio</a></li>
|
||||
<li><a class="dropdown-item" href="/video">Video</a></li>
|
||||
<li><a class="dropdown-item" href="/image">Image</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@if(session)
|
||||
<li class="nav-item @if(session)dropdown@endif">
|
||||
<a class="nav-link ddcontent" href="#"@if(typeof session.mode !== "undefined") content="{{ modes[session.mode] ?? 'sfw' }}" data-toggle="dropdown"@endif>Mode</a>
|
||||
<ul class="dropdown-menu">
|
||||
@for(let i = 0; i < modes.length; i++)
|
||||
<li><a class="dropdown-item" href="/mode/{{ i }}">{{ modes[i] }}</a></li>
|
||||
@endfor
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
<li class="nav-item">
|
||||
<a id="random" class="nav-link" href="/random@if(typeof filter !== "undefined")/{{ filter }}@endif">
|
||||
<span class="nav-link-identifier">Random</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse show" id="navbarSupportedContent">
|
||||
<div class="pagination-container-fluid">
|
||||
<div class="pagination-wrapper">
|
||||
@include(partials/pagination)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -3,11 +3,11 @@
|
||||
<div class="navigation-links">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown" id="themes">
|
||||
<a class="nav-link" href="#" content="{{=theme}}" data-toggle="dropdown">Theme</a>
|
||||
<a class="nav-link" href="#" content="{{ theme }}" data-toggle="dropdown">Theme</a>
|
||||
<ul class="dropdown-menu">
|
||||
{{each themes as t}}
|
||||
<li><a href="/theme/{{=t}}">{{=t}}</a></li>
|
||||
{{/each}}
|
||||
@each(themes as t)
|
||||
<li><a href="/theme/{{ t }}">{{ t }}</a></li>
|
||||
@endeach
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@ -21,22 +21,22 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span class="nav-link-identifier">blah</span>
|
||||
<a class="nav-link" href="/admin/log">
|
||||
<span class="nav-link-identifier">Log</span>
|
||||
</a>
|
||||
</li>
|
||||
{{if typeof totals !== "undefined"}}
|
||||
@if(typeof totals !== "undefined")
|
||||
<li class="nav-item" style="width: 100%; text-align: center">
|
||||
total: {{=totals.total}} | tagged: {{=totals.tagged}} | untagged: {{=totals.untagged}}
|
||||
total: {{ totals.total }} | tagged: {{ totals.tagged }} | untagged: {{ totals.untagged }} | sfw: {{ totals.sfw }} | nsfw: {{ totals.nsfw }}
|
||||
</li>
|
||||
{{/if}}
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse show" id="navbarSupportedContent">
|
||||
<div class="pagination-container-fluid">
|
||||
<div class="pagination-wrapper">
|
||||
Henlo, {{=session.user}} <a href="/logout">Logout</a>
|
||||
{{include partials/pagination}}
|
||||
Henlo, {{ session.user }} <a href="/logout">Logout</a>
|
||||
@include(partials/pagination)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user