forked from syntaxbullet/AuroraBot-discord
feat: Implement dynamic event loading and refactor event handlers into dedicated files.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Client as DiscordClient, Collection, GatewayIntentBits, REST, Routes } from "discord.js";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import type { Command } from "@lib/types";
|
||||
import type { Command, Event } from "@lib/types";
|
||||
import { env } from "@lib/env";
|
||||
|
||||
class Client extends DiscordClient {
|
||||
@@ -23,6 +23,16 @@ class Client extends DiscordClient {
|
||||
await this.readCommandsRecursively(commandsPath, reload);
|
||||
}
|
||||
|
||||
async loadEvents(reload: boolean = false) {
|
||||
if (reload) {
|
||||
this.removeAllListeners();
|
||||
console.log("♻️ Reloading events...");
|
||||
}
|
||||
|
||||
const eventsPath = join(import.meta.dir, '../events');
|
||||
await this.readEventsRecursively(eventsPath, reload);
|
||||
}
|
||||
|
||||
private async readCommandsRecursively(dir: string, reload: boolean = false) {
|
||||
try {
|
||||
const files = await readdir(dir, { withFileTypes: true });
|
||||
@@ -63,10 +73,52 @@ class Client extends DiscordClient {
|
||||
}
|
||||
}
|
||||
|
||||
private async readEventsRecursively(dir: string, reload: boolean = false) {
|
||||
try {
|
||||
const files = await readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = join(dir, file.name);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
await this.readEventsRecursively(filePath, reload);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.ts') && !file.name.endsWith('.js')) continue;
|
||||
|
||||
try {
|
||||
const importPath = reload ? `${filePath}?t=${Date.now()}` : filePath;
|
||||
const eventModule = await import(importPath);
|
||||
const event = eventModule.default;
|
||||
|
||||
if (this.isValidEvent(event)) {
|
||||
if (event.once) {
|
||||
this.once(event.name, (...args) => event.execute(...args));
|
||||
} else {
|
||||
this.on(event.name, (...args) => event.execute(...args));
|
||||
}
|
||||
console.log(`✅ Loaded event: ${event.name}`);
|
||||
} else {
|
||||
console.warn(`⚠️ Skipping invalid event in ${file.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to load event from ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading directory ${dir}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
private isValidCommand(command: any): command is Command {
|
||||
return command && typeof command === 'object' && 'data' in command && 'execute' in command;
|
||||
}
|
||||
|
||||
private isValidEvent(event: any): event is Event<any> {
|
||||
return event && typeof event === 'object' && 'name' in event && 'execute' in event;
|
||||
}
|
||||
|
||||
async deployCommands() {
|
||||
// We use env.DISCORD_BOT_TOKEN directly so this can run without client.login()
|
||||
const token = env.DISCORD_BOT_TOKEN;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { ChatInputCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandsOnlyBuilder } from "discord.js";
|
||||
import type { ChatInputCommandInteraction, ClientEvents, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandsOnlyBuilder } from "discord.js";
|
||||
|
||||
export interface Command {
|
||||
data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder | SlashCommandSubcommandsOnlyBuilder;
|
||||
execute: (interaction: ChatInputCommandInteraction) => Promise<void> | void;
|
||||
}
|
||||
|
||||
export interface Event<K extends keyof ClientEvents> {
|
||||
name: K;
|
||||
once?: boolean;
|
||||
execute: (...args: ClientEvents[K]) => Promise<void> | void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user