forked from syntaxbullet/AuroraBot-discord
feat(lib): extract EventLoader from BotClient
- Create dedicated EventLoader class for event loading logic - Implement recursive directory scanning - Add event validation and registration (once vs on) - Improve error handling with structured results - Enable better testability and separation of concerns
This commit is contained in:
84
src/lib/loaders/EventLoader.ts
Normal file
84
src/lib/loaders/EventLoader.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import type { Event } from "@lib/types";
|
||||
import type { LoadResult } from "./types";
|
||||
import type { Client } from "../BotClient";
|
||||
|
||||
/**
|
||||
* Handles loading events from the file system
|
||||
*/
|
||||
export class EventLoader {
|
||||
private client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load events from a directory recursively
|
||||
*/
|
||||
async loadFromDirectory(dir: string, reload: boolean = false): Promise<LoadResult> {
|
||||
const result: LoadResult = { loaded: 0, skipped: 0, errors: [] };
|
||||
await this.scanDirectory(dir, reload, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan directory for event files
|
||||
*/
|
||||
private async scanDirectory(dir: string, reload: boolean, result: LoadResult): Promise<void> {
|
||||
try {
|
||||
const files = await readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = join(dir, file.name);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
await this.scanDirectory(filePath, reload, result);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.ts') && !file.name.endsWith('.js')) continue;
|
||||
|
||||
await this.loadEventFile(filePath, reload, result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading directory ${dir}:`, error);
|
||||
result.errors.push({ file: dir, error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single event file
|
||||
*/
|
||||
private async loadEventFile(filePath: string, reload: boolean, result: LoadResult): Promise<void> {
|
||||
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.client.once(event.name, (...args) => event.execute(...args));
|
||||
} else {
|
||||
this.client.on(event.name, (...args) => event.execute(...args));
|
||||
}
|
||||
console.log(`✅ Loaded event: ${event.name}`);
|
||||
result.loaded++;
|
||||
} else {
|
||||
console.warn(`⚠️ Skipping invalid event in ${filePath}`);
|
||||
result.skipped++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to load event from ${filePath}:`, error);
|
||||
result.errors.push({ file: filePath, error });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to validate event structure
|
||||
*/
|
||||
private isValidEvent(event: any): event is Event<any> {
|
||||
return event && typeof event === 'object' && 'name' in event && 'execute' in event;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user