import { readdir } from "node:fs/promises"; import { join } from "node:path"; import type { Command } from "@shared/lib/types"; import { config } from "@shared/lib/config"; import type { LoadResult, LoadError } from "./types"; import type { Client } from "../BotClient"; /** * Handles loading commands from the file system */ export class CommandLoader { private client: Client; constructor(client: Client) { this.client = client; } /** * Load commands from a directory recursively */ async loadFromDirectory(dir: string, reload: boolean = false): Promise { const result: LoadResult = { loaded: 0, skipped: 0, errors: [] }; await this.scanDirectory(dir, reload, result); return result; } /** * Recursively scan directory for command files */ private async scanDirectory(dir: string, reload: boolean, result: LoadResult): Promise { 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')) || file.name.endsWith('.test.ts') || file.name.endsWith('.spec.ts')) continue; await this.loadCommandFile(filePath, reload, result); } } catch (error) { console.error(`Error reading directory ${dir}:`, error); result.errors.push({ file: dir, error }); } } /** * Load a single command file */ private async loadCommandFile(filePath: string, reload: boolean, result: LoadResult): Promise { try { const importPath = reload ? `${filePath}?t=${Date.now()}` : filePath; const commandModule = await import(importPath); const commands = Object.values(commandModule); if (commands.length === 0) { console.warn(`No commands found in ${filePath}`); result.skipped++; return; } const category = this.extractCategory(filePath); for (const command of commands) { if (this.isValidCommand(command)) { command.category = category; // Track all known commands regardless of enabled status this.client.knownCommands.set(command.data.name, category); const isEnabled = config.commands[command.data.name] !== false; if (!isEnabled) { console.log(`🚫 Skipping disabled command: ${command.data.name}`); result.skipped++; continue; } this.client.commands.set(command.data.name, command); console.log(`Loaded command: ${command.data.name}`); result.loaded++; } else { console.warn(`Skipping invalid command in ${filePath}`); result.skipped++; } } } catch (error) { console.error(`Failed to load command from ${filePath}:`, error); result.errors.push({ file: filePath, error }); } } /** * Extract category from file path * e.g., /path/to/commands/admin/features.ts -> "admin" */ private extractCategory(filePath: string): string { const pathParts = filePath.split('/'); return pathParts[pathParts.length - 2] ?? "uncategorized"; } /** * Type guard to validate command structure */ private isValidCommand(command: any): command is Command { return command && typeof command === 'object' && 'data' in command && 'execute' in command; } }