Denim-Bot/src/modules/spink.ts

316 lines
10 KiB
TypeScript

// Modul dedikovaný funkci spinkáček
import { ChannelType, Role, TextBasedChannel, User } from "discord.js";
import { emouty } from "../utils/emotes";
import { Modul, SRecord } from "../utils/types";
import { formatCas, formatter, log, oddiakritikovat, ping, safeQuery, sendDM } from "../utils/utils";
const enum Spinky {
vstavacek,
spinkacek
}
const budouciSpinky: SRecord<NodeJS.Timeout> = {};
const autoSpinky: SRecord<[boolean, TextBasedChannel]> = {};
const contactSpinkServer = async (druh: Spinky, user: User) => {
handleSpink(druh, user);
const sql = druh == Spinky.spinkacek
? "INSERT INTO spinkacky (discord_id) VALUE (?)"
: "UPDATE spinkacky SET vstavacek = curtime() WHERE discord_id = ? ORDER BY id DESC LIMIT 1";
const odpoved = await safeQuery(sql, [user.id]);
return odpoved.err;
};
const ifUzRemove = (id: string) => {
const uzPgn = budouciSpinky[id];
if (uzPgn) {
clearTimeout(uzPgn);
delete budouciSpinky[id];
return true;
}
return false;
};
async function getLastEntry(id: string) {
const odpoved = await safeQuery<{ spinkacek: string; vstavacek: string | undefined; }>(`SELECT spinkacek, vstavacek FROM spinkacky WHERE discord_id = ? ORDER BY spinkacek DESC LIMIT 1; `, [id]);
if (odpoved.err || !odpoved.data.length) return false;
return odpoved.data[0];
}
/*
R.I.P.
async tovjemamvolepreceroliky(roliky: User, rictCas?: boolean)
* 20. 11. 2021 ✝ 19. 11. 2023
Vzpomínáme!
*/
const sendGmMessage = async (user: User, rictCas?: boolean) => {
const odpoved = await contactSpinkServer(Spinky.vstavacek, user);
if (odpoved) return "uz jsi vzhuru ty hajzle";
const zapis = await getLastEntry(user.id);
const zacatek = `dobry rano hajzle ${user}`;
if (!rictCas || zapis === false) return zacatek;
const cas = (Number(new Date(zapis.vstavacek ?? Date.now())) - Number(new Date(zapis.spinkacek))) / 1000;
const formatedCas = formatCas(cas);
let zpr = "";
if (cas >= 57600) zpr = "mas dat more vstavacek uz kdyz vstanes retarde";
else if (cas >= 43200) zpr = "extrémní bídák";
else if (cas >= 36000) zpr = "fakt bídák";
else if (cas < 30) zpr = "dobrej mikrospánek debile";
else if (cas < 10800) zpr = "dobrej fake spink debile";
return `${zacatek}\nspal sy ${formatedCas}\n${zpr}`;
};
function handleSpink(stav: Spinky, uzivatel: User) {
for (const [, guild] of uzivatel.client.guilds.cache) {
const member = guild.members.cache.get(uzivatel.id);
if (!member) continue;
const c = guild.roles.cache;
const spinkRole = c.find(r => r.name == "spink");
const adminRole = c.find(r => r.name == "admin");
const make = (akce: "a" | "r", role: Role) => {
const handle = (e: Error) => {
if (["Missing Permissions", "Missing Access"].includes(e.message)) return;
log("chyba pri davani/odebirani role", e);
};
if (akce == "a") member.roles.add(role).catch(handle);
else member.roles.remove(role).catch(handle);
};
if (stav == Spinky.spinkacek) {
if (spinkRole) make("a", spinkRole);
if (adminRole) make("r", adminRole);
} else {
if (spinkRole) make("r", spinkRole);
if (adminRole) make("a", adminRole);
continue;
}
for (const [, channel] of guild.channels.cache) {
if (channel.type != ChannelType.GuildVoice) continue;
for (const [, member] of channel.members) {
if (member.id != uzivatel.id) continue;
member.voice.disconnect("spinkacek");
return false;
}
}
}
}
/**
* Zpracuje komplexní string pro pgn a vrátí počet milisekund, o kolik v budoucnu autor zamýšlel
* @param str
* @returns array, kde první informace je, zda-li byla informace relativní nebo absolutní a druhá je počet milisekund v budoucnoti
*/
function strToDate(str: string): ["rel" | "abs", number] | never[] {
const cas = new Date();
const dnes = new Date();
let neco: RegExpExecArray | null;
if (str == "") return [];
// Relativní čas
neco = /^(?:(?<h>\d+)h)?(?:(?<m>\d+)m)?(?:(?<s>\d+)s?)?$/.exec(str.toLowerCase());
if (neco) {
const g = neco.groups!;
return ["rel", ((Number(g.h) * 3600 || 0) + (Number(g.m) * 60 || 0) + (Number(g.s) || 0)) * 1000] as const;
}
// Absolutní čas
neco = /^((?<d>\d+)\. ?((?<mo>\d+)\.)? ?)?(?<h>\d+)(:(?<m>\d+)(:(?<s>\d+))?)? ?(?<p>am|pm)?$/.exec(str.toLowerCase());
if (neco) {
const g = neco.groups!;
if (typeof g.m == "undefined" && typeof g.p == "undefined") return [];
if (g.d) cas.setDate(Number(g.d));
if (g.mo) cas.setMonth(Number(g.mo) - 1);
const h = Number(g.h);
cas.setHours(g.p == "pm" && h < 12 ? h + 12 : h);
cas.setMinutes(Number(g.m) || 0);
cas.setSeconds(Number(g.s) || 0);
if (Number(cas) < Number(dnes)) cas.setDate(dnes.getDate() + 1);
return ["abs", Number(cas) - Number(dnes)] as const;
}
return [];
}
const exp: Modul = {
more_komandy: {
spinkacek: {
als: ["spink", "spoink", "spinkake", "spoinkacek", "gn", emouty.spinkacek, emouty.gn],
run: async mes => {
if (mes.author.bot) return `až někdy${emouty.kapp}`;
const odpoved = await contactSpinkServer(Spinky.spinkacek, mes.author);
if (!odpoved) {
mes.react(emouty.spinkacek);
ifUzRemove(mes.author.id);
}
else return 'nespis uz?????';
}
},
vstavacek: {
als: ["vstavcacek", "gm", "unspinkacek"],
arg: "fejk (nepovinné)",
run: async (mes, arg) => {
if (mes.author.bot) return emouty.sjeta;
return sendGmMessage(mes.author, arg.toLowerCase() != "fejk");
}
},
pgn: {
als: [`p${emouty.gn}`],
run: (mes, kdy) => {
if (mes.author.bot) return emouty.sjeta;
// Random výplod GitHub Copilota
if (kdy == "fejk") {
mes.react(emouty.lukiw);
return "fejk spinkacek";
}
let druh = "";
let spinkZa: number;
let random = false;
if (kdy.startsWith("random")) {
random = true;
let rozdil = 540_000;
let min = 60_000;
if (kdy.length > 7) {
const str = kdy.toLowerCase().slice(7).replace(/ /g, "").split("-");
if (str.length != 2) return "cos to napsal kkte";
const [from, to] = [strToDate(str[0]), strToDate(str[1])];
if (!from.length || !to.length) return "cos to napsal kkte";
if (to[1] <= from[1]) return "ja teda nevim jestli ti vys jak funguje cas ael vym ze tam mas chibu";
rozdil = to[1] - from[1];
min = from[1];
}
spinkZa = (Math.random() * rozdil + min);
log(`random spink pro ${oddiakritikovat(mes.member?.displayName || "neznamejmeno")} byl nastaven na ${formatter(Date.now() + spinkZa)}`);
} else {
if (kdy == "")
[druh, spinkZa] = ["rel", 2 * 60 * 60 * 1000];
else {
const back = strToDate(kdy);
if (typeof back[1] != "number") return "cos to napsal kkte";
[druh, spinkZa] = back;
}
}
ifUzRemove(mes.author.id);
budouciSpinky[mes.author.id] = setTimeout(() => {
contactSpinkServer(Spinky.spinkacek, mes.author);
mes.react(emouty.spinkacek);
}, spinkZa);
if (!random) {
if (druh == "abs") return `tvuj spinkacek byl naplanovan na za ${formatCas(spinkZa / 1000)}`;
else return `tvuj spinkacek byl naplanovan na ${formatter(Date.now() + spinkZa)}`;
}
mes.react(emouty.lajk);
}
},
ps: {
als: ["poslednispink"],
arg: "kdo (nepovinné)",
run: async (mes, arg) => {
const prazdno = arg == "";
if (!ping.test(arg) && !prazdno) return "sikkt";
const id = prazdno ? mes.author.id : mes.mentions.members?.first()?.id ?? "";
const entry = await getLastEntry(id);
if (entry === false) return `tuten kkt jeste nespal ${emouty.lukiw}`;
if (!entry.vstavacek) return "ten kkt jiz ickon spi";
const cas = Number(new Date()) - Number(new Date(entry.vstavacek));
return `uz ${prazdno ? "jsi" : "je"} vzhuru ${formatCas(cas / 1000)}`;
}
},
zruspgn: {
als: ["zpgn", "cancelpgn", "cpgn", "unpgn", `unp${emouty.gn}`],
run: mes => {
if (!ifUzRemove(mes.author.id)) return `spinkacek ale nemas naplanovanej ty kkte ${mes.author}`;
return "to bylo teda trapny debile";
}
},
autospink: mes =>
`autospink je ${autoSpinky[mes.author.id] ? (delete autoSpinky[mes.author.id], "vypnut") : (autoSpinky[mes.author.id] = [true, mes.channel], "zapnut")}`,
autoautospink: {
als: ["🚗🚗spink"],
run: mes =>
`autoautospink je ${autoSpinky[mes.author.id] ? (delete autoSpinky[mes.author.id], "vypnut") : (autoSpinky[mes.author.id] = [false, mes.channel], "zapnut")}`
}
},
on_voiceStateUpdate: async (bef, aft) => {
if (!aft.channel || bef.channel) return;
const entry = await getLastEntry(aft.id);
if (entry && !entry.vstavacek) aft.disconnect("spinkacek")
.catch(err => log("spinkacek odpojit se nepovedlo nebo co:", err));
},
super_on_messageCreate: async (mes, cmd?: string) => {
const entry = await getLastEntry(mes.author.id);
if ((entry && !entry.vstavacek) && cmd != "vstavacek") {
if (mes.channel.type == ChannelType.DM) {
mes.channel.send("drz hubu");
return true;
}
mes.delete().catch((e: Error) => {
if (e.name == "DiscordAPIError[5OO13]") return;
log("chyba pri mazani", e);
});
sendDM(mes.author, "spis tak nepis");
return true;
}
return false;
},
// autoSpink handeler
on_userPresenceUpdate: async (bef, aft) => {
if (!bef) return;
const befoff = bef.status == "offline";
const aftoff = aft.status == "offline";
const prop = autoSpinky[aft.userId];
if (!prop) return;
if (!befoff && aftoff) {
return contactSpinkServer(Spinky.spinkacek, aft.member!.user);
}
if (befoff && !aftoff) {
// autospink, ne autoautospink
if (prop[0]) delete autoSpinky[aft.userId];
const kanel = prop[1];
if (kanel?.type == ChannelType.GuildText) kanel.send(await sendGmMessage(aft.member!.user));
}
}
};
if (!process.env.ignoreSpink)
module.exports = exp;