Denim-Bot/src/app.ts

204 lines
7.7 KiB
TypeScript

import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, ComponentType, GatewayIntentBits, Message, Partials } from "discord.js";
import { readdirSync } from "fs";
import { CUser, EventSOn, KomandNaExport, Komand, ListenerFunkce, Modul, SRecord, SuperListenerFunkce, RunFunkce, CClient, HelpServer } from "./utils/types";
import { adminLog, formatCas, oddiakritikovat, log } from "./utils/utils.js";
import levenshtein from "js-levenshtein";
import { emouty } from "./utils/emotes";
import { existsSync } from "fs";
if (!existsSync("config.json")) throw new Error("config.json neexistuje");
process.env = { ...process.env, ...require("../config.json") };
const ints = GatewayIntentBits;
const client = new Client({
partials: [Partials.Channel],
intents: [ints.Guilds, ints.GuildVoiceStates, ints.GuildPresences, ints.GuildMessages, ints.DirectMessages, ints.MessageContent]
}) as CClient;
const prefix = process.env.prefix || "more";
const modulFolder = `${__dirname}/modules/`;
const eventy: SRecord<ListenerFunkce[]> = {};
const superEventy: SRecord<SuperListenerFunkce[]> = {};
const kuldan_log: SRecord<SRecord<number>> = {};
const komandyNaPoslani: KomandNaExport[] = [];
const helpServer: HelpServer = require("./utils/helpServer");
client.komandy = {};
client.aliasy = {};
function getFirstArg(fn: RunFunkce | string) {
if (typeof fn != "function") return;
return fn.toString()
.match(/(?:function\s.*?)?\(([^)]*)\)|\w+ =>/)![1]
?.split(",")[1]
?.trim();
}
const runEvent = (name: string, args: unknown[]) => {
for (const listener of superEventy[name] || []) {
if (!listener) continue; // [after] pozice v superEventy arrayi se dají nastavovat a teoreticky může nastat mezera
try {
if (listener(...args)) return true; // if listener returns true, it means, we shouldn't continue with execustion
} catch (e) {
if (process.env.dieOnError) throw e;
log("error pri spusteni super listeneru", e as Error);
adminLog(client, "error pri supereventu");
}
}
eventy[name]?.forEach(listener => {
try {
listener(...args);
} catch (e) {
if (process.env.dieOnError) throw e;
log("error pri spousteni eventu", e as Error);
adminLog(client, "error pri eventu");
}
});
};
// Loading of modules
readdirSync(modulFolder).forEach(soubor => {
if (!soubor.endsWith(".js")) return;
const modul: Modul = require(`${modulFolder}${soubor}`);
log(`Loaded: ${modulFolder}${soubor}`);
modul.client = client;
Object.keys(modul).forEach(name => {
const prefix = /^(?<s>super_)?on_(?<h>.+)/.exec(name);
if (prefix) {
const groups = prefix.groups!;
const ev = groups.s ? superEventy : eventy;
if (!ev[groups.h]) {
ev[groups.h] = [];
if (!["messageCreate", "userPresenceUpdate"].includes(groups.h)) client.on(groups.h, (...args) => void runEvent(groups.h, args));
}
const n = modul[name as EventSOn]!;
if (typeof n == "object") {
const prev = ev[groups.h][n.pos];
ev[groups.h][n.pos] = n.fun;
if (prev) ev[groups.h].push(prev);
}
else ev[groups.h].push(n);
} else if (name === "more_komandy") {
const n = modul[name]!;
Object.keys(n).forEach(cmdName => {
const value = n[cmdName];
const toCoExportuju: KomandNaExport = { name: cmdName };
let hide = false;
if (typeof value !== "object") {
client.komandy[cmdName] = { run: value };
toCoExportuju.arg = getFirstArg(value);
} else {
client.komandy[cmdName] = { run: value.run, cd: value.cd, DMUnsafe: value.DMUnsafe };
Object.assign(toCoExportuju, { als: value.als, arg: value.arg ?? getFirstArg(value.run) });
hide = !!value.hidden;
value.als?.forEach(al => client.aliasy[al] = cmdName);
}
if (!hide) komandyNaPoslani.push(toCoExportuju);
});
}
});
});
helpServer.komandy = komandyNaPoslani;
const maKuldan = (id: string, komand: string, kuldan_komandu: number) => {
if (!kuldan_log[komand]) kuldan_log[komand] = {};
const cas_ted = Date.now() / 1000;
const rozdil = cas_ted - kuldan_log[komand][id];
if (rozdil < kuldan_komandu) return kuldan_komandu - rozdil;
kuldan_log[komand][id] = cas_ted;
return 0;
};
async function runKomand(mes: Message, cmd: Komand, cmdName: string, arg: string) {
if (cmd.cd) {
const zbyva = Math.round(maKuldan(mes.author.id, cmdName, cmd.cd));
if (zbyva) return void mes.channel.send(`si kkt vole maz kuldan jeste ${formatCas(zbyva)}`);
}
const akce = cmd.run;
if (typeof akce == "string") return void mes.channel.send(akce);
try {
const result = await akce(mes, arg);
if (result) mes.channel.send(result);
} catch (e) {
if (process.env.dieOnError) throw e;
log("error pri spousteni akce", e as Error);
const admin = process.env.adminID;
mes.channel.send(`pri spousteni thohoto komandu nastala chyba ${admin ? `<@${admin}> uz?` : ""}`);
}
}
client.on("messageCreate", async mes => {
if (process.env.ignoreMess) return;
const [mesPrefix, komandSDiakritikou, ...args] = mes.content.split(" ");
if (oddiakritikovat(mesPrefix.toLowerCase()) != prefix) return void runEvent("messageCreate", [mes]);
if (!komandSDiakritikou) {
if (!runEvent("messageCreate", [mes])) mes.channel.send("coe voe");
return;
}
const celArgs = args.join(" ");
const komandBez = oddiakritikovat(komandSDiakritikou).toLowerCase();
const cmdName = client.aliasy[komandBez] ?? komandBez;
if (runEvent("messageCreate", [mes, cmdName])) return;
const komand = client.komandy[cmdName];
if (mes.channel.type == ChannelType.DM && komand?.DMUnsafe) return void mes.channel.send("tuten komand bohuzel v sz nefunkuje");
if (komand) return void runKomand(mes, komand, cmdName, celArgs);
// neměl jsi na mysli?
const slova: string[] = [];
[...Object.keys(client.komandy), ...Object.keys(client.aliasy)].forEach(cmd => {
const distance = levenshtein(cmd, cmdName);
if (distance <= Math.ceil(cmdName.length * 0.3)) slova.push(cmd);
});
if (!slova.length) return void mes.channel.send("co to znamena ti gadzovko");
const nabidka = slova.sort().slice(0, 5);
const radek = new ActionRowBuilder<ButtonBuilder>();
nabidka.forEach((cmd, i) => {
const nazev = cmd.length > 80 ? `${cmd.slice(0, 77)}...` : cmd;
radek.addComponents(new ButtonBuilder({ customId: `${i}`, label: nazev, style: ButtonStyle.Primary }));
});
const zprava = await mes.channel.send({ content: "nemnel sy na misli:", components: [radek] });
const collector = mes.channel.createMessageComponentCollector<ComponentType.Button>({ filter: i => i.message.id == zprava.id && mes.author.id == i.user.id, time: 18e4 });
collector.on("collect", i => {
const lookupId = Number(i.customId);
if (isNaN(lookupId)) return void mes.channel.send("neco nefunguje idk");
const komand = nabidka[lookupId];
const cmdName = client.aliasy[komand] ?? komand;
const cmd = client.komandy[cmdName];
radek.components.forEach(btn => btn.setDisabled());
i.update({ content: `ok vole ${emouty.d3k}`, components: [radek] });
runKomand(mes, cmd, cmdName, celArgs);
});
collector.on("end", c => {
if (c.size) return;
radek.components.forEach(btn => btn.setDisabled());
zprava.edit({ content: "pozde", components: [radek] });
});
});
// Simulation of userPresenceUpdate event
client.on("presenceUpdate", (bef, aft) => {
const user = aft.user as CUser | null;
if (!user) return;
if (!user.presence) user.presence = {};
if (JSON.stringify(aft.clientStatus) == JSON.stringify(user.presence)) return;
user.presence = aft.clientStatus || {};
runEvent("userPresenceUpdate", [bef, aft]);
});
client.login(process.env.token);