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

✨ Initial working command handling implementation

+51 -44
-16
src/core/Handler.ts
··· 1 - import type { ChatInputCommandInteraction } from "discord.js"; 2 - import type { VoidyClient } from "./VoidyClient"; 3 - 4 - interface IHandler<T extends object> { 5 - invoke: (data: ChatInputCommandInteraction) => void 6 - } 7 - 8 - export abstract class Handler<T extends object> implements IHandler<T> { 9 - protected client: VoidyClient; 10 - 11 - public constructor(client: VoidyClient) { 12 - this.client = client; 13 - } 14 - 15 - public abstract invoke(data: ChatInputCommandInteraction): void 16 - }
+21 -4
src/core/VoidyClient.ts
··· 4 4 type APIApplicationCommandSubcommandOption, 5 5 type ApplicationCommandDataResolvable, 6 6 type ClientOptions, 7 + type Interaction, 7 8 Client, 8 9 } from "discord.js"; 9 10 import { Registry } from "./Registry"; 10 11 import type { Event } from "../loaders/EventLoader"; 12 + import type { Command } from "../loaders/CommandLoader"; 11 13 12 14 export class VoidyClient extends Client { 13 15 public registries: Registry[]; 16 + public cache: Command[] = []; 14 17 15 18 public constructor(options: ClientOptions) { 16 19 super(options); ··· 22 25 23 26 public async start(token: string) { 24 27 // 1. Prepare commands and events, without registering them 25 - const { commands, events } = await this.initialize(); 28 + const { 29 + commands, 30 + commandsJSON, 31 + events 32 + } = await this.collectBundledRegistryData(); 33 + 34 + // 1.1 Cache collected commands 35 + this.cache = [...commands]; 26 36 27 37 // 2. Register event listeners 28 38 await this.registerEventHandlers(events); ··· 31 41 await this.login(token); 32 42 33 43 // 4. Register/Publish commands 34 - await this.registerCommands(commands); 44 + await this.registerCommands(commandsJSON); 35 45 } 36 46 37 - private async initialize() { 47 + private async collectBundledRegistryData() { 38 48 for (const registry of this.registries) { 39 49 // 1. Collecting required registry data 40 50 console.info(`[Voidy] Collecting registry data: ${registry.dataSource}`); ··· 49 59 await registry.activate(); 50 60 } 51 61 62 + // Only get active registries 52 63 const activeRegistries = this.registries 53 64 .filter(registry => registry.active); 54 65 66 + // Collect events from active registries 55 67 const events = activeRegistries 56 68 .flatMap(registry => registry.events); 57 69 70 + // Collect raw commands from active registries 58 71 const commands = activeRegistries 59 72 .flatMap(registry => registry.commands) 73 + 74 + // Collect JSON export of commands from active registries 75 + const commandsJSON = activeRegistries 76 + .flatMap(registry => registry.commands) 60 77 .flatMap(commands => commands.data.toJSON()) 61 78 62 - return { commands, events }; 79 + return { commands, commandsJSON, events }; 63 80 } 64 81 65 82 private async registerEventHandlers(events: Event[]) {
+3 -13
src/handlers/CommandHandler.ts
··· 1 1 import type { ChatInputCommandInteraction } from "discord.js"; 2 - import { Handler } from "../core/Handler"; 3 2 import type { Command } from "../loaders/CommandLoader"; 4 3 import type { VoidyClient } from "../core/VoidyClient"; 5 4 6 - export class CommandHandler extends Handler<Command> { 7 - public constructor( 8 - client: VoidyClient 9 - ) { 10 - super(client); 11 - } 12 - 13 - public invoke(data: ChatInputCommandInteraction): void { 14 - console.log(data); 15 - // @Todo: implement invoke method, which fetches command information from registries, based on the command name 16 - // 17 - // @Todo: consider whether we actually need handlers as separate classes, or if we can just give the client a handle method. 5 + export class ChatInputCommandHandler { 6 + public static invoke(interaction: ChatInputCommandInteraction, payload: Command, client: VoidyClient): void { 7 + payload.execute(interaction, client); 18 8 } 19 9 }
+1 -5
src/modules/core/commands/ping.ts
··· 4 4 export default { 5 5 data: new SlashCommandBuilder() 6 6 .setName("ping") 7 - .setDescription("View the websocket ping between Discord and the Bot.") 8 - .addSubcommand(subcommand => subcommand 9 - .setName("balls") 10 - .setDescription("A required description") 11 - ), 7 + .setDescription("View the websocket ping between Discord and the Bot."), 12 8 13 9 execute: async (interaction, client) => { 14 10 await interaction.reply({
+26 -6
src/modules/core/events/interactionCreate.ts
··· 1 - import { Events, type Interaction } from "discord.js"; 1 + import { Events, MessageFlags, type Interaction } from "discord.js"; 2 2 import type { Event } from "../../../loaders/EventLoader"; 3 3 import type { VoidyClient } from "../../../core/VoidyClient"; 4 - import { CommandHandler } from "../../../handlers/CommandHandler"; 4 + import { ChatInputCommandHandler } from "../../../handlers/CommandHandler"; 5 5 6 6 export default { 7 7 name: Events.InteractionCreate, 8 8 execute: async (client: VoidyClient, interaction: Interaction) => { 9 - if (!interaction.isChatInputCommand() || !interaction.isCommand()) return null; 9 + if (interaction.isChatInputCommand() && interaction.isCommand()) { 10 + // Filter the client command cache to locate the invoked command 11 + const payload = client.cache.filter(commands => commands.data.name === interaction.commandName)[0]; 10 12 11 - console.log(interaction.commandName); 12 - 13 - 13 + if (!payload) return interaction.reply({ 14 + content: `Sorry, but the command ${interaction.commandName} could not be located in my command cache >:3`, 15 + flags: [MessageFlags.Ephemeral] 16 + }); 17 + 18 + ChatInputCommandHandler.invoke(interaction, payload, client); 19 + } else { 20 + let dmChannel = interaction.user.dmChannel; 21 + 22 + // Attempt DM channel creation, if not found. 23 + if (!dmChannel) { 24 + dmChannel = await interaction.user.createDM(); 25 + } 26 + 27 + // If the DM channel is still not available, give up. 28 + if (!dmChannel || !dmChannel.isSendable()) return; 29 + 30 + dmChannel.send({ 31 + content: `Sorry, but your last interaction wasn't successful and has been logged as an error case, for debugging purposes.\n\nIf you have any additional information to share with us, please communicate with the bot within this DM channel, and we will get in contact.\n\nThank you for understanding, and have a great day :3`, 32 + }) 33 + } 14 34 } 15 35 } as Event