A powerful and extendable Discord bot, with it's own module system :3 thevoid.cafe/projects/voidy

✨ Add button handling

+62 -5
+5
src/core/Registry.ts
··· 1 + import { ButtonLoader, type Button } from "../loaders/ButtonLoader" 1 2 import { CommandLoader, type Command } from "../loaders/CommandLoader" 2 3 import { EventLoader, type Event } from "../loaders/EventLoader" 3 4 import { ModuleLoader, type Module } from "../loaders/ModuleLoader" ··· 7 8 dataSource: string 8 9 modules: Module[] 9 10 commands: Command[] 11 + buttons: Button[] 10 12 events: Event[] 11 13 active: boolean 12 14 ··· 20 22 public dataSource: string; 21 23 public modules: Module[] = []; 22 24 public commands: Command[] = []; 25 + public buttons: Button[] = []; 23 26 public events: Event[] = []; 24 27 public active = false; 25 28 ··· 50 53 this.commands.push(...loader.getJSON()); 51 54 } else if (loader instanceof EventLoader) { 52 55 this.events.push(...loader.getJSON()); 56 + } else if (loader instanceof ButtonLoader) { 57 + this.buttons.push(...loader.getJSON()); 53 58 } 54 59 } 55 60 }
+14 -4
src/core/VoidyClient.ts
··· 10 10 import { Registry } from "./Registry"; 11 11 import type { Event } from "../loaders/EventLoader"; 12 12 import type { Command } from "../loaders/CommandLoader"; 13 + import type { Button } from "../loaders/ButtonLoader"; 13 14 14 15 export class VoidyClient extends Client { 15 16 public registries: Registry[]; 16 - public cache: Command[] = []; 17 + public cache: { commands: Command[], buttons: Button[] } = { 18 + commands: [], 19 + buttons: [], 20 + }; 17 21 18 22 public constructor(options: ClientOptions) { 19 23 super(options); ··· 28 32 const { 29 33 commands, 30 34 commandsJSON, 35 + buttons, 31 36 events 32 37 } = await this.collectBundledRegistryData(); 33 38 34 - // 1.1 Cache collected commands 35 - this.cache = [...commands]; 39 + // 1.1 Cache collected commands and buttons 40 + this.cache.commands = [...commands]; 41 + this.cache.buttons = [...buttons]; 36 42 37 43 // 2. Register event listeners 38 44 await this.registerEventHandlers(events); ··· 76 82 .flatMap(registry => registry.commands) 77 83 .flatMap(commands => commands.data.toJSON()) 78 84 79 - return { commands, commandsJSON, events }; 85 + // Collect raw buttons from active registries 86 + const buttons = activeRegistries 87 + .flatMap(registry => registry.buttons) 88 + 89 + return { commands, commandsJSON, buttons, events }; 80 90 } 81 91 82 92 private async registerEventHandlers(events: Event[]) {
+9
src/handlers/ButtonHandler.ts
··· 1 + import type { ButtonInteraction } from "discord.js"; 2 + import type { Button } from "../loaders/ButtonLoader"; 3 + import type { VoidyClient } from "../core/VoidyClient"; 4 + 5 + export class ButtonHandler { 6 + public static invoke(interaction: ButtonInteraction, payload: Button, client: VoidyClient): void { 7 + payload.execute(interaction, client); 8 + } 9 + }
+17
src/loaders/ButtonLoader.ts
··· 1 + import type { ButtonInteraction } from "discord.js"; 2 + import { Loader } from "../core/Loader"; 3 + import type { VoidyClient } from "../core/VoidyClient"; 4 + 5 + export interface Button { 6 + id: string, 7 + execute: ( 8 + interaction: ButtonInteraction, client: VoidyClient 9 + ) => Promise<void> 10 + } 11 + 12 + export class ButtonLoader extends Loader<Button> { 13 + public override async validate(data: Partial<Button>) { 14 + if (!data.id || !data.execute) return null; 15 + return data as Button; 16 + } 17 + }
+12 -1
src/modules/core/events/interactionCreate.ts
··· 2 2 import type { Event } from "../../../loaders/EventLoader"; 3 3 import type { VoidyClient } from "../../../core/VoidyClient"; 4 4 import { ChatInputCommandHandler } from "../../../handlers/CommandHandler"; 5 + import { ButtonHandler } from "../../../handlers/ButtonHandler"; 5 6 6 7 export default { 7 8 name: Events.InteractionCreate, 8 9 execute: async (client: VoidyClient, interaction: Interaction) => { 9 10 if (interaction.isChatInputCommand() && interaction.isCommand()) { 10 11 // Filter the client command cache to locate the invoked command 11 - const payload = client.cache.filter(commands => commands.data.name === interaction.commandName)[0]; 12 + const payload = client.cache.commands.filter(commands => commands.data.name === interaction.commandName)[0]; 12 13 13 14 if (!payload) return interaction.reply({ 14 15 content: `Sorry, but the command ${interaction.commandName} could not be located in my command cache >:3`, ··· 16 17 }); 17 18 18 19 ChatInputCommandHandler.invoke(interaction, payload, client); 20 + } else if (interaction.isButton()) { 21 + // Filter the client button cache to locate the invoked button 22 + const payload = client.cache.buttons.filter(buttons => buttons.id === interaction.customId)[0]; 23 + 24 + if (!payload) return interaction.reply({ 25 + content: `Sorry, but the button ${interaction.customId} could not be located in my button cache >:3`, 26 + flags: [MessageFlags.Ephemeral] 27 + }); 28 + 29 + ButtonHandler.invoke(interaction, payload, client); 19 30 } else { 20 31 let dmChannel = interaction.user.dmChannel; 21 32
+5
src/modules/core/module.ts
··· 1 + import { ButtonLoader } from "../../loaders/ButtonLoader"; 1 2 import { CommandLoader } from "../../loaders/CommandLoader"; 2 3 import { EventLoader } from "../../loaders/EventLoader"; 3 4 import type { Module } from "../../loaders/ModuleLoader"; ··· 15 16 { 16 17 source: `${import.meta.dir}/commands`, 17 18 loader: CommandLoader, 19 + }, 20 + { 21 + source: `${import.meta.dir}/buttons`, 22 + loader: ButtonLoader, 18 23 } 19 24 ] 20 25 } as Module;