From 6eca55d6d62ef5855b259118d6ff18b37c5ab1b3 Mon Sep 17 00:00:00 2001 From: Histmy Date: Sat, 4 Mar 2023 17:09:53 +0100 Subject: [PATCH] revoluce je tady doprdele!! --- src/app.ts | 56 +++++++--- src/modules/anketa.ts | 158 ++++++++++++++++----------- src/modules/registerSlashCommands.ts | 62 +++++++++++ src/modules/spink.ts | 11 +- src/utils/types.ts | 28 +++-- 5 files changed, 226 insertions(+), 89 deletions(-) create mode 100644 src/modules/registerSlashCommands.ts diff --git a/src/app.ts b/src/app.ts index b8b9dbe..f7f908b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,6 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, ComponentType, GatewayIntentBits, Message, Partials } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, Client, CommandInteraction, 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 { CUser, EventSOn, KomandNaExport, Komand, ListenerFunkce, Modul, SRecord, SuperListenerFunkce, CClient, HelpServer, KomandRaw } from "./utils/types"; import { adminLog, formatCas, oddiakritikovat, log, rand } from "./utils/utils.js"; import levenshtein from "js-levenshtein"; import { emouty } from "./utils/emotes"; @@ -26,7 +26,7 @@ const helpServer: HelpServer = require("./utils/helpServer"); client.komandy = {}; client.aliasy = {}; -function getFirstArg(fn: RunFunkce | string) { +function getFirstArg(fn: KomandRaw["run"]) { if (typeof fn != "function") return; return fn.toString() .match(/(?:function\s.*?)?\(([^)]*)\)|\w+ =>/)![1] @@ -91,7 +91,7 @@ readdirSync(modulFolder).forEach(soubor => { client.komandy[cmdName] = { run: value }; toCoExportuju.arg = getFirstArg(value); } else { - client.komandy[cmdName] = { run: value.run, cd: value.cd, DMUnsafe: value.DMUnsafe }; + client.komandy[cmdName] = { run: value.run, slashRun: value.slashRun, 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); @@ -215,23 +215,42 @@ function renderMessage(expression: string, args: string[]) { } } +async function runKomand(mes: Message, cmd: Komand, cmdName: string, arg: string): Promise; +async function runKomand(interaction: CommandInteraction, cmd: Komand, cmdName: string): Promise; +async function runKomand(mesOrInt: Message | CommandInteraction, cmd: Komand, cmdName: string, arg?: string) { + const jeMes = mesOrInt instanceof Message; -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)}`); + if (cmd.cd && jeMes) { + const zbyva = Math.round(maKuldan(mesOrInt.author.id, cmdName, cmd.cd)); + if (zbyva) return void mesOrInt.channel.send(`si kkt vole maz kuldan jeste ${formatCas(zbyva)}`); + } + + const akce = jeMes + ? cmd.run + : cmd.slashRun!; + + if (typeof akce == "string") { + const res = renderMessage(akce, arg?.split(" ") || []); + if (mesOrInt instanceof Message) return mesOrInt.channel.send(res); + return mesOrInt.reply(res); } - const akce = cmd.run; - if (typeof akce == "string") return void mes.channel.send(renderMessage(akce, arg.split(" "))); try { - const result = await akce(mes, arg); - if (result) mes.channel.send(result); + // @ts-ignore todle prostě musí fungovat a jestli náhodou ne, stejně je tu catch + const result = await akce(mesOrInt, arg); + if (!result) return; + + if (mesOrInt instanceof Message) return mesOrInt.channel.send(result); + // @ts-ignore a zas ksd + mesOrInt.reply(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?` : ""}`); + + const txt = `pri spousteni thohoto komandu nastala chyba ${admin ? `<@${admin}> uz?` : ""}`; + if (mesOrInt instanceof Message) return mesOrInt.channel.send(txt); + mesOrInt.reply(txt); } } @@ -305,4 +324,15 @@ client.on("presenceUpdate", (bef, aft) => { runEvent("userPresenceUpdate", [bef, aft]); }); +client.on("interactionCreate", async int => { + if (process.env.ignoreMess || !int.isCommand()) return; + + const cmdName = int.commandName.slice(4); + + const command = client.komandy[cmdName]; + if (!command) int.reply(`komand nenalezen ${emouty.lukiw}`); + + runKomand(int, command, cmdName); +}); + client.login(process.env.token); diff --git a/src/modules/anketa.ts b/src/modules/anketa.ts index be9e494..064a41f 100644 --- a/src/modules/anketa.ts +++ b/src/modules/anketa.ts @@ -1,14 +1,97 @@ // Modul na komand "anketa" -import { ActionRowBuilder, APIEmbed, APIEmbedField, ButtonBuilder, ButtonStyle, ComponentType, Message, MessageComponentInteraction } from "discord.js"; -import { Modul } from "../utils/types"; +import { ActionRowBuilder, APIEmbed, APIEmbedField, ButtonBuilder, ButtonStyle, CommandInteraction, ComponentType, InteractionResponse, Message, MessageComponentInteraction } from "discord.js"; +import { Modul, SRecord } from "../utils/types"; import { formatter } from "../utils/utils"; +function parseMoznosti(moznostiStr: string) { + return moznostiStr.split(moznostiStr.includes("|") ? "|" : ",").reduce((acc, c) => { + if (c.length) acc.push(c.trim()); + return acc; + }, []); +} + +async function zbytek(settings: { time: number; immediateShow: boolean; listNames: boolean; }, moznosti: string[], mesOrInt: Message | CommandInteraction) { + if (moznosti.length < 2) return "zadej alespo%n dve moznosti"; + if (moznosti.length > 25) return "toje moc"; + + const jeMes = mesOrInt instanceof Message; + + const konec = new Date(); + konec.setSeconds(konec.getSeconds() + settings.time); + + const embed: APIEmbed = { + title: "Hlasování", + description: settings.immediateShow ? `Končí ${formatter(konec)}` : `Výsledky se zobrazí ${formatter(konec)}` + }; + const fildy: APIEmbedField[] = []; + const radky: ActionRowBuilder[] = []; + const odpovedi: SRecord = {}; + + function prepocitat(int?: MessageComponentInteraction) { + const celkem = Object.keys(odpovedi).length; + fildy.forEach((f, i) => { + const hovn = Object.values(odpovedi).filter(n => n == i); + const dylka = hovn.length; + const p = Math.floor(dylka / celkem * 10) || 0; + const people = Object.keys(odpovedi).reduce((pr, cur) => { + if (odpovedi[cur] == i) pr.push(cur); + return pr; + }, []); + const kindo = people.map(w => mesOrInt.guild?.members.cache.get(w)?.displayName); + f.value = `${"█".repeat(p)}${"░".repeat(10 - p)} ${Math.round(dylka / celkem * 100) || 0}% (${dylka}) ${settings.listNames ? " (" + kindo.join(", ") + ")" : ""}`; + }); + int?.update({ embeds: [embed] }); + } + + moznosti.forEach((h, i) => { + const mnam = (i - (i % 5)) / 5; + if (i % 5 == 0) radky[mnam] = new ActionRowBuilder; + fildy.push({ name: h, value: "" }); + radky[mnam].addComponents(new ButtonBuilder({ customId: `${i}`, label: h, style: ButtonStyle.Primary })); + }); + prepocitat(); + if (settings.immediateShow) embed.fields = fildy; + + let zprava: Message | InteractionResponse; + let edit: (c: any) => void; + + if (jeMes) { + zprava = await mesOrInt.channel.send({ embeds: [embed], components: radky }); + // @ts-ignore seru na to, logicky to musí fungovat a to že to typscript nechápe není můj problém + edit = content => { zprava.edit(content); }; + } else { + zprava = await mesOrInt.reply({ embeds: [embed], components: radky }); + zprava; + edit = content => { mesOrInt.editReply(content); }; + } + const collector = zprava.createMessageComponentCollector({ time: settings.time * 1000 }); + + collector.on("collect", d => { + if (!(d.message instanceof Message)) return; + const i = Number(d.customId); + const prev = odpovedi[d.user.id]; + if (prev == i) return void d.deferUpdate(); + odpovedi[d.user.id] = i; + if (!settings.immediateShow) return void d.deferUpdate(); + prepocitat(); + d.update({ embeds: [embed] }); + }); + + collector.on("end", () => { + embed.description = `Skončilo ${formatter(konec)}`; + prepocitat(); + embed.fields = fildy; + edit({ components: [], embeds: [embed] }); + }); +} + const exp: Modul = { more_komandy: { anketa: { arg: "([nastaveni]) moznosti...", + DMUnsafe: true, run: async (mes, arg) => { const settings = { @@ -31,70 +114,23 @@ const exp: Modul = { } } - const moznosti = arg.slice(huuuuuu + 1).split(arg.includes("|") ? "|" : ",").reduce((acc, c) => { - if (c.length) acc.push(c.trim()); - return acc; - }, []); - if (moznosti.length < 2) return "zadej alespo%n dve moznosti"; - if (moznosti.length > 25) return "toje moc"; + const moznosti = parseMoznosti(arg.slice(huuuuuu + 1)); - const konec = new Date(); - konec.setSeconds(konec.getSeconds() + settings.time); + return await zbytek(settings, moznosti, mes); + }, - const embed: APIEmbed = { - title: "Hlasování", - description: settings.immediateShow ? `Končí ${formatter(konec)}` : `Výsledky se zobrazí ${formatter(konec)}` + slashRun: async (int) => { + + const opt = int.options; + const settings = { + time: opt.get("cas")?.value as number || 60, + immediateShow: !opt.get("neprubezny")?.value as boolean, + listNames: !!opt.get("jmena")?.value as boolean }; - const x5fxd55: APIEmbedField[] = []; - const radky: ActionRowBuilder[] = []; - const odpovedi: Record = {}; - function prepocitat(): void; - function prepocitat(int: MessageComponentInteraction): void; - function prepocitat(int?: MessageComponentInteraction) { - const celkem = Object.keys(odpovedi).length; - x5fxd55.forEach((f, i) => { - const hovn = Object.values(odpovedi).filter(n => n == i); - const dylka = hovn.length; - const p = Math.floor(dylka / celkem * 10) || 0; - const people = Object.keys(odpovedi).reduce((pr, cur) => { - if (odpovedi[cur] == i) pr.push(cur); - return pr; - }, []); - const kindo = people.map(w => mes.guild?.members.cache.get(w)?.displayName); - f.value = `${"█".repeat(p)}${"░".repeat(10 - p)} ${Math.round(dylka / celkem * 100) || 0}% (${dylka}) ${settings.listNames ? " (" + kindo.join(", ") + ")" : ""}`; - }); - int?.update({ embeds: [embed] }); - } + const moznosti = parseMoznosti(opt.get("moznosti")!.value as string); - moznosti.forEach((h, i) => { - const mnam = (i - (i % 5)) / 5; - if (i % 5 == 0) radky[mnam] = new ActionRowBuilder; - x5fxd55.push({ name: h, value: "" }); - radky[mnam].addComponents(new ButtonBuilder({ customId: `${i}`, label: h, style: ButtonStyle.Primary })); - }); - prepocitat(); - if (settings.immediateShow) embed.fields = x5fxd55; - const zprava = await mes.channel.send({ embeds: [embed], components: radky }); - const collector = mes.channel.createMessageComponentCollector({ filter: i => i.message.id == zprava.id, time: settings.time * 1000 }); - - collector.on("collect", d => { - if (!(d.message instanceof Message)) return; - const i = Number(d.customId); - const prev = odpovedi[d.user.id]; - if (prev == i) return void d.deferUpdate(); - odpovedi[d.user.id] = i; - if (!settings.immediateShow) return void d.deferUpdate(); - prepocitat(); - d.update({ embeds: [embed] }); - }); - - collector.on("end", () => { - embed.description = `Skončilo ${formatter(konec)}`; - prepocitat(); - embed.fields = x5fxd55; - zprava.edit({ components: [], embeds: [embed] }); - }); + return await zbytek(settings, moznosti, int); } } diff --git a/src/modules/registerSlashCommands.ts b/src/modules/registerSlashCommands.ts new file mode 100644 index 0000000..ff74661 --- /dev/null +++ b/src/modules/registerSlashCommands.ts @@ -0,0 +1,62 @@ +import { REST, RESTPostAPIChatInputApplicationCommandsJSONBody, Routes } from 'discord.js'; +import { CClient, Modul } from '../utils/types'; + +const komandy: RESTPostAPIChatInputApplicationCommandsJSONBody[] = [ + { + name: "moreanketa", + description: "udelam fajnonovou anketu haby sy moch myt zbitecny anzori ostatnich", + dm_permission: false, + options: [ + { + name: "moznosti", + description: "mezi cim maj ty morove vibirat (odeluj carkama nebo pipama)", + type: 3, // String + required: true + }, + { + name: "cas", + description: "jak dlouho budem cekat na ti bridili nez se rozhodnou", + type: 4 // Integer + }, + { + name: "jmena", + description: "jestli chces strapnovat ostatni jejich vibjerem tak tuto zapni", + type: 5 // Boolean + }, + { + name: "neprubezny", + description: "jestli chces haby bili visledky vydet hned nebo az potom co bude pozde sy to rozmyslet", + type: 5 // Boolean + } + ] + } +]; + +const exp: Modul = { + on_ready: async () => { + + // Construct and prepare an instance of the REST module + const rest = new REST({ version: '10' }).setToken(process.env.token!); + + // and deploy your commands! + try { + console.log(`Started refreshing application (/) commands.`); + + // The put method is used to fully refresh all commands in the guild with the current set + const data = (await rest.put( + Routes.applicationCommands(client.user!.id), + { body: komandy }, + )) as any; + + console.log(data); + + console.log(`Successfully reloaded ${data?.length} application (/) commands.`); + } catch (error) { + // And of course, make sure you catch and log any errors! + console.error("Chyba při refreshování aplikačních (/) komandů", error); + } + + } +}; + +module.exports = exp; diff --git a/src/modules/spink.ts b/src/modules/spink.ts index 7630450..2c1707d 100644 --- a/src/modules/spink.ts +++ b/src/modules/spink.ts @@ -197,7 +197,7 @@ const exp: Modul = { if (mes.author.id === '831318260493844494') { syncSpink(); const uzivatel = mes.content.match(/(?<=discord_)\d+/)?.[0]; - if (!uzivatel) return; + if (!uzivatel) return false; for (const [, guild] of mes.client.guilds.cache) { const member = guild.members.cache.get(uzivatel); if (!member) continue; @@ -228,17 +228,22 @@ const exp: Modul = { for (const [, member] of channel.members) { if (member.id !== uzivatel) continue; member.voice.disconnect("spinkacek"); - return; + return false; } } } } else if (spinkacky[mes.author.id]?.spinkacek && cmd != "vstavacek") { - if (mes.channel.type == ChannelType.DM) return mes.channel.send("drz hubu"); + if (mes.channel.type == ChannelType.DM) { + mes.channel.send("drz hubu"); + return true; + } mes.delete(); sendDM(mes.author, "spis tak nepis"); return true; } + + return false; }, on_userPresenceUpdate: async (bef: Presence | null, aft: Presence) => { diff --git a/src/utils/types.ts b/src/utils/types.ts index e044d22..aabb94e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,23 +1,25 @@ import { StreamType, VoiceConnection } from "@discordjs/voice"; -import { Client, ClientEvents, ClientPresenceStatusData, Message, MessageCreateOptions, MessagePayload, User } from "discord.js"; +import { Awaitable, Client, ClientEvents, ClientPresenceStatusData, CommandInteraction, Message, MessageCreateOptions, MessagePayload, User } from "discord.js"; import { EventEmitter } from "events"; import { Readable } from "node:stream"; -type OutputRunFunkce = string | MessagePayload | MessageCreateOptions | void; +export type RunFunkce = (message: Message, argumenty: string) => Awaitable; +export type InteractionRunFunkce = (interaction: CommandInteraction) => Awaitable; -export type RunFunkce = (message: Message, argumenty: string) => OutputRunFunkce | Promise; - -export interface BaseKomandProps { +export type BaseKomandProps = { als?: string[]; arg?: string; -} +}; -type KomandRaw = { +export type KomandRaw = BaseKomandProps & +{ run: string | RunFunkce; + slashRun?: InteractionRunFunkce; cd?: number; hidden?: true; DMUnsafe?: true; -} & BaseKomandProps; +}; + export type ListenerFunkce = (...args: any[]) => void; @@ -37,13 +39,15 @@ export type SRecord = Record; export type Modul = { more_komandy?: SRecord; client?: Client; -} & { [key in EventSOn]?: ListenerFunkce | SuperObject | SuperListenerFunkce; }; +} & { [key in `on_${Eventy}`]?: ListenerFunkce } + & { [key in `super_on_${Eventy}`]?: SuperObject | SuperListenerFunkce; }; -export interface Komand { - run: RunFunkce | string; +export type Komand = { + run: string | RunFunkce; + slashRun?: InteractionRunFunkce; cd?: number; DMUnsafe?: true; -} +}; export interface Spinkackar { spinkacek: boolean;