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
JavaScript
/**
* 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)}}