This commit is contained in:
Flummi
2020-04-02 04:35:28 +02:00
parent 5ff96cdf5e
commit d39deeb038
100 changed files with 34498 additions and 1100 deletions

View File

@ -1,44 +1,22 @@
import sql from "./sql";
import config from "../../config.json";
export let admins = [];
export const loadAdmins = async () => {
const db = await sql;
admins = [];
try {
const rows = await db.query("select id, prefix, account, level, network from user");
rows.forEach(row => admins.push({
id: row.id,
prefix: row.prefix,
account: row.account,
network: row.network,
level: row.level
}));
} catch(err) {
console.log("keine Admins vorhanden", err);
}
};
(async () => {
await loadAdmins();
})();
export const getLevel = (network, user) => {
export const getLevel = user => {
let ret = {
level: 0,
verified: false
};
if (typeof user !== "object")
return "user has to be an object!";
if (!user.account || !user.prefix)
if (!user.prefix)
return ret;
for(let admin of admins) {
if (admin.account === user.account.toLowerCase() && admin.network === network.toLowerCase()) {
ret = {
level: admin.level,
verified: user.prefix.toLowerCase() === admin.prefix
};
}
};
let admin;
if(admin = config.admins.filter(e => e.prefix === user.prefix)[0]) {
ret = {
level: admin.level,
verified: true
};
}
return ret;
};
};

View File

@ -1,10 +1,25 @@
import logger from "../log.mjs";
const versions = [
"AmIRC.1 (8 Bit) for Commodore Amiga 500",
"HexChat 0.72 [x86] / Windows 95c [500MHz]"
];
export default self => {
self.bot.on("ctcp:version", e => {
e.write(`notice ${e.user.nick} :\u0001VERSION ${versions[~~(Math.random() * versions.length)]}\u0001`);
});
};
export default async bot => {
return [{
name: "version",
listener: "ctcp:version",
f: e => {
logger.info(`${e.network} -> ${e.channel} -> ${e.user.nick}: ctcp:version ${e.message}`);
e.write(`notice ${e.user.nick} :\u0001VERSION ${versions[~~(Math.random() * versions.length)]}\u0001`);
}
}, {
name: "ping",
listener: "ctcp:ping",
f: e => {
logger.info(`${e.network} -> ${e.channel} -> ${e.user.nick}: ctcp:ping ${e.message}`);
e.write(`notice ${e.user.nick} :${e.message}`);
}
}];
};

12
src/inc/events/error.mjs Normal file
View File

@ -0,0 +1,12 @@
import logger from "../log.mjs";
export default async bot => {
return [{
name: "error",
listener: "error",
f: e => {
logger.error(e);
}
}];
};

View File

@ -1,6 +0,0 @@
import ctcp from "./ctcp";
import message from "./message";
export default [
ctcp, message
];

18
src/inc/events/info.mjs Normal file
View File

@ -0,0 +1,18 @@
import logger from "../log.mjs";
export default async bot => {
return [{
name: "info",
listener: "info",
f: e => {
logger.debug(e);
}
}, {
name: "debug",
listener: "debug",
f: e => {
logger.debug(e);
}
}];
};

View File

