meocord 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +152 -140
  3. package/dist/cjs/_shared/meocord.app-CHjdCAA_.cjs +496 -0
  4. package/dist/cjs/_shared/theme-BdtbtMZX.cjs +176 -0
  5. package/dist/cjs/common/index.cjs +16 -0
  6. package/dist/cjs/core/index.cjs +35 -0
  7. package/dist/cjs/decorator/index.cjs +360 -0
  8. package/dist/cjs/enum/index.cjs +20 -0
  9. package/dist/cjs/interface/index.cjs +2 -0
  10. package/dist/esm/bin/generator.js +92 -0
  11. package/dist/esm/bin/helper/controller-generator.helper.js +105 -0
  12. package/dist/esm/bin/helper/guard-generator.helper.js +33 -0
  13. package/dist/esm/bin/helper/service-generator.helper.js +33 -0
  14. package/dist/esm/bin/meocord.js +333 -0
  15. package/dist/esm/common/index.js +2 -0
  16. package/dist/esm/common/logger.js +72 -0
  17. package/dist/{core/index.d.ts → esm/common/theme.js} +8 -2
  18. package/dist/esm/core/index.js +1 -0
  19. package/dist/esm/core/meocord-factory.js +28 -0
  20. package/dist/esm/core/meocord.app.js +267 -0
  21. package/dist/esm/decorator/app.decorator.js +99 -0
  22. package/dist/esm/decorator/command-builder.decorator.js +32 -0
  23. package/dist/esm/decorator/container.js +6 -0
  24. package/dist/esm/decorator/controller.decorator.js +218 -0
  25. package/dist/esm/decorator/guard.decorator.js +165 -0
  26. package/dist/esm/decorator/index.js +6 -0
  27. package/dist/esm/decorator/service.decorator.js +58 -0
  28. package/dist/esm/enum/controller.enum.js +43 -0
  29. package/dist/esm/enum/index.js +1 -0
  30. package/dist/esm/interface/index.js +1 -0
  31. package/dist/esm/package.json.js +5 -0
  32. package/dist/esm/util/common.util.js +68 -0
  33. package/dist/esm/util/embed.util.js +13 -0
  34. package/dist/esm/util/generator-cli.util.js +107 -0
  35. package/dist/{util → esm/util}/json.util.js +10 -6
  36. package/dist/esm/util/meocord-cli.util.js +172 -0
  37. package/dist/esm/util/meocord-config-loader.util.js +48 -0
  38. package/dist/esm/util/tsconfig.util.js +83 -0
  39. package/dist/{util → esm/util}/wait.util.js +5 -1
  40. package/dist/{common/logger.d.ts → types/common/index.d.ts} +30 -1
  41. package/dist/{core/meocord.app.d.ts → types/core/index.d.ts} +30 -2
  42. package/dist/types/decorator/index.d.ts +425 -0
  43. package/dist/types/enum/index.d.ts +18 -0
  44. package/dist/{interface → types/interface}/index.d.ts +11 -7
  45. package/package.json +64 -48
  46. package/webpack.config.js +2 -2
  47. package/dist/bin/generator.d.ts +0 -29
  48. package/dist/bin/generator.js +0 -17
  49. package/dist/bin/helper/controller-generator.helper.d.ts +0 -67
  50. package/dist/bin/helper/controller-generator.helper.js +0 -50
  51. package/dist/bin/helper/guard-generator.helper.d.ts +0 -32
  52. package/dist/bin/helper/guard-generator.helper.js +0 -25
  53. package/dist/bin/helper/service-generator.helper.d.ts +0 -32
  54. package/dist/bin/helper/service-generator.helper.js +0 -25
  55. package/dist/bin/meocord.d.ts +0 -19
  56. package/dist/bin/meocord.js +0 -34
  57. package/dist/common/index.d.ts +0 -19
  58. package/dist/common/index.js +0 -17
  59. package/dist/common/logger.js +0 -17
  60. package/dist/common/theme.d.ts +0 -24
  61. package/dist/common/theme.js +0 -17
  62. package/dist/core/index.js +0 -17
  63. package/dist/core/meocord-factory.d.ts +0 -24
  64. package/dist/core/meocord-factory.js +0 -17
  65. package/dist/core/meocord.app.js +0 -17
  66. package/dist/decorator/app.decorator.d.ts +0 -59
  67. package/dist/decorator/app.decorator.js +0 -61
  68. package/dist/decorator/command-builder.decorator.d.ts +0 -39
  69. package/dist/decorator/command-builder.decorator.js +0 -35
  70. package/dist/decorator/container.d.ts +0 -20
  71. package/dist/decorator/container.js +0 -17
  72. package/dist/decorator/controller.decorator.d.ts +0 -125
  73. package/dist/decorator/controller.decorator.js +0 -113
  74. package/dist/decorator/guard.decorator.d.ts +0 -101
  75. package/dist/decorator/guard.decorator.js +0 -94
  76. package/dist/decorator/index.d.ts +0 -23
  77. package/dist/decorator/index.js +0 -17
  78. package/dist/decorator/service.decorator.d.ts +0 -36
  79. package/dist/decorator/service.decorator.js +0 -36
  80. package/dist/enum/controller.enum.d.ts +0 -42
  81. package/dist/enum/controller.enum.js +0 -19
  82. package/dist/enum/index.d.ts +0 -18
  83. package/dist/enum/index.js +0 -17
  84. package/dist/interface/command-decorator.interface.d.ts +0 -43
  85. package/dist/interface/command-decorator.interface.js +0 -1
  86. package/dist/interface/index.js +0 -1
  87. package/dist/util/common.util.d.ts +0 -40
  88. package/dist/util/common.util.js +0 -38
  89. package/dist/util/embed.util.d.ts +0 -19
  90. package/dist/util/embed.util.js +0 -17
  91. package/dist/util/generator-cli.util.d.ts +0 -65
  92. package/dist/util/generator-cli.util.js +0 -49
  93. package/dist/util/index.d.ts +0 -18
  94. package/dist/util/index.js +0 -17
  95. package/dist/util/json.util.d.ts +0 -27
  96. package/dist/util/meocord-cli.util.d.ts +0 -62
  97. package/dist/util/meocord-cli.util.js +0 -50
  98. package/dist/util/meocord-config-loader.util.d.ts +0 -32
  99. package/dist/util/meocord-config-loader.util.js +0 -34
  100. package/dist/util/tsconfig.util.d.ts +0 -29
  101. package/dist/util/tsconfig.util.js +0 -32
  102. package/dist/util/wait.util.d.ts +0 -18
  103. /package/dist/{bin → esm/bin}/builder-template/builder/context-menu.builder.template +0 -0
  104. /package/dist/{bin → esm/bin}/builder-template/builder/slash.builder.template +0 -0
  105. /package/dist/{bin → esm/bin}/builder-template/controller/button.controller.template +0 -0
  106. /package/dist/{bin → esm/bin}/builder-template/controller/context-menu.controller.template +0 -0
  107. /package/dist/{bin → esm/bin}/builder-template/controller/message.controller.template +0 -0
  108. /package/dist/{bin → esm/bin}/builder-template/controller/modal-submit.controller.template +0 -0
  109. /package/dist/{bin → esm/bin}/builder-template/controller/reaction.controller.template +0 -0
  110. /package/dist/{bin → esm/bin}/builder-template/controller/select-menu.controller.template +0 -0
  111. /package/dist/{bin → esm/bin}/builder-template/controller/slash.controller.template +0 -0
  112. /package/dist/{bin → esm/bin}/builder-template/guard.template +0 -0
  113. /package/dist/{bin → esm/bin}/builder-template/service.template +0 -0
