UNPKG

meocord

Version:

MeoCord is a lightweight and modular framework for building scalable Discord bots using TypeScript and Discord.js. It simplifies bot development with an extensible architecture, TypeScript-first approach, and powerful CLI tools.

113 lines 7.07 kB
/** * MeoCord Framework * Copyright (C) 2025 Ukasyah Rahmatullah Zada * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */import{injectable}from"inversify";import"reflect-metadata";import{mainContainer}from"./index.js";import{ButtonInteraction,ChatInputCommandInteraction,ContextMenuCommandInteraction,ModalSubmitInteraction,StringSelectMenuInteraction}from"discord.js";import{CommandType}from"../enum/index.js";var COMMAND_METADATA_KEY=Symbol("commands"),MESSAGE_HANDLER_METADATA_KEY=Symbol("message_handlers"),REACTION_HANDLER_METADATA_KEY=Symbol("reaction_handlers");/** * Decorator to register message handlers in the controller. * * @param keyword - An optional keyword to filter messages this handler should respond to. * * @example * ```typescript * @MessageHandler('hello') * async handleHelloMessage(message: Message) { * await message.reply('Hello! How can I help you?'); * } * * @MessageHandler() * async handleAnyMessage(message: Message) { * console.log(`Received a message: ${message.content}`); * } * ``` */export function MessageHandler(a){return function(b,c){var d=Reflect.getMetadata(MESSAGE_HANDLER_METADATA_KEY,b)||[];d.push({keyword:a,method:c.toString()}),Reflect.defineMetadata(MESSAGE_HANDLER_METADATA_KEY,d,b)}}/** * Decorator to register reaction handlers in the controller. * * @param emoji - Optional emoji name to filter reactions this handler should respond to. * * @example * ```typescript * @ReactionHandler('👍') * async handleThumbsUpReaction(reaction: MessageReaction, { user }: ReactionHandlerOptions) { * console.log(`User ${user.username} reacted with 👍`); * } * * @ReactionHandler() * async handleAnyReaction(reaction: MessageReaction, { user }: ReactionHandlerOptions) { * console.log(`User ${user.username} reacted with ${reaction.emoji.name}`); * } * ``` */export function ReactionHandler(a){return function(b,c){var d=Reflect.getMetadata(REACTION_HANDLER_METADATA_KEY,b)||[];d.push({emoji:a,method:c.toString()}),Reflect.defineMetadata(REACTION_HANDLER_METADATA_KEY,d,b)}}/** * Retrieves reaction handlers metadata from a given controller. * * @param controller - The controller class instance. * @returns An array of reaction handler metadata objects. */export function getReactionHandlers(a){return Reflect.getMetadata(REACTION_HANDLER_METADATA_KEY,a)||[]}/** * Retrieves message handlers metadata from a given controller. * * @param controller - The controller class instance. * @returns An array of message handler method names. */export function getMessageHandlers(a){return Reflect.getMetadata(MESSAGE_HANDLER_METADATA_KEY,a)||[]}/** * Helper function to create regex and parameter mappings from a pattern string. * * @param pattern - The pattern string to parse. * @returns An object containing the generated regex and parameter names. */function createRegexFromPattern(a){var b=[],c=a.replace(/[/\\^$*+?.()|[\]]/g,"\\$&"),d=c.replace(/\{(\w+)}/g,function(a,c){if(!/^\w+$/.test(c))throw new Error("Invalid parameter name: ".concat(c,". Parameter names must be alphanumeric."));return b.push(c),"(?<".concat(c,">[a-zA-Z0-9]+)")}),e=new RegExp("^".concat(d,"$"));// Escape special characters except for {} and - // Removed hyphen `-` from this list // Replace placeholders with named capturing groups // Construct the final regex return{regex:e,params:b}}/** * Decorator to register command methods in a controller. * * @param commandName - The name or pattern of the command. * @param builderOrType - A command builder class or a command type from `CommandType`. * * @example * ```typescript * @Command('help', CommandType.SLASH) * public async handleHelp(interaction: ChatInputCommandInteraction) { * await interaction.reply('This is the help command!') * } * * @Command('stats-{id}', CommandType.BUTTON) * public async handleStats(message: ButtonInteraction, { id }) { * await message.reply(`Fetching stats for ID: ${id}`); * } * ``` */export function Command(a,b){return function(c,d,e){var f=e.value;if(!f)throw new Error("Missing implementation for method ".concat(d));// Wrap original method for interaction type validation e.value=function(a,b){var c=h===CommandType.BUTTON&&a instanceof ButtonInteraction||h===CommandType.SELECT_MENU&&a instanceof StringSelectMenuInteraction||h===CommandType.SLASH&&a instanceof ChatInputCommandInteraction||h===CommandType.CONTEXT_MENU&&a instanceof ContextMenuCommandInteraction||h===CommandType.MODAL_SUBMIT&&a instanceof ModalSubmitInteraction;if(!c)throw new Error("Invalid interaction type passed to @Command for method: ".concat(d));return f.apply(this,[a,b])};// Retrieve existing metadata or initialize it var g,h,i,j=Reflect.getMetadata(COMMAND_METADATA_KEY,c)||{},k=[];// Determine command type and builder if("function"==typeof b){var l=new b;if(g=l.build(a),h=Reflect.getMetadata("commandType",b),!(h in CommandType))throw new Error("Metadata for 'commandType' is missing on builder ".concat(b.name))}else h=b;if(h!==CommandType.SLASH&&h!==CommandType.CONTEXT_MENU){var m=createRegexFromPattern(a),n=m.regex,o=m.params;i=n,k=o}// Ensure commandName supports multiple entries j[a]||(j[a]=[]),j[a].push({methodName:d,builder:g,type:h,regex:i,dynamicParams:k}),Reflect.defineMetadata(COMMAND_METADATA_KEY,j,c)}}/** * Retrieves the command map for a given controller. * * @param controller - The controller class instance. * @returns A record containing command metadata indexed by command names. */export function getCommandMap(a){return Reflect.getMetadata(COMMAND_METADATA_KEY,a)}/** * Decorator to mark a class as a controller that can later be registered to the App class `(app.ts)` using the `@MeoCord` decorator. * * @example * ```typescript * @Controller() * export class PingSlashController { * constructor(private pingService: PingService) {} * * @Command('ping', PingCommandBuilder) * async ping(interaction: ChatInputCommandInteraction) { * const response = await this.pingService.handlePing() * await interaction.reply(response) * } * } * ``` */export function Controller(){return function(a){Reflect.hasMetadata("inversify:injectable",a)||injectable()(a);var b=Reflect.getMetadata("design:paramtypes",a)||[];b.map(function(a){mainContainer.isBound(a)||(!Reflect.hasMetadata("inversify:injectable",a)&&injectable()(a),mainContainer.bind(a).toSelf().inSingletonScope())}),Reflect.defineMetadata("inversify:container",mainContainer,a)}}