import type { Message } from "@fluxerjs/core"; import { COMMAND_META_KEY, type CommandMeta } from "@/decorators/command"; import { GUARDS_KEY, type Guard } from "@/decorators/guards"; import { parseArgs } from "@/args"; import type { BotContext, ICommand } from "@/types"; import type { CommandCtor } from "@/types"; import { Logger } from "tslog"; interface FileRecord { filePath: string; triggers: string[]; } type AnyCommand = ICommand; export class CommandRegistry { private map = new Map(); private guards = new Map(); private fileSources = new Map(); private logger: Logger; constructor() { this.logger = new Logger({ type: "pretty", name: "CommandRegistry", }); } register(prefix: string, filePath: string | null, ...ctors: CommandCtor[]): void { for (const ctor of ctors) { try { const meta: CommandMeta = Reflect.getMetadata(COMMAND_META_KEY, ctor); // Checks if command class has @Command decorators if (!meta) throw new Error(`${ctor.name} has missing @Command decorators`); const instance = new ctor(); const instanceGuards: Guard[] = Reflect.getMetadata(GUARDS_KEY, ctor) ?? []; const triggers = [meta.name, ...(meta.aliases ?? [])].map((n) => `${prefix}${n}`); for (const trigger of triggers) { this.map.set(trigger, instance); this.guards.set(trigger, instanceGuards); } if (filePath) { this.fileSources.set(meta.name, { filePath, triggers }); } } catch (e) { if (e instanceof Error) this.logger.error(`Something went wrong while registring: ${e.message}`); } } } unregisterFile(filePath: string): void { for (const [name, record] of this.fileSources) { if (record.filePath === filePath) { for (const trigger of record.triggers) { this.map.delete(trigger); this.guards.delete(trigger); } this.fileSources.delete(name); } } } async handle(ctx: BotContext, prefix: string, message: Message): Promise { if (!message.content.startsWith(prefix)) return; try { const [rawCmd, ...args] = message.content.slice(prefix.length).trim().split(/\s+/); const key = `${prefix}${rawCmd?.toLowerCase()}`; const command = this.map.get(key); if (!command) return; const guards = this.guards.get(key) ?? []; for (const guard of guards) { if (!(await guard(message))) return; } // Parse raw string args through the command's schema if it has one, // otherwise pass the raw array cast to the expected type. const parsedArgs = command.args ? await parseArgs(command.args, args) : ({} as never); this.logger.info( `Executing ${rawCmd} ${args ? `with arguments: ${args.join(", ")}` : "without arguments"}`, ); await command.execute(ctx, message, parsedArgs); } catch (err) { if (err instanceof Error) { this.logger.error(`Something went wrong while executing this command: ${err.message}`); message.reply({ content: `Something went wrong while executing this command:\n\`\`\`\n${err.message}\n\`\`\``, }); } } } }