@@ -0,0 +1,496 @@
1
+ 'use strict';
2
+
3
+ var discord_js = require('discord.js');
4
+ var theme = require('./theme-BdtbtMZX.cjs');
5
+ require('reflect-metadata');
6
+ var inversify = require('inversify');
7
+ var enum_index = require('../enum/index.cjs');
8
+ var lodashEs = require('lodash-es');
9
+
10
+ const mainContainer = new inversify.Container();
11
+
12
+ const COMMAND_METADATA_KEY = Symbol('commands');
13
+ const MESSAGE_HANDLER_METADATA_KEY = Symbol('message_handlers');
14
+ const REACTION_HANDLER_METADATA_KEY = Symbol('reaction_handlers');
15
+ /**
16
+ * Decorator to register message handlers in the controller.
17
+ *
18
+ * @param keyword - An optional keyword to filter messages this handler should respond to.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * @MessageHandler('hello')
23
+ * async handleHelloMessage(message: Message) {
24
+ * await message.reply('Hello! How can I help you?');
25
+ * }
26
+ *
27
+ * @MessageHandler()
28
+ * async handleAnyMessage(message: Message) {
29
+ * console.log(`Received a message: ${message.content}`);
30
+ * }
31
+ * ```
32
+ */ function MessageHandler(keyword) {
33
+ return function(target, propertyKey, _descriptor) {
34
+ const handlers = Reflect.getMetadata(MESSAGE_HANDLER_METADATA_KEY, target) || [];
35
+ handlers.push({
36
+ keyword,
37
+ method: propertyKey.toString()
38
+ });
39
+ Reflect.defineMetadata(MESSAGE_HANDLER_METADATA_KEY, handlers, target);
40
+ };
41
+ }
42
+ /**
43
+ * Decorator to register reaction handlers in the controller.
44
+ *
45
+ * @param emoji - Optional emoji name to filter reactions this handler should respond to.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * @ReactionHandler('👍')
50
+ * async handleThumbsUpReaction(reaction: MessageReaction, { user }: ReactionHandlerOptions) {
51
+ * console.log(`User ${user.username} reacted with 👍`);
52
+ * }
53
+ *
54
+ * @ReactionHandler()
55
+ * async handleAnyReaction(reaction: MessageReaction, { user }: ReactionHandlerOptions) {
56
+ * console.log(`User ${user.username} reacted with ${reaction.emoji.name}`);
57
+ * }
58
+ * ```
59
+ */ function ReactionHandler(emoji) {
60
+ return function(target, propertyKey, _descriptor) {
61
+ const handlers = Reflect.getMetadata(REACTION_HANDLER_METADATA_KEY, target) || [];
62
+ handlers.push({
63
+ emoji,
64
+ method: propertyKey.toString()
65
+ });
66
+ Reflect.defineMetadata(REACTION_HANDLER_METADATA_KEY, handlers, target);
67
+ };
68
+ }
69
+ /**
70
+ * Retrieves reaction handlers metadata from a given controller.
71
+ *
72
+ * @param controller - The controller class instance.
73
+ * @returns An array of reaction handler metadata objects.
74
+ */ function getReactionHandlers(controller) {
75
+ return Reflect.getMetadata(REACTION_HANDLER_METADATA_KEY, controller) || [];
76
+ }
77
+ /**
78
+ * Retrieves message handlers metadata from a given controller.
79
+ *
80
+ * @param controller - The controller class instance.
81
+ * @returns An array of message handler method names.
82
+ */ function getMessageHandlers(controller) {
83
+ return Reflect.getMetadata(MESSAGE_HANDLER_METADATA_KEY, controller) || [];
84
+ }
85
+ /**
86
+ * Helper function to create regex and parameter mappings from a pattern string.
87
+ *
88
+ * @param pattern - The pattern string to parse.
89
+ * @returns An object containing the generated regex and parameter names.
90
+ */ function createRegexFromPattern(pattern) {
91
+ const params = [];
92
+ // Escape special characters except for {} and -
93
+ const escapedPattern = pattern.replace(/[/\\^$*+?.()|[\]]/g, '\\$&') // Removed hyphen `-` from this list
94
+ ;
95
+ // Replace placeholders with named capturing groups
96
+ const regexPattern = escapedPattern.replace(/\{(\w+)}/g, (_, param)=>{
97
+ if (!/^\w+$/.test(param)) {
98
+ throw new Error(`Invalid parameter name: ${param}. Parameter names must be alphanumeric.`);
99
+ }
100
+ params.push(param);
101
+ return `(?<${param}>[a-zA-Z0-9]+)`;
102
+ });
103
+ // Construct the final regex
104
+ const regex = new RegExp(`^${regexPattern}$`);
105
+ return {
106
+ regex,
107
+ params
108
+ };
109
+ }
110
+ /**
111
+ * Decorator to register command methods in a controller.
112
+ *
113
+ * @param commandName - The name or pattern of the command.
114
+ * @param builderOrType - A command builder class or a command type from `CommandType`.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * @Command('help', CommandType.SLASH)
119
+ * public async handleHelp(interaction: ChatInputCommandInteraction) {
120
+ * await interaction.reply('This is the help command!')
121
+ * }
122
+ *
123
+ * @Command('stats-{id}', CommandType.BUTTON)
124
+ * public async handleStats(message: ButtonInteraction, { id }) {
125
+ * await message.reply(`Fetching stats for ID: ${id}`);
126
+ * }
127
+ * ```
128
+ */ function Command(commandName, builderOrType) {
129
+ return function(target, propertyKey, _descriptor) {
130
+ const originalMethod = _descriptor.value;
131
+ if (!originalMethod) {
132
+ throw new Error(`Missing implementation for method ${propertyKey}`);
133
+ }
134
+ // Wrap original method for interaction type validation
135
+ _descriptor.value = function(interaction, params) {
136
+ const expectedInteraction = commandType === enum_index.CommandType.BUTTON && interaction instanceof discord_js.ButtonInteraction || commandType === enum_index.CommandType.SELECT_MENU && interaction instanceof discord_js.StringSelectMenuInteraction || commandType === enum_index.CommandType.SLASH && interaction instanceof discord_js.ChatInputCommandInteraction || commandType === enum_index.CommandType.CONTEXT_MENU && interaction instanceof discord_js.ContextMenuCommandInteraction || commandType === enum_index.CommandType.MODAL_SUBMIT && interaction instanceof discord_js.ModalSubmitInteraction;
137
+ if (!expectedInteraction) {
138
+ throw new Error(`Invalid interaction type passed to @Command for method: ${propertyKey}`);
139
+ }
140
+ return originalMethod.apply(this, [
141
+ interaction,
142
+ params
143
+ ]);
144
+ };
145
+ // Retrieve existing metadata or initialize it
146
+ const commands = Reflect.getMetadata(COMMAND_METADATA_KEY, target) || {};
147
+ let builderInstance;
148
+ let commandType;
149
+ let regex;
150
+ let dynamicParams = [];
151
+ // Determine command type and builder
152
+ if (typeof builderOrType === 'function') {
153
+ const builderObj = new builderOrType();
154
+ builderInstance = builderObj.build(commandName);
155
+ commandType = Reflect.getMetadata('commandType', builderOrType);
156
+ if (!(commandType in enum_index.CommandType)) {
157
+ throw new Error(`Metadata for 'commandType' is missing on builder ${builderOrType.name}`);
158
+ }
159
+ } else {
160
+ commandType = builderOrType;
161
+ }
162
+ if (commandType !== enum_index.CommandType.SLASH && commandType !== enum_index.CommandType.CONTEXT_MENU) {
163
+ const { regex: generatedRegex, params } = createRegexFromPattern(commandName);
164
+ regex = generatedRegex;
165
+ dynamicParams = params;
166
+ }
167
+ // Ensure commandName supports multiple entries
168
+ if (!commands[commandName]) {
169
+ commands[commandName] = [];
170
+ }
171
+ commands[commandName].push({
172
+ methodName: propertyKey,
173
+ builder: builderInstance,
174
+ type: commandType,
175
+ regex,
176
+ dynamicParams
177
+ });
178
+ Reflect.defineMetadata(COMMAND_METADATA_KEY, commands, target);
179
+ };
180
+ }
181
+ /**
182
+ * Retrieves the command map for a given controller.
183
+ *
184
+ * @param controller - The controller class instance.
185
+ * @returns A record containing command metadata indexed by command names.
186
+ */ function getCommandMap(controller) {
187
+ return Reflect.getMetadata(COMMAND_METADATA_KEY, controller);
188
+ }
189
+ /**
190
+ * Decorator to mark a class as a controller that can later be registered to the App class `(app.ts)` using the `@MeoCord` decorator.
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * @Controller()
195
+ * export class PingSlashController {
196
+ * constructor(private pingService: PingService) {}
197
+ *
198
+ * @Command('ping', PingCommandBuilder)
199
+ * async ping(interaction: ChatInputCommandInteraction) {
200
+ * const response = await this.pingService.handlePing()
201
+ * await interaction.reply(response)
202
+ * }
203
+ * }
204
+ * ```
205
+ */ function Controller() {
206
+ return function(target) {
207
+ if (!Reflect.hasMetadata('inversify:injectable', target)) {
208
+ inversify.injectable()(target);
209
+ }
210
+ const injectables = Reflect.getMetadata('design:paramtypes', target) || [];
211
+ injectables.map((dep)=>{
212
+ if (!mainContainer.isBound(dep)) {
213
+ if (!Reflect.hasMetadata('inversify:injectable', dep)) {
214
+ inversify.injectable()(dep);
215
+ }
216
+ mainContainer.bind(dep).toSelf().inSingletonScope();
217
+ }
218
+ });
219
+ Reflect.defineMetadata('inversify:container', mainContainer, target);
220
+ };
221
+ }
222
+
223
+ const createErrorEmbed = (description)=>{
224
+ const embed = new discord_js.EmbedBuilder();
225
+ embed.setColor(theme.Theme.errorColor);
226
+ embed.setTitle('Oops!');
227
+ embed.setDescription(description);
228
+ return embed;
229
+ };
230
+
231
+ class MeoCordApp {
232
+ async start() {
233
+ try {
234
+ this.logger.log('Starting bot...');
235
+ this.bot.on('clientReady', async ()=>{
236
+ this.activityInterval = setInterval(()=>{
237
+ this.bot.user?.setActivity(lodashEs.sample(this.activities));
238
+ }, 10000);
239
+ await this.registerCommands();
240
+ });
241
+ this.bot.on('interactionCreate', async (interaction)=>{
242
+ await this.handleInteraction(interaction);
243
+ });
244
+ this.bot.on('messageCreate', async (message)=>{
245
+ await this.handleMessage(message);
246
+ });
247
+ this.bot.on('messageReactionAdd', async (reaction, user)=>{
248
+ await this.handleReaction(reaction, {
249
+ user,
250
+ action: enum_index.ReactionHandlerAction.ADD
251
+ });
252
+ });
253
+ this.bot.on('messageReactionRemove', async (reaction, user)=>{
254
+ await this.handleReaction(reaction, {
255
+ user,
256
+ action: enum_index.ReactionHandlerAction.REMOVE
257
+ });
258
+ });
259
+ await this.bot.login(this.discordToken);
260
+ this.logger.log('Bot is online!');
261
+ } catch (error) {
262
+ this.logger.error('Error during bot startup:', error);
263
+ }
264
+ }
265
+ async registerCommands() {
266
+ const builders = [];
267
+ for (const controller of this.controllers){
268
+ const commandMap = getCommandMap(controller);
269
+ for(const commandName in commandMap){
270
+ const commandMetadataArray = commandMap[commandName];
271
+ if (!Array.isArray(commandMetadataArray)) continue;
272
+ for (const { builder, type } of commandMetadataArray){
273
+ if (type in enum_index.CommandType && builder) {
274
+ builders.push(builder);
275
+ }
276
+ }
277
+ }
278
+ }
279
+ try {
280
+ if (this.bot.application) {
281
+ await this.bot.application.commands.set(builders);
282
+ this.logger.log(`Registered ${builders.length} bot commands:`, builders.map((builder)=>{
283
+ const json = typeof builder.toJSON === 'function' ? builder.toJSON() : builder;
284
+ const typeName = json?.type === 1 ? 'SlashCommand' : json?.type === 2 ? 'UserContextMenu' : json?.type === 3 ? 'MessageContextMenu' : builder instanceof discord_js.SlashCommandBuilder ? 'SlashCommand' : 'Command';
285
+ const subCommands = Array.isArray(json?.options) && json.options.length ? json.options.map((opt)=>({
286
+ name: opt.name,
287
+ options: opt.options.map((opt)=>opt.name)
288
+ })) : undefined;
289
+ const name = json?.name || builder.name;
290
+ return subCommands ? {
291
+ type: typeName,
292
+ name,
293
+ subCommands
294
+ } : {
295
+ type: typeName,
296
+ name
297
+ };
298
+ }));
299
+ }
300
+ } catch (error) {
301
+ this.logger.error('Error during command registration:', error);
302
+ }
303
+ }
304
+ async handleInteraction(interaction) {
305
+ for (const controller of this.controllers){
306
+ let controllerInstance = this.controllerInstancesCache.get(controller);
307
+ if (!controllerInstance) {
308
+ controllerInstance = mainContainer.get(controller.constructor);
309
+ this.controllerInstancesCache.set(controller, controllerInstance);
310
+ }
311
+ const commandMap = getCommandMap(controllerInstance);
312
+ if (!commandMap) continue;
313
+ let commandMetadataArray = undefined;
314
+ let commandIdentifier = undefined;
315
+ if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) {
316
+ commandIdentifier = interaction.commandName;
317
+ commandMetadataArray = commandMap[commandIdentifier];
318
+ } else if (interaction.isButton() || interaction.isStringSelectMenu() || interaction.isModalSubmit()) {
319
+ commandIdentifier = interaction.customId;
320
+ const foundEntry = Object.entries(commandMap).find(([commandName, metaArray])=>{
321
+ if (!Array.isArray(metaArray)) return false;
322
+ return metaArray.some((meta)=>{
323
+ if (!meta.regex || !commandIdentifier) return false;
324
+ const match = meta.regex.exec(commandIdentifier);
325
+ if (match?.groups) {
326
+ interaction.dynamicParams = match.groups;
327
+ return true;
328
+ }
329
+ return commandIdentifier === commandName;
330
+ });
331
+ });
332
+ if (foundEntry) {
333
+ commandMetadataArray = foundEntry[1];
334
+ }
335
+ }
336
+ if (commandMetadataArray && commandMetadataArray.length > 0) {
337
+ const commandMetadata = commandMetadataArray[0];
338
+ const { methodName, type } = commandMetadata;
339
+ try {
340
+ if (type === enum_index.CommandType.SLASH && interaction.isChatInputCommand() || type === enum_index.CommandType.BUTTON && interaction.isButton() || type === enum_index.CommandType.SELECT_MENU && interaction.isStringSelectMenu() || type === enum_index.CommandType.CONTEXT_MENU && interaction.isUserContextMenuCommand() || type === enum_index.CommandType.CONTEXT_MENU && interaction.isMessageContextMenuCommand() || type === enum_index.CommandType.MODAL_SUBMIT && interaction.isModalSubmit()) {
341
+ this.logger.log('[INTERACTION]', `[${enum_index.CommandType[type]}]`, `[${methodName}]`);
342
+ let dynamicParams = {};
343
+ if (interaction.isChatInputCommand() && interaction.options) {
344
+ dynamicParams = interaction.options.data.reduce((acc, opt)=>{
345
+ acc[opt.name] = opt.value;
346
+ return acc;
347
+ }, {});
348
+ } else if (interaction.isButton() || interaction.isStringSelectMenu() || interaction.isModalSubmit()) {
349
+ dynamicParams = interaction.dynamicParams || {};
350
+ }
351
+ await controllerInstance[methodName](interaction, dynamicParams);
352
+ return;
353
+ } else {
354
+ this.logger.debug(type, methodName, enum_index.CommandType.BUTTON, interaction.isButton());
355
+ this.logger.warn(`Interaction type mismatch for command "${commandIdentifier}". Interaction type: ${interaction.type}.`);
356
+ }
357
+ } catch (error) {
358
+ this.logger.error(`Error executing command "${commandIdentifier}":`, error);
359
+ if (interaction.isRepliable()) {
360
+ const embed = createErrorEmbed('An error occurred while executing the command.');
361
+ await interaction.reply({
362
+ embeds: [
363
+ embed
364
+ ],
365
+ flags: discord_js.MessageFlagsBitField.Flags.Ephemeral
366
+ });
367
+ }
368
+ }
369
+ return;
370
+ }
371
+ }
372
+ // If no matching command is found
373
+ if (interaction.isRepliable()) {
374
+ const embed = createErrorEmbed('Command not found!');
375
+ await interaction.reply({
376
+ embeds: [
377
+ embed
378
+ ],
379
+ flags: discord_js.MessageFlagsBitField.Flags.Ephemeral
380
+ });
381
+ }
382
+ }
383
+ async handleMessage(message) {
384
+ if (message.author.bot || !message.content?.trim()) return;
385
+ const messageContent = message.content.trim();
386
+ const relevantControllers = this.controllers.filter((controller)=>{
387
+ const messageHandlers = getMessageHandlers(controller);
388
+ return messageHandlers.some((handler)=>!handler.keyword || handler.keyword === messageContent);
389
+ });
390
+ for (const controller of relevantControllers){
391
+ let controllerInstance = this.controllerInstancesCache.get(controller.constructor);
392
+ if (!controllerInstance) {
393
+ const container = Reflect.getMetadata('inversify:container', controller.constructor);
394
+ controllerInstance = container.get(controller.constructor, {
395
+ autobind: true
396
+ });
397
+ this.controllerInstancesCache.set(controller.constructor, controllerInstance);
398
+ }
399
+ let messageHandlers = getMessageHandlers(controller);
400
+ messageHandlers = messageHandlers.sort((a, b)=>{
401
+ if (a.keyword && !b.keyword) return -1;
402
+ if (!a.keyword && b.keyword) return 1;
403
+ return 0;
404
+ });
405
+ for (const handler of messageHandlers){
406
+ const { keyword, method } = handler;
407
+ if (!keyword || keyword === messageContent) {
408
+ try {
409
+ await controllerInstance[method](message);
410
+ } catch (error) {
411
+ this.logger.error(`Error handling message "${messageContent}" for method "${method}":`, error);
412
+ }
413
+ }
414
+ }
415
+ }
416
+ }
417
+ async handleReaction(reaction, { user, action }) {
418
+ await reaction.message.fetch();
419
+ const relevantControllers = this.controllers.filter((controller)=>{
420
+ const reactionHandlers = getReactionHandlers(controller);
421
+ return reactionHandlers.some((handler)=>!handler.emoji || handler.emoji === reaction.emoji.name);
422
+ });
423
+ for (const controller of relevantControllers){
424
+ let controllerInstance = this.controllerInstancesCache.get(controller.constructor);
425
+ if (!controllerInstance) {
426
+ const container = Reflect.getMetadata('inversify:container', controller.constructor);
427
+ controllerInstance = container.get(controller.constructor, {
428
+ autobind: true
429
+ });
430
+ this.controllerInstancesCache.set(controller.constructor, controllerInstance);
431
+ }
432
+ let reactionHandlers = getReactionHandlers(controller);
433
+ reactionHandlers = reactionHandlers.sort((a, b)=>{
434
+ if (a.emoji && !b.emoji) return -1;
435
+ if (!a.emoji && b.emoji) return 1;
436
+ return 0;
437
+ });
438
+ for (const handler of reactionHandlers){
439
+ const { emoji, method } = handler;
440
+ if (!emoji || emoji === reaction.emoji.name) {
441
+ try {
442
+ await controllerInstance[method](reaction, {
443
+ user,
444
+ action
445
+ });
446
+ } catch (error) {
447
+ this.logger.error(`Error handling reaction "${reaction.emoji.name}" for method "${method}":`, error);
448
+ }
449
+ }
450
+ }
451
+ }
452
+ }
453
+ async gracefulShutdown() {
454
+ if (this.isShuttingDown) {
455
+ // Second signal received while shutting down — force exit immediately
456
+ process.exit(1);
457
+ }
458
+ if (this.bot) {
459
+ try {
460
+ this.isShuttingDown = true;
461
+ this.logger.log('Shutting down bot...');
462
+ if (this.activityInterval) clearInterval(this.activityInterval);
463
+ this.bot.removeAllListeners();
464
+ await this.bot.destroy();
465
+ this.logger.log('Bot has shut down');
466
+ process.exit(0);
467
+ } catch (error) {
468
+ this.logger.error('Error during shutdown:', error);
469
+ process.exit(1);
470
+ }
471
+ }
472
+ }
473
+ constructor(controllers, discordClient, discordToken, activities){
474
+ this.controllers = controllers;
475
+ this.discordClient = discordClient;
476
+ this.discordToken = discordToken;
477
+ this.activities = activities;
478
+ this.logger = new theme.Logger(MeoCordApp.name);
479
+ this.isShuttingDown = false;
480
+ this.activityInterval = null;
481
+ this.controllerInstancesCache = new Map();
482
+ this.bot = this.discordClient;
483
+ process.on('SIGINT', ()=>this.gracefulShutdown());
484
+ process.on('SIGTERM', ()=>this.gracefulShutdown());
485
+ }
486
+ }
487
+
488
+ exports.Command = Command;
489
+ exports.Controller = Controller;
490
+ exports.MeoCordApp = MeoCordApp;
491
+ exports.MessageHandler = MessageHandler;
492
+ exports.ReactionHandler = ReactionHandler;
493
+ exports.getCommandMap = getCommandMap;
494
+ exports.getMessageHandlers = getMessageHandlers;
495
+ exports.getReactionHandlers = getReactionHandlers;
496
+ exports.mainContainer = mainContainer;
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ var node_util = require('node:util');
4
+ var dayjs = require('dayjs');
5
+ var utc = require('dayjs/plugin/utc.js');
6
+ var timezone = require('dayjs/plugin/timezone.js');
7
+ var path = require('path');
8
+ var fs = require('fs');
9
+ var jiti = require('jiti');
10
+ var chalk = require('chalk');
11
+
12
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
+ /**
14
+ * MeoCord Framework
15
+ * Copyright (C) 2025 Ukasyah Rahmatullah Zada
16
+ *
17
+ * This program is free software: you can redistribute it and/or modify
18
+ * it under the terms of the GNU General Public License as published by
19
+ * the Free Software Foundation, either version 3 of the License, or
20
+ * (at your option) any later version.
21
+ *
22
+ * This program is distributed in the hope that it will be useful,
23
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
+ * GNU General Public License for more details.
26
+ *
27
+ * You should have received a copy of the GNU General Public License
28
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
29
+ */ /**
30
+ * Helper function to fix common JSON formatting issues in tsconfig.json, such as:
31
+ * - Removing single-line comments.
32
+ * - Removing trailing commas.
33
+ * - Stripping newlines.
34
+ *
35
+ * @param {string} jsonString - The raw JSON string to fix.
36
+ * @returns {string} The corrected JSON string.
37
+ */ function fixJSON(jsonString) {
38
+ return jsonString.replace(/\/\/.*$/gm, '') // Remove single-line comments
39
+ .replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas before } or ]
40
+ .replace(/,\s*$/, '') // Remove trailing commas at the end of the file
41
+ .replace(/^\s*[\r\n]/gm, '') // Replace empty lines only
42
+ ;
43
+ }
44
+
45
+ /**
46
+ * Loads the MeoCord configuration file (meocord.config.ts) directly at runtime
47
+ * using jiti, without a separate compilation step.
48
+ *
49
+ * @returns {MeoCordConfig | undefined} The loaded configuration object, or undefined if loading fails.
50
+ */ function loadMeoCordConfig() {
51
+ const configPath = path.resolve(process.cwd(), 'meocord.config.ts');
52
+ if (!fs.existsSync(configPath)) {
53
+ return undefined;
54
+ }
55
+ try {
56
+ // Read user's tsconfig.json to extract path aliases for jiti
57
+ const tsConfigPath = path.resolve(process.cwd(), 'tsconfig.json');
58
+ const aliases = {};
59
+ if (fs.existsSync(tsConfigPath)) {
60
+ const tsConfig = JSON.parse(fixJSON(fs.readFileSync(tsConfigPath, 'utf-8')));
61
+ const paths = tsConfig?.compilerOptions?.paths;
62
+ if (paths) {
63
+ for (const [key, values] of Object.entries(paths)){
64
+ // Convert TS path alias format "@src/*" -> ["./src/*"] to jiti alias format
65
+ const aliasKey = key.replace('/*', '');
66
+ const aliasValue = path.resolve(process.cwd(), values[0].replace('/*', ''));
67
+ aliases[aliasKey] = aliasValue;
68
+ }
69
+ }
70
+ }
71
+ const jiti$1 = jiti.createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/theme-BdtbtMZX.cjs', document.baseURI).href)), {
72
+ interopDefault: true,
73
+ alias: aliases,
74
+ moduleCache: false
75
+ });
76
+ return jiti$1(configPath);
77
+ } catch (error) {
78
+ if (error instanceof Error) {
79
+ console.error(`[MeoCord] Failed to load config: ${error.message}`);
80
+ } else {
81
+ console.error(`[MeoCord] Failed to load config: Unknown error`);
82
+ }
83
+ return undefined;
84
+ }
85
+ }
86
+
87
+ dayjs.extend(utc);
88
+ dayjs.extend(timezone);
89
+ class Logger {
90
+ log(...args) {
91
+ this.logWithContext('log', args);
92
+ }
93
+ info(...args) {
94
+ this.logWithContext('log', args);
95
+ }
96
+ warn(...args) {
97
+ this.logWithContext('warn', args);
98
+ }
99
+ error(...args) {
100
+ this.logWithContext('error', args);
101
+ }
102
+ debug(...args) {
103
+ this.logWithContext('debug', args);
104
+ }
105
+ verbose(...args) {
106
+ this.logWithContext('log', args);
107
+ }
108
+ formatMessage(message, logType) {
109
+ if (typeof message === 'object' && message !== null) {
110
+ return node_util.inspect(message, {
111
+ showHidden: true,
112
+ depth: null,
113
+ colors: true,
114
+ compact: false,
115
+ showProxy: true
116
+ });
117
+ }
118
+ return (this.colorMap[logType] || ((msg)=>msg))(message);
119
+ }
120
+ logWithContext(logLevel, messages) {
121
+ if (messages.length === 0) return;
122
+ const config = loadMeoCordConfig();
123
+ const logType = logLevel.toUpperCase();
124
+ const applyColor = this.colorMap[logType] || ((msg)=>msg);
125
+ const formattedMessages = messages.map((message)=>this.formatMessage(message, logType));
126
+ const coloredAppName = config?.appName ? applyColor(chalk.bold(`[${config.appName}]`)) : undefined;
127
+ const timestamp = chalk.bold(dayjs().format('dddd, MMMM D, YYYY HH:mm:ss [UTC]Z'));
128
+ const coloredLogLevel = applyColor(chalk.bold(`[${logType}]`));
129
+ const coloredContext = this.context ? chalk.yellow.bold(`[${this.context}]`) : '';
130
+ const logTexts = [
131
+ coloredAppName,
132
+ timestamp,
133
+ coloredLogLevel,
134
+ coloredContext,
135
+ ...formattedMessages
136
+ ].filter((log)=>!!log);
137
+ console[logLevel](...logTexts);
138
+ }
139
+ constructor(context){
140
+ this.context = context;
141
+ this.colorMap = {
142
+ LOG: chalk.green,
143
+ INFO: chalk.cyan,
144
+ WARN: chalk.yellow,
145
+ ERROR: chalk.red,
146
+ DEBUG: chalk.magenta
147
+ };
148
+ }
149
+ }
150
+
151
+ /**
152
+ * MeoCord Framework
153
+ * Copyright (C) 2025 Ukasyah Rahmatullah Zada
154
+ *
155
+ * This program is free software: you can redistribute it and/or modify
156
+ * it under the terms of the GNU General Public License as published by
157
+ * the Free Software Foundation, either version 3 of the License, or
158
+ * (at your option) any later version.
159
+ *
160
+ * This program is distributed in the hope that it will be useful,
161
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
162
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
163
+ * GNU General Public License for more details.
164
+ *
165
+ * You should have received a copy of the GNU General Public License
166
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
167
+ */ class Theme {
168
+ }
169
+ Theme.successColor = '#28A745';
170
+ Theme.infoColor = '#17A2B8';
171
+ Theme.errorColor = '#DC3545';
172
+ Theme.warningColor = '#FFC107';
173
+
174
+ exports.Logger = Logger;
175
+ exports.Theme = Theme;
176
+ exports.loadMeoCordConfig = loadMeoCordConfig;
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ var theme = require('../_shared/theme-BdtbtMZX.cjs');
4
+ require('node:util');
5
+ require('dayjs');
6
+ require('dayjs/plugin/utc.js');
7
+ require('dayjs/plugin/timezone.js');
8
+ require('path');
9
+ require('fs');
10
+ require('jiti');
11
+ require('chalk');
12
+
13
+
14
+
15
+ exports.Logger = theme.Logger;
16
+ exports.Theme = theme.Theme;