@ -1,44 +1,42 @@
import { getLevel } from "../admin";
import logger from "../log.mjs";
import { getLevel } from "../../inc/admin.mjs";
const parseArgs = msg => {
let args = msg.trim().split(" ");
let cmd = args.shift();
let args = msg.trim().split(" ")
, cmd = args.shift();
return {
cmd: cmd.replace(/^(\.|\/|\!)/, ""),
args: args
};
};
export default self => {
self.bot.on("message", e => {
for (var [name, trigger] of self._trigger.entries()) {
if (!trigger.call.exec(e.message))
continue;
if (!trigger.clients.includes(e.type))
continue;
export default async bot => {
/*let active = false;
if (e.type === "irc" && cfg.trigger[e.network + e.channel]) {
if (cfg.trigger[e.network + e.channel].val[trigger.name])
active = true;
}
else
active = trigger.active;*/
return [{
name: "message",
listener: "message",
f: e => {
logger.info(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${e.message}`);
//if (!active)
// continue;
const trigger = [...bot._trigger.entries()].filter(t =>
t[1].call.exec(e.message) &&
t[1].clients.includes(e.type) &&
t[1].active &&
t[1].level <= getLevel(e.network, e.user).level &&
!((e.self.set !== "all" && e.self.set !== t[1].set) && t[1].set !== "all")
);
if ((e.self.set !== "all" && e.self.set !== trigger.set) && trigger.set !== "all")
continue;
if (trigger.level > getLevel(e.network, e.user)) {
e.reply(`no permission, min level ${trigger.level} required`);
break;
}
e = Object.assign(e, parseArgs(e.message));
trigger.f(e);
trigger.forEach(async t => {
try {
await t[1].f({ ...e, ...parseArgs(e.message) });
console.log(`triggered > ${t[0]}`);
}
catch(error) {
e.reply(`${t[0]}: An error occured.`);
logger.error(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${error.toString ? error : JSON.stringify(error)}`);
}
});
}
console.info(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${e.message}`);
});
};
}];
};

View File

@ -1,35 +0,0 @@
import http from "http";
import https from "https";
import url from "url";
import querystring from "querystring";
const readdata = (res, mode, data = "") => new Promise((resolve, reject) => res
.setEncoding("utf8")
.on("data", chunk => data += chunk)
.on("end", () => {
switch(mode) {
case "text": resolve(data); break;
case "json": try { resolve(JSON.parse(data)); } catch(err) { reject("no json D:"); } break;
case "buffer": resolve(new Buffer.from(data)); break;
default: reject("lol no"); break;
}
}));
export default (a, options = {}, link = url.parse(a), body = "") => new Promise((resolve, reject) => {
options = {...{ hostname: link.hostname, path: link.path, method: "GET" }, ...options};
if(options.method === "POST") {
body = querystring.stringify(options.body);
delete options.body;
options.headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(body)
};
}
(link.protocol === "https:"?https:http).request(options, res => resolve({
body: res,
headers: res.headers,
text: () => readdata(res, "text"),
json: () => readdata(res, "json"),
buffer: () => readdata(res, "buffer")
})).on("error", err => reject(err)).end(body);
});

15
src/inc/log.mjs Normal file
View File

@ -0,0 +1,15 @@
import fs from "fs";
const logger = ["debug", "info", "error"];
const logfiles = logger.reduce((a, b) => ({...a, [b]: fs.createWriteStream(`./logs/${~~(Date.now() / 1000)}_${b}.log`)}), {});
export default new Proxy({}, {
get: (_, prop) => (msg, timestamp = new Date().toLocaleString()) =>
logger.includes(prop) &&
logfiles[prop].write(JSON.stringify({
level: prop,
message: msg,
timestamp: timestamp
}) + "\n") &&
console.log(timestamp, prop, msg)
});

View File

@ -1,4 +1,8 @@
class Router {
import { promises as fs } from "fs";
import path from "path";
export default new class Router {
#mimes;
constructor() {
this.routes = new Map();
};
@ -12,11 +16,32 @@ class Router {
post() {
this.route("POST", arguments);
};
async static({ dir = path.resolve() + "/public", route = /^\/public/ }) {
if(!this.#mimes) {
this.#mimes = new Map();
(await fs.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 {
return res.reply({
type: this.#mimes.get(req.url.path.split(".").pop()).toLowerCase(),
body: await fs.readFile(path.join(dir, req.url.path.replace(route, "")))
});
} catch {
return res.reply({
code: 404,
body: "404 - file not found"
});
}
});
};
};
const router = new Router();
export default router;
export const routes = router.routes;
Map.prototype.getRegex = function(path, method, tmp) {
return (!(tmp = [...this.entries()].filter(r => r[0].exec(path) && r[1].method.includes(method))[0])) ? false : tmp[1].f;
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].f;
};

View File

@ -1,16 +1,15 @@
import router from "../router";
import sql from "../sql";
import router from "../router.mjs";
import sql from "../sql.mjs";
import { parse } from "url";
import cfg from "../../../config.json";
import { mimes, queries } from "./inc/api";
import { mimes, queries } from "./inc/api.mjs";
router.get(/^\/api$/, (req, res) => {
router.get("/api", (req, res) => {
res.end("api lol");
});
router.get(/^\/api\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => {
const db = await sql.getConnection();
const args = [];
let q = queries.random.main;
@ -22,26 +21,24 @@ router.get(/^\/api\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, r
q += queries.random.where(mimes[req.url.split[2]] ? mimes[req.url.split[2]].map(mime => `mime = "${mime}"`).join(" or ") : null);
try {
const rows = await db.query(q, args);
const rows = await sql.query(q, args);
res
.writeHead(200, { 'Content-Type': 'application/json' })
.end(JSON.stringify(rows.length > 0 ? rows[0] : []), 'utf-8');
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(rows.length > 0 ? rows[0] : []), "utf-8");
} catch(err) {
res
.writeHead(500)
.end(JSON.stringify(err), 'utf-8');
.end(JSON.stringify(err), "utf-8");
}
db.end();
});
router.get(/^\/api\/p$/, async (req, res) => {
const db = await sql.getConnection();
router.get("/api/p", async (req, res) => {
let id = parseInt(req.url.qs.id) || 99999999;
const eps = Math.min(parseInt(req.url.qs.eps) || 100, 200);
let [ order, trend ] = req.url.qs.order === "asc" ? [ "asc", ">" ] : [ "desc", "<" ];
try {
const tmp = (await db.query("select min(id) as min, max(id) as max from items limit 1"))[0];
const tmp = (await sql.query("select min(id) as min, max(id) as max from items limit 1"))[0];
if((id - 1 + eps) > tmp.max) {
id = tmp.max;
[ order, trend ] = [ "desc", "<=" ];
@ -51,7 +48,7 @@ router.get(/^\/api\/p$/, async (req, res) => {
[ order, trend ] = [ "asc", ">=" ];
}
const rows = await db.query(queries.p(trend, order), [ id, eps ]);
const rows = await sql.query(queries.p(trend, order), [ id, eps ]);
const items = {
items: rows,
first: rows[0].id,
@ -60,21 +57,33 @@ router.get(/^\/api\/p$/, async (req, res) => {
oldest: tmp.min
};
res
.writeHead(200, { 'Content-Type': 'application/json' })
.end(JSON.stringify(items), 'utf-8');
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(items), "utf-8");
} catch(err) {
console.error(err);
res
.writeHead(500)
.end(JSON.stringify(err), 'utf-8');
.end(JSON.stringify(err), "utf-8");
}
db.end();
});
router.get(/^\/api\/p\/([0-9]+)/, async (req, res) => { // legacy
let eps = 100;
let id = +req.url.split[2];
const query = await sql.query("select * from items where id < ? order by id desc limit ?", [ id, eps ]);
const items = {
items: query,
last: query[query.length - 1].id
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(items), "utf-8");
});
router.get(/^\/api\/item\/[0-9]+$/, async (req, res) => {
const db = await sql.getConnection();
try {
const rows = await db.query(queries.item, Array(3).fill(req.url.split[2]));
const rows = await sql.query(queries.item, Array(3).fill(req.url.split[2]));
const data = rows[0].length > 0 ? {
...rows[0][0], ...{
thumb: `${cfg.main.url}/t/${rows[0][0].id}.png`,
@ -86,29 +95,27 @@ router.get(/^\/api\/item\/[0-9]+$/, async (req, res) => {
error: true
};
res
.writeHead(200, { 'Content-Type': 'application/json' })
.end(JSON.stringify(data), 'utf-8');
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(data), "utf-8");
} catch(err) {
res
.writeHead(500)
.end(JSON.stringify(err), 'utf-8');
.end(JSON.stringify(err), "utf-8");
}
db.end();
});
router.get(/^\/api\/user\/.*(\/[0-9]+)?$/, async (req, res) => { // auf qs umstellen
const db = await sql.getConnection();
const user = req.url.split[2];
const eps = Math.min(req.url.split[3] || 50, 50);
try {
const rows = await db.query(queries.user, [ user, eps ]);
const rows = await sql.query(queries.user, [ user, eps ]);
res
.writeHead(200, { 'Content-Type': 'application/json' })
.end(JSON.stringify(rows.length > 0 ? rows : []), 'utf-8');
.writeHead(200, { "Content-Type": "application/json" })
.end(JSON.stringify(rows.length > 0 ? rows : []), "utf-8");
} catch(err) {
res
.writeHead(500)
.end(JSON.stringify(err), 'utf-8');
.end(JSON.stringify(err), "utf-8");
}
db.end();
});

View File

@ -1,10 +1,104 @@
import router from "../router";
import router from "../router.mjs";
import cfg from "../../../config.json";
import fs from "fs";
import sql from "../sql.mjs";
import swig from "swig";
import url from "url";
const tpl = fs.readFileSync("./views/index.html", "utf-8");
const templates = {
contact: fs.readFileSync("./views/contact.html", "utf-8"),
help: fs.readFileSync("./views/help.html", "utf-8"),
how: fs.readFileSync("./views/how.html", "utf-8"),
index: fs.readFileSync("./views/index.html", "utf-8"),
item: fs.readFileSync("./views/item.html", "utf-8")
};
router.get(/^\/(page\/[0-9]+)?$/, async (req, res) => {
res
.writeHead(200, { 'Content-Type': 'text/html' })
.end(tpl);
router.get("/", async (req, res) => {
const query = await sql.query("select id, mime from items order by id desc limit 300");
const data = {
items: query,
last: query[0].id
};
res.reply({
body: swig.compile(templates.index)(data)
});
});
router.get(/^\/([0-9]+)$/, async (req, res) => {
const q = "select * from items where id = ? limit 1; " // get item
+ "select id from items where id = (select min(id) from items where id > ?); " // get previous item
+ "select id from items where id = (select max(id) from items where id < ?)"; // get next item
const query = await sql.query(q, [req.url.split[0], req.url.split[0], req.url.split[0]]);
const data = {
id: '',
username: '',
item: '',
src: '',
dest: '',
mime: '',
size: '',
userchannel: '',
usernetwork: '',
thumb: null,
thumbnail: null,
next: null,
prev: null
};
if(query[0][0]) {
const e = query[0][0];
switch(e.mime) {
case "image/png":
case "image/jpeg":
case "image/gif":
data.item = "image";
break;
case "video/webm":
case "video/mp4":
case "video/quicktime":
data.item = "video";
break;
case "audio/mpeg":
case "audio/ogg":
case "audio/flac":
case "audio/x-flac":
data.item = "audio";
break;
}
data.id = e.id;
data.username = e.username;
data.srcurl = e.src;
data.src = url.parse(e.src).hostname;
data.thumb = `${cfg.websrv.paths.thumbnails}/${e.id}.png`;
data.dest = `${cfg.websrv.paths.images}/${e.dest}`;
data.mime = e.mime;
data.size = e.size;//lib.formatSize(e.size);
data.userchannel = e.userchannel;
data.usernetwork = e.usernetwork;
data.timestamp = new Date(e.stamp * 1000).toISOString();
if(query[1].length)
data.next = query[1][0].id;
if(query[2].length)
data.prev = query[2][0].id;
}
res.reply({
body: swig.compile(templates.item)(data)
});
});
router.get(/^\/(contact|help|how)$/, (req, res) => {
res.reply({
body: templates[req.url.split[0]]
});
});
router.get("/random", async (req, res) => {
res
.writeHead(301, {
"Cache-Control": "no-cache, public",
"Location": "/" + (await sql.query("select id from items order by rand() limit 1"))[0].id
})
.end();
});

17
src/inc/routes/static.mjs Normal file
View File

@ -0,0 +1,17 @@
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\//
});

View File

@ -1,35 +1,51 @@
import { admins } from "../admin";
import { getLevel } from "../admin.mjs";
import fetch from "flumm-fetch-cookies";
import vm from "vm";
const maxoutput = 1000;
let maxoutput = 750;
let context = vm.createContext({
e: null,
bot: null,
admins: null,
fetch: fetch
});
export default bot => bot._trigger.set("sandbox_debug", new bot.trigger({
call: /^\!debug (.*)/i,
level: 100,
active: true,
f: e => {
const args = e.message.trim().substring(7);
if(args === "true" || args === "false")
return e.self.debug = !e.self.debug;
try {
context.admins = admins;
context.e = e;
context.bot = bot;
let output = vm.runInContext(args, vm.createContext(context));
if (typeof output !== undefined && output) {
output = JSON.stringify(output);
return r.reply(output.length > maxoutput ? `holy fuck, Ausgabe wäre viel zu lang! (${output.length} Zeichen :DDDDDD)` : output);
}
else
e.reply("false lol");
export default async bot => {
return [{
name: "level",
call: /^!level (.*)/i,
active: true,
f: e => {
const user = e.message.trim().substring(7);
e.reply( JSON.stringify( getLevel( e.self.user.get(user) || {} ) ) );
}
catch (err) {
e.reply(err.message);
}, {
name: "sandbox_debug",
call: /^\!debug (.*)/i,
active: true,
level: 100,
f: e => {
const args = e.message.trim().substring(7);
try {
context.e = e;
context.bot = bot;
context.level = getLevel;
let output = vm.runInContext(args, vm.createContext(context));
if (typeof output !== undefined && output) {
output = JSON.stringify(output);
if (output.length > maxoutput)
return e.reply(`holy fuck, Ausgabe wäre viel zu lang! (${output.length} Zeichen :DDDDDD)`);
else
e.reply(output);
}
else
e.reply("false lol");
}
catch (err) {
e.reply(err.message);
}
}
}
}));
}];
};

View File

@ -1,3 +0,0 @@
import debug from "./debug";
export default [ debug ];

View File

@ -1,28 +1,51 @@
import cfg from "../config.json";
import { cuffeo } from "cuffeo";
import cuffeo from "cuffeo";
import { promises as fs } from "fs";
import triggers from "./inc/trigger";
import events from "./inc/events";
import "./websrv";
import "./websrv.mjs";
(async () => {
// Chatbots
/*const self = {
const self = {
_trigger: new Map(),
trigger: function trigger(args) {
this.call = args.call;
this.help = args.help || false;
this.level = args.level || 0;
this.active = args.hasOwnProperty("active") ? args.active : true;
this.set = args.set || "all"; // uwe, nxy, f0ck, all
this.clients = args.clients || ["irc", "tg"];
this.clients = args.clients || [ "irc", "tg", "slack" ];
this.f = args.f;
},
bot: new cuffeo(cfg.clients)
bot: await new cuffeo(cfg.clients)
};
triggers.forEach(mod => mod(self));
events.forEach(event => event(self));*/
//
console.time("loading");
const modules = {
events: (await fs.readdir("./src/inc/events")).filter(f => f.endsWith(".mjs")),
trigger: (await fs.readdir("./src/inc/trigger")).filter(f => f.endsWith(".mjs"))
};
console.timeLog("loading", "directories");
const blah = (await Promise.all(Object.entries(modules).map(async ([dir, mods]) => ({
[dir]: (await Promise.all(mods.map(async mod => {
const res = await Promise.race([
(await import(`./inc/${dir}/${mod}`)).default(self),
new Promise((_, rej) => setTimeout(() => rej(false), timeout))
]);
console.timeLog("loading", `${dir}/${mod}`);
return res;
}))).flat(2)
})))).reduce((a, b) => ({...a, ...b}));
blah.events.forEach(event => {
console.timeLog("loading", `registering event > ${event.name}`);
self.bot.on(event.listener, event.f);
});
blah.trigger.forEach(trigger => {
console.timeLog("loading", `registering trigger > ${trigger.name}`);
self._trigger.set(trigger.name, new self.trigger(trigger));
});
console.timeEnd("loading");
})();

View File

@ -2,19 +2,30 @@ import http from "http";
import url from "url";
import querystring from "querystring";
import cfg from "../config.json";
import router from "./inc/router.mjs";
// routes
import "./inc/routes/index";
import "./inc/routes/api";
import "./inc/routes/index.mjs";
import "./inc/routes/api.mjs";
import "./inc/routes/static.mjs";
import { routes } from "./inc/router";
http.createServer((req, res, r) => {
http.createServer(async (req, res, r) => {
req.url = url.parse(req.url.replace(/(?!^.)(\/+)?$/, ''));
req.url.split = req.url.pathname.split("/").slice(1);
req.url.qs = querystring.parse(req.url.query);
console.log(`[${(new Date()).toLocaleTimeString()}] ${req.method} ${req.url.pathname}`);
req.post = new Promise((resolve, _, data = "") => req
.on("data", d => void (data += d))
.on("end", () => void resolve(Object.fromEntries(Object.entries(querystring.parse(data)).map(([k, v]) => [k, decodeURIComponent(v)])))));
!(r = routes.getRegex(req.url.pathname, req.method)) ? res.writeHead(404).end(`404 - ${req.url.pathname}`) : r(req, res);
}).listen(cfg.websrv.port, () => console.log(`f0ck is listening on port ${cfg.websrv.port}.`));
res.reply = ({
code = 200,
type = "text/html",
body
}) => res.writeHead(code, { "Content-Type": `${type}; charset=utf-8` }).end(body);
!(r = router.routes.getRoute(req.url.pathname, req.method)) ? res.writeHead(404).end(`404 - ${req.url.pathname}`) : await r(req, res);
console.log(`[${(new Date()).toLocaleTimeString()}] ${res.statusCode} ${req.method}\t${req.url.pathname}`);
}).listen(cfg.websrv.port, () => setTimeout(() => {
console.log(`f0ck is listening on port ${cfg.websrv.port}.`);
}, 500));