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.
- package/CHANGELOG.md +35 -0
- package/README.md +152 -140
- package/dist/cjs/_shared/meocord.app-CHjdCAA_.cjs +496 -0
- package/dist/cjs/_shared/theme-BdtbtMZX.cjs +176 -0
- package/dist/cjs/common/index.cjs +16 -0
- package/dist/cjs/core/index.cjs +35 -0
- package/dist/cjs/decorator/index.cjs +360 -0
- package/dist/cjs/enum/index.cjs +20 -0
- package/dist/cjs/interface/index.cjs +2 -0
- package/dist/esm/bin/generator.js +92 -0
- package/dist/esm/bin/helper/controller-generator.helper.js +105 -0
- package/dist/esm/bin/helper/guard-generator.helper.js +33 -0
- package/dist/esm/bin/helper/service-generator.helper.js +33 -0
- package/dist/esm/bin/meocord.js +333 -0
- package/dist/esm/common/index.js +2 -0
- package/dist/esm/common/logger.js +72 -0
- package/dist/{core/index.d.ts → esm/common/theme.js} +8 -2
- package/dist/esm/core/index.js +1 -0
- package/dist/esm/core/meocord-factory.js +28 -0
- package/dist/esm/core/meocord.app.js +267 -0
- package/dist/esm/decorator/app.decorator.js +99 -0
- package/dist/esm/decorator/command-builder.decorator.js +32 -0
- package/dist/esm/decorator/container.js +6 -0
- package/dist/esm/decorator/controller.decorator.js +218 -0
- package/dist/esm/decorator/guard.decorator.js +165 -0
- package/dist/esm/decorator/index.js +6 -0
- package/dist/esm/decorator/service.decorator.js +58 -0
- package/dist/esm/enum/controller.enum.js +43 -0
- package/dist/esm/enum/index.js +1 -0
- package/dist/esm/interface/index.js +1 -0
- package/dist/esm/package.json.js +5 -0
- package/dist/esm/util/common.util.js +68 -0
- package/dist/esm/util/embed.util.js +13 -0
- package/dist/esm/util/generator-cli.util.js +107 -0
- package/dist/{util → esm/util}/json.util.js +10 -6
- package/dist/esm/util/meocord-cli.util.js +172 -0
- package/dist/esm/util/meocord-config-loader.util.js +48 -0
- package/dist/esm/util/tsconfig.util.js +83 -0
- package/dist/{util → esm/util}/wait.util.js +5 -1
- package/dist/{common/logger.d.ts → types/common/index.d.ts} +30 -1
- package/dist/{core/meocord.app.d.ts → types/core/index.d.ts} +30 -2
- package/dist/types/decorator/index.d.ts +425 -0
- package/dist/types/enum/index.d.ts +18 -0
- package/dist/{interface → types/interface}/index.d.ts +11 -7
- package/package.json +64 -48
- package/webpack.config.js +2 -2
- package/dist/bin/generator.d.ts +0 -29
- package/dist/bin/generator.js +0 -17
- package/dist/bin/helper/controller-generator.helper.d.ts +0 -67
- package/dist/bin/helper/controller-generator.helper.js +0 -50
- package/dist/bin/helper/guard-generator.helper.d.ts +0 -32
- package/dist/bin/helper/guard-generator.helper.js +0 -25
- package/dist/bin/helper/service-generator.helper.d.ts +0 -32
- package/dist/bin/helper/service-generator.helper.js +0 -25
- package/dist/bin/meocord.d.ts +0 -19
- package/dist/bin/meocord.js +0 -34
- package/dist/common/index.d.ts +0 -19
- package/dist/common/index.js +0 -17
- package/dist/common/logger.js +0 -17
- package/dist/common/theme.d.ts +0 -24
- package/dist/common/theme.js +0 -17
- package/dist/core/index.js +0 -17
- package/dist/core/meocord-factory.d.ts +0 -24
- package/dist/core/meocord-factory.js +0 -17
- package/dist/core/meocord.app.js +0 -17
- package/dist/decorator/app.decorator.d.ts +0 -59
- package/dist/decorator/app.decorator.js +0 -61
- package/dist/decorator/command-builder.decorator.d.ts +0 -39
- package/dist/decorator/command-builder.decorator.js +0 -35
- package/dist/decorator/container.d.ts +0 -20
- package/dist/decorator/container.js +0 -17
- package/dist/decorator/controller.decorator.d.ts +0 -125
- package/dist/decorator/controller.decorator.js +0 -113
- package/dist/decorator/guard.decorator.d.ts +0 -101
- package/dist/decorator/guard.decorator.js +0 -94
- package/dist/decorator/index.d.ts +0 -23
- package/dist/decorator/index.js +0 -17
- package/dist/decorator/service.decorator.d.ts +0 -36
- package/dist/decorator/service.decorator.js +0 -36
- package/dist/enum/controller.enum.d.ts +0 -42
- package/dist/enum/controller.enum.js +0 -19
- package/dist/enum/index.d.ts +0 -18
- package/dist/enum/index.js +0 -17
- package/dist/interface/command-decorator.interface.d.ts +0 -43
- package/dist/interface/command-decorator.interface.js +0 -1
- package/dist/interface/index.js +0 -1
- package/dist/util/common.util.d.ts +0 -40
- package/dist/util/common.util.js +0 -38
- package/dist/util/embed.util.d.ts +0 -19
- package/dist/util/embed.util.js +0 -17
- package/dist/util/generator-cli.util.d.ts +0 -65
- package/dist/util/generator-cli.util.js +0 -49
- package/dist/util/index.d.ts +0 -18
- package/dist/util/index.js +0 -17
- package/dist/util/json.util.d.ts +0 -27
- package/dist/util/meocord-cli.util.d.ts +0 -62
- package/dist/util/meocord-cli.util.js +0 -50
- package/dist/util/meocord-config-loader.util.d.ts +0 -32
- package/dist/util/meocord-config-loader.util.js +0 -34
- package/dist/util/tsconfig.util.d.ts +0 -29
- package/dist/util/tsconfig.util.js +0 -32
- package/dist/util/wait.util.d.ts +0 -18
- /package/dist/{bin → esm/bin}/builder-template/builder/context-menu.builder.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/builder/slash.builder.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/controller/button.controller.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/controller/context-menu.controller.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/controller/message.controller.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/controller/modal-submit.controller.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/controller/reaction.controller.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/controller/select-menu.controller.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/controller/slash.controller.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/guard.template +0 -0
- /package/dist/{bin → esm/bin}/builder-template/service.template +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('reflect-metadata');
|
|
4
|
+
var theme = require('../_shared/theme-BdtbtMZX.cjs');
|
|
5
|
+
var meocord_app = require('../_shared/meocord.app-CHjdCAA_.cjs');
|
|
6
|
+
require('inversify');
|
|
7
|
+
require('discord.js');
|
|
8
|
+
require('path');
|
|
9
|
+
require('fs');
|
|
10
|
+
require('jiti');
|
|
11
|
+
require('node:util');
|
|
12
|
+
require('dayjs');
|
|
13
|
+
require('dayjs/plugin/utc.js');
|
|
14
|
+
require('dayjs/plugin/timezone.js');
|
|
15
|
+
require('chalk');
|
|
16
|
+
require('../enum/index.cjs');
|
|
17
|
+
require('lodash-es');
|
|
18
|
+
|
|
19
|
+
class MeoCordFactory {
|
|
20
|
+
static create(target) {
|
|
21
|
+
const container = Reflect.getMetadata('inversify:container', target);
|
|
22
|
+
if (!container) {
|
|
23
|
+
if (typeof target === 'function') {
|
|
24
|
+
this.logger.error(`No container found for class: ${target.name}`);
|
|
25
|
+
} else {
|
|
26
|
+
this.logger.error('No container found for the provided target.');
|
|
27
|
+
}
|
|
28
|
+
throw new Error('No container found on the target class.');
|
|
29
|
+
}
|
|
30
|
+
return meocord_app.mainContainer.get(meocord_app.MeoCordApp);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
MeoCordFactory.logger = new theme.Logger();
|
|
34
|
+
|
|
35
|
+
exports.MeoCordFactory = MeoCordFactory;
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
require('reflect-metadata');
|
|
4
|
+
var meocord_app = require('../_shared/meocord.app-CHjdCAA_.cjs');
|
|
5
|
+
var inversify = require('inversify');
|
|
6
|
+
var discord_js = require('discord.js');
|
|
7
|
+
var theme = require('../_shared/theme-BdtbtMZX.cjs');
|
|
8
|
+
require('../enum/index.cjs');
|
|
9
|
+
require('lodash-es');
|
|
10
|
+
require('node:util');
|
|
11
|
+
require('dayjs');
|
|
12
|
+
require('dayjs/plugin/utc.js');
|
|
13
|
+
require('dayjs/plugin/timezone.js');
|
|
14
|
+
require('path');
|
|
15
|
+
require('fs');
|
|
16
|
+
require('jiti');
|
|
17
|
+
require('chalk');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `@Service()` decorator to mark a class as a service that can be injected into controllers or used as standalone services.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* @Service()
|
|
25
|
+
* class MyService {
|
|
26
|
+
* constructor(private anotherService: AnotherService) {}
|
|
27
|
+
*
|
|
28
|
+
* doSomething() {
|
|
29
|
+
* this.anotherService.alsoDoSomething()
|
|
30
|
+
* console.log('Hello, World!')
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
* @returns A decorator function to apply to the class.
|
|
35
|
+
*/ function Service() {
|
|
36
|
+
return function(target) {
|
|
37
|
+
// Check if the class is already injectable; if not, make it injectable dynamically
|
|
38
|
+
if (!Reflect.hasMetadata('inversify:injectable', target)) {
|
|
39
|
+
inversify.injectable()(target);
|
|
40
|
+
}
|
|
41
|
+
// Check if the class is already injectable; if not, make it injectable dynamically
|
|
42
|
+
if (!meocord_app.mainContainer.isBound(target)) {
|
|
43
|
+
// Bind the target class to the container in a singleton scope
|
|
44
|
+
meocord_app.mainContainer.bind(target).toSelf().inSingletonScope();
|
|
45
|
+
}
|
|
46
|
+
// Recursively bind dependencies
|
|
47
|
+
bindDependencies$1(target);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function bindDependencies$1(target) {
|
|
51
|
+
// Get the constructor parameter types using Reflect metadata
|
|
52
|
+
const dependencies = Reflect.getMetadata('design:paramtypes', target) || [];
|
|
53
|
+
dependencies.forEach((dep)=>{
|
|
54
|
+
// Bind the dependency if not already bound
|
|
55
|
+
if (!meocord_app.mainContainer.isBound(dep)) {
|
|
56
|
+
if (dep.name === discord_js.Client.name) return;
|
|
57
|
+
try {
|
|
58
|
+
// Check if the class is already injectable; if not, make it injectable dynamically
|
|
59
|
+
if (!Reflect.hasMetadata('inversify:injectable', dep)) {
|
|
60
|
+
inversify.injectable()(dep);
|
|
61
|
+
}
|
|
62
|
+
meocord_app.mainContainer.bind(dep).toSelf().inSingletonScope();
|
|
63
|
+
bindDependencies$1(dep); // Recur for the dependencies of the current dependency
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn(`Could not bind dependency: ${dep?.name || dep}`, error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* This decorator is used to mark a class as a Discord command builder that later can be registered on the `@Command` decorator.
|
|
73
|
+
* It defines the command type using metadata and dynamically makes the class injectable if it isn't already.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* @CommandBuilder(CommandType.SLASH)
|
|
78
|
+
* export class MySlashCommand implements CommandBuilderBase {
|
|
79
|
+
* build(commandName: string): SlashCommandBuilder {
|
|
80
|
+
* return new SlashCommandBuilder().setName(commandName).setDescription('A sample slash command')
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
*```
|
|
84
|
+
*
|
|
85
|
+
* @param commandType - The type of the command, specified from the `CommandType` enum.
|
|
86
|
+
* @returns A decorator function that makes the target class injectable
|
|
87
|
+
* and assigns the `commandType` metadata.
|
|
88
|
+
*/ function CommandBuilder(commandType) {
|
|
89
|
+
return function(target) {
|
|
90
|
+
// Check if the class is already injectable; if not, make it injectable dynamically
|
|
91
|
+
if (!Reflect.hasMetadata('inversify:injectable', target)) {
|
|
92
|
+
inversify.injectable()(target);
|
|
93
|
+
}
|
|
94
|
+
// Define the command type metadata for the target class
|
|
95
|
+
Reflect.defineMetadata('commandType', commandType, target);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isValidContext(context) {
|
|
100
|
+
return context instanceof discord_js.BaseInteraction || context instanceof discord_js.Message || context instanceof discord_js.MessageReaction;
|
|
101
|
+
}
|
|
102
|
+
function applyGuards(descriptor, guards, propertyKey) {
|
|
103
|
+
const originalMethod = descriptor.value;
|
|
104
|
+
descriptor.value = async function(...args) {
|
|
105
|
+
const [context] = args;
|
|
106
|
+
if (!isValidContext(context)) {
|
|
107
|
+
throw new Error(`The first argument of ${String(propertyKey)} must be an instance of Interaction, Message, or MessageReaction.`);
|
|
108
|
+
}
|
|
109
|
+
// Iterate over each guard and check if it allows the method to proceed
|
|
110
|
+
for (const guard of guards){
|
|
111
|
+
let guardInstance;
|
|
112
|
+
if (isGuardWithParams(guard)) {
|
|
113
|
+
const { provide, params } = guard;
|
|
114
|
+
guardInstance = meocord_app.mainContainer.get(provide, {
|
|
115
|
+
autobind: true
|
|
116
|
+
});
|
|
117
|
+
// Inject the parameters into the guard instance
|
|
118
|
+
Object.assign(guardInstance, params);
|
|
119
|
+
} else {
|
|
120
|
+
// Resolve guard without parameters
|
|
121
|
+
guardInstance = meocord_app.mainContainer.get(guard, {
|
|
122
|
+
autobind: true
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Ensure the guard has the necessary method `canActivate`
|
|
126
|
+
if (!guardInstance.canActivate) {
|
|
127
|
+
throw new Error(`Guard ${guard.constructor.name} applied to ${String(propertyKey)} does not have a valid canActivate method.`);
|
|
128
|
+
}
|
|
129
|
+
// Check if the guard allows the method to proceed
|
|
130
|
+
const canActivate = await guardInstance.canActivate(...args);
|
|
131
|
+
if (!canActivate) {
|
|
132
|
+
return; // Prevent method execution if the guard fails
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Call the original method if all guards pass
|
|
136
|
+
return originalMethod.apply(this, args);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* `@Guard()` decorator to mark a class as a Guard that later can be added on `@UseGuard` decorator.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* @Guard()
|
|
145
|
+
export class ButtonInteractionGuard implements GuardInterface {
|
|
146
|
+
private readonly logger = new Logger(ButtonInteractionGuard.name)
|
|
147
|
+
|
|
148
|
+
async canActivate(context: ButtonInteraction, { ownerId }: { ownerId: string }): Promise<boolean> {
|
|
149
|
+
if (context.user.id !== ownerId) {
|
|
150
|
+
this.logger.error(
|
|
151
|
+
`User with id ${context.user.id} is not allowed to use this command that initiated by user with id ${ownerId}.`,
|
|
152
|
+
)
|
|
153
|
+
const embed = generateErrorEmbed(
|
|
154
|
+
`Hi <@${context.user.id}>, this command can only be used by the person who initiated it: <@${ownerId}>.`,
|
|
155
|
+
)
|
|
156
|
+
await context.reply({
|
|
157
|
+
embeds: [embed],
|
|
158
|
+
flags: MessageFlagsBitField.Flags.Ephemeral,
|
|
159
|
+
})
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
* ```
|
|
166
|
+
*/ function Guard() {
|
|
167
|
+
return function(target) {
|
|
168
|
+
// Check if the class is already injectable; if not, make it injectable dynamically
|
|
169
|
+
if (!Reflect.hasMetadata('inversify:injectable', target)) {
|
|
170
|
+
inversify.injectable()(target);
|
|
171
|
+
}
|
|
172
|
+
meocord_app.mainContainer.bind(target).toSelf().inTransientScope();
|
|
173
|
+
// Bind any dependencies that the guard requires
|
|
174
|
+
const injectables = Reflect.getMetadata('design:paramtypes', target) || [];
|
|
175
|
+
injectables.forEach((dep)=>{
|
|
176
|
+
if (!meocord_app.mainContainer.isBound(dep)) {
|
|
177
|
+
meocord_app.mainContainer.bind(dep).toSelf().inSingletonScope();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Type guard to check if the object is a GuardWithParams.
|
|
184
|
+
* This function helps to check whether a guard is parameterized or not.
|
|
185
|
+
*
|
|
186
|
+
* @param guard - The guard to check.
|
|
187
|
+
* @returns `true` if the guard has parameters, otherwise `false`.
|
|
188
|
+
*/ function isGuardWithParams(guard) {
|
|
189
|
+
return typeof guard === 'object' && 'provide' in guard && 'params' in guard;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* `@UseGuard()` decorator to apply one or more guards to methods.
|
|
193
|
+
* Guards are used to handle permission checks before executing a method.
|
|
194
|
+
* Each guard must use `@Guard` decorator and implement the `canActivate` method, which determines
|
|
195
|
+
* whether the method should be allowed to execute based on the provided context (Interaction, Message, or Reaction) and arguments.
|
|
196
|
+
* This decorator ensures that all guards pass validation before calling the original method.
|
|
197
|
+
* Supports guards that are parameterized (accepting additional parameters).
|
|
198
|
+
*
|
|
199
|
+
* @param guards - One or more guard classes to apply. These can be regular guards or guards with additional parameters.
|
|
200
|
+
* - If providing a guard with parameters, it should be an object with:
|
|
201
|
+
* - `provide`: The guard class to instantiate. Must implement `GuardInterface`.
|
|
202
|
+
* - `params`: A record of key-value pairs to be passed as additional properties to the guard instance.
|
|
203
|
+
* @returns A method decorator function that applies the guards to the method.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* // Method-level usage
|
|
208
|
+
* @Command('profile-{id}', CommandType.BUTTON)
|
|
209
|
+
* @UseGuard(
|
|
210
|
+
* { provide: RateLimiterGuard, params: { limit: 2, window: 3000 } },
|
|
211
|
+
* ButtonInteractionGuard
|
|
212
|
+
* )
|
|
213
|
+
* async showProfileById(interaction: ButtonInteraction, { id }: { id: string }) {
|
|
214
|
+
* await interaction.reply(`Profile ID: ${id}`)
|
|
215
|
+
* }
|
|
216
|
+
*
|
|
217
|
+
* // Class-level usage
|
|
218
|
+
* @Controller()
|
|
219
|
+
* @UseGuard(GlobalGuard)
|
|
220
|
+
* class MyController {
|
|
221
|
+
* @Command('ping', CommandType.SLASH)
|
|
222
|
+
* async ping(interaction: ChatInputCommandInteraction) {
|
|
223
|
+
* await interaction.reply('Pong!')
|
|
224
|
+
* }
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*/ function UseGuard(...guards) {
|
|
228
|
+
return function(target, propertyKey, descriptor) {
|
|
229
|
+
if (descriptor && propertyKey) {
|
|
230
|
+
// Method Decorator
|
|
231
|
+
applyGuards(descriptor, guards, String(propertyKey));
|
|
232
|
+
// Store guard metadata for later access (if needed)
|
|
233
|
+
Reflect.defineMetadata('guards', guards, target, propertyKey);
|
|
234
|
+
} else if (typeof target === 'function' && !propertyKey && !descriptor) {
|
|
235
|
+
// Class Decorator
|
|
236
|
+
const prototype = target.prototype;
|
|
237
|
+
// 1. Get all methods to guard
|
|
238
|
+
const methods = new Set();
|
|
239
|
+
const commandMap = meocord_app.getCommandMap(prototype) || {};
|
|
240
|
+
Object.values(commandMap).flat().forEach((cmd)=>methods.add(cmd.methodName));
|
|
241
|
+
const messageHandlers = meocord_app.getMessageHandlers(prototype) || [];
|
|
242
|
+
messageHandlers.forEach((handler)=>methods.add(handler.method));
|
|
243
|
+
const reactionHandlers = meocord_app.getReactionHandlers(prototype) || [];
|
|
244
|
+
reactionHandlers.forEach((handler)=>methods.add(handler.method));
|
|
245
|
+
for (const methodName of methods){
|
|
246
|
+
const methodDescriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
|
|
247
|
+
if (methodDescriptor) {
|
|
248
|
+
applyGuards(methodDescriptor, guards, methodName);
|
|
249
|
+
Object.defineProperty(prototype, methodName, methodDescriptor);
|
|
250
|
+
Reflect.defineMetadata('guards', guards, prototype, methodName);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Binds a class and its dependencies to the Inversify container in singleton scope.
|
|
259
|
+
*
|
|
260
|
+
* @param {Container} container - The Inversify container instance.
|
|
261
|
+
* @param {any} cls - The class to be bound to the container.
|
|
262
|
+
*/ function bindDependencies(container, cls) {
|
|
263
|
+
if (!container.isBound(cls)) {
|
|
264
|
+
container.bind(cls).toSelf().inSingletonScope();
|
|
265
|
+
const dependencies = Reflect.getMetadata('design:paramtypes', cls) || [];
|
|
266
|
+
dependencies.forEach((dep)=>bindDependencies(container, dep));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Resolves dependencies for a given class by binding them to the container and returning the resolved instances.
|
|
271
|
+
*
|
|
272
|
+
* @param {Container} container - The Inversify container instance.
|
|
273
|
+
* @param {any} target - The target class whose dependencies are to be resolved.
|
|
274
|
+
* @returns {any[]} - An array of resolved instances of the target's dependencies.
|
|
275
|
+
*/ function resolveDependencies(container, target) {
|
|
276
|
+
const injectables = Reflect.getMetadata('design:paramtypes', target) || [];
|
|
277
|
+
return injectables.map((dep)=>{
|
|
278
|
+
bindDependencies(container, dep);
|
|
279
|
+
return container.get(dep);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* `@MeoCord()` decorator for initializing and setting up the MeoCord application.
|
|
284
|
+
*
|
|
285
|
+
* @param {Object} options - The decorator options.
|
|
286
|
+
* @param {ServiceIdentifier[]} options.controllers - The list of controllers to be registered.
|
|
287
|
+
* @param {ClientOptions} options.clientOptions - The Discord client options for initializing the bot.
|
|
288
|
+
* @param {ActivityOptions[]} [options.activities] - Optional activities for the bot.
|
|
289
|
+
* @param {ServiceIdentifier[]} [options.services] - Optional services to be registered.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```typescript
|
|
293
|
+
* @MeoCord({
|
|
294
|
+
* controllers: [PingSlashController],
|
|
295
|
+
* clientOptions: {
|
|
296
|
+
* intents: [
|
|
297
|
+
* GatewayIntentBits.Guilds,
|
|
298
|
+
* GatewayIntentBits.GuildMembers,
|
|
299
|
+
* GatewayIntentBits.GuildMessages,
|
|
300
|
+
* GatewayIntentBits.GuildMessageReactions,
|
|
301
|
+
* GatewayIntentBits.MessageContent,
|
|
302
|
+
* ],
|
|
303
|
+
* partials: [Partials.Message, Partials.Channel, Partials.Reaction],
|
|
304
|
+
* },
|
|
305
|
+
* activities: [{
|
|
306
|
+
* name: `${sample(['Genshin', 'ZZZ'])} with Romeo`,
|
|
307
|
+
* type: ActivityType.Playing,
|
|
308
|
+
* url: 'https://enka.network/u/824957678/',
|
|
309
|
+
* }],
|
|
310
|
+
* services: [MyStandaloneService],
|
|
311
|
+
* })
|
|
312
|
+
* class MyApp {}
|
|
313
|
+
* ```
|
|
314
|
+
**/ function MeoCord(options) {
|
|
315
|
+
return (target)=>{
|
|
316
|
+
if (!Reflect.hasMetadata('inversify:injectable', target)) {
|
|
317
|
+
inversify.injectable()(target); // Make target injectable (inversify-specific)
|
|
318
|
+
}
|
|
319
|
+
const meocordConfig = theme.loadMeoCordConfig();
|
|
320
|
+
if (!meocordConfig) return;
|
|
321
|
+
const discordClient = new discord_js.Client(options.clientOptions);
|
|
322
|
+
meocord_app.mainContainer.bind(discord_js.Client).toConstantValue(discordClient);
|
|
323
|
+
[
|
|
324
|
+
...options.controllers,
|
|
325
|
+
...options.services || []
|
|
326
|
+
].forEach((dep)=>{
|
|
327
|
+
bindDependencies(meocord_app.mainContainer, dep);
|
|
328
|
+
});
|
|
329
|
+
// Bind other static values to the container
|
|
330
|
+
meocord_app.mainContainer.bind(target).toConstantValue(options.clientOptions);
|
|
331
|
+
if (options.activities) {
|
|
332
|
+
meocord_app.mainContainer.bind(target).toConstantValue(options.activities);
|
|
333
|
+
}
|
|
334
|
+
if (options.services) {
|
|
335
|
+
meocord_app.mainContainer.bind(target).toConstantValue(options.services.map((s)=>meocord_app.mainContainer.get(s)));
|
|
336
|
+
}
|
|
337
|
+
const meocordApp = new meocord_app.MeoCordApp(options.controllers.map((c)=>meocord_app.mainContainer.get(c)), discordClient, meocordConfig.discordToken, options?.activities);
|
|
338
|
+
meocord_app.mainContainer.bind(meocord_app.MeoCordApp).toConstantValue(meocordApp);
|
|
339
|
+
// Bind the App class dynamically with resolved dependencies
|
|
340
|
+
meocord_app.mainContainer.bind(target).toDynamicValue(()=>{
|
|
341
|
+
const dependencies = resolveDependencies(meocord_app.mainContainer, target);
|
|
342
|
+
return new target(...dependencies);
|
|
343
|
+
}).inSingletonScope();
|
|
344
|
+
Reflect.defineMetadata('inversify:container', meocord_app.mainContainer, target);
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
exports.Command = meocord_app.Command;
|
|
349
|
+
exports.Controller = meocord_app.Controller;
|
|
350
|
+
exports.MessageHandler = meocord_app.MessageHandler;
|
|
351
|
+
exports.ReactionHandler = meocord_app.ReactionHandler;
|
|
352
|
+
exports.getCommandMap = meocord_app.getCommandMap;
|
|
353
|
+
exports.getMessageHandlers = meocord_app.getMessageHandlers;
|
|
354
|
+
exports.getReactionHandlers = meocord_app.getReactionHandlers;
|
|
355
|
+
exports.mainContainer = meocord_app.mainContainer;
|
|
356
|
+
exports.CommandBuilder = CommandBuilder;
|
|
357
|
+
exports.Guard = Guard;
|
|
358
|
+
exports.MeoCord = MeoCord;
|
|
359
|
+
exports.Service = Service;
|
|
360
|
+
exports.UseGuard = UseGuard;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var CommandType = /*#__PURE__*/ function(CommandType) {
|
|
4
|
+
CommandType["SLASH"] = "SLASH";
|
|
5
|
+
CommandType["BUTTON"] = "BUTTON";
|
|
6
|
+
CommandType["CONTEXT_MENU"] = "CONTEXT_MENU";
|
|
7
|
+
CommandType["SELECT_MENU"] = "SELECT_MENU";
|
|
8
|
+
CommandType["MODAL_SUBMIT"] = "MODAL_SUBMIT";
|
|
9
|
+
return CommandType;
|
|
10
|
+
}({});
|
|
11
|
+
/**
|
|
12
|
+
* Enum representing actions that can be performed on a message reaction.
|
|
13
|
+
*/ var ReactionHandlerAction = /*#__PURE__*/ function(ReactionHandlerAction) {
|
|
14
|
+
/** Reaction added to a message. */ ReactionHandlerAction["ADD"] = "ADD";
|
|
15
|
+
/** Reaction removed from a message. */ ReactionHandlerAction["REMOVE"] = "REMOVE";
|
|
16
|
+
return ReactionHandlerAction;
|
|
17
|
+
}({});
|
|
18
|
+
|
|
19
|
+
exports.CommandType = CommandType;
|
|
20
|
+
exports.ReactionHandlerAction = ReactionHandlerAction;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Argument } from 'commander';
|
|
2
|
+
import { ControllerGeneratorHelper } from './helper/controller-generator.helper.js';
|
|
3
|
+
import { Logger } from '../common/logger.js';
|
|
4
|
+
import '../common/theme.js';
|
|
5
|
+
import { ServiceGeneratorHelper } from './helper/service-generator.helper.js';
|
|
6
|
+
import { GuardGeneratorHelper } from './helper/guard-generator.helper.js';
|
|
7
|
+
import wait from '../util/wait.util.js';
|
|
8
|
+
|
|
9
|
+
class GeneratorCLI {
|
|
10
|
+
register(program) {
|
|
11
|
+
const generatorCommand = program.command('generate').alias('g').description('Generate components');
|
|
12
|
+
generatorCommand.command('controller').alias('co').description('Generate a controller component').addArgument(new Argument('<type>', 'Type of the controller (e.g., button, context-menu, etc.)').choices([
|
|
13
|
+
'button',
|
|
14
|
+
'context-menu',
|
|
15
|
+
'message',
|
|
16
|
+
'modal-submit',
|
|
17
|
+
'reaction',
|
|
18
|
+
'select-menu',
|
|
19
|
+
'slash'
|
|
20
|
+
])).addArgument(new Argument('<name>', 'Name of the controller')).action(async (type, name)=>{
|
|
21
|
+
await this.handleGenerateComponent({
|
|
22
|
+
component: 'controller',
|
|
23
|
+
type: type,
|
|
24
|
+
name
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
generatorCommand.command('service').alias('s').addArgument(new Argument('<name>', 'Name of the service.')).description('Generate a service component').action(async (name)=>{
|
|
28
|
+
await this.handleGenerateComponent({
|
|
29
|
+
component: 'service',
|
|
30
|
+
name
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
generatorCommand.command('guard').alias('gu').addArgument(new Argument('<name>', 'Name of the guard.')).description('Generate a guard component').action(async (name)=>{
|
|
34
|
+
await this.handleGenerateComponent({
|
|
35
|
+
component: 'guard',
|
|
36
|
+
name
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
return program;
|
|
40
|
+
}
|
|
41
|
+
async handleGenerateComponent(args) {
|
|
42
|
+
const { component, name, type } = args;
|
|
43
|
+
if (!name) {
|
|
44
|
+
this.logger.error('Name is required');
|
|
45
|
+
await wait(100);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
switch(component){
|
|
49
|
+
case 'controller':
|
|
50
|
+
if (!type) {
|
|
51
|
+
this.logger.error('Type is required for controllers');
|
|
52
|
+
await wait(100);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
await this.handleGenerateController({
|
|
56
|
+
name,
|
|
57
|
+
type
|
|
58
|
+
});
|
|
59
|
+
break;
|
|
60
|
+
case 'service':
|
|
61
|
+
this.serviceGeneratorHelper.generateService(name);
|
|
62
|
+
break;
|
|
63
|
+
case 'guard':
|
|
64
|
+
this.guardGeneratorHelper.generateGuard(name);
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
this.logger.error(`Unsupported component type: ${component}`);
|
|
68
|
+
await wait(100);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async handleGenerateController(args) {
|
|
73
|
+
try {
|
|
74
|
+
this.controllerGeneratorHelper.generateController({
|
|
75
|
+
controllerName: args.name
|
|
76
|
+
}, args.type);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.logger.error(`Error generating controller: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
await wait(100);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
constructor(appName){
|
|
84
|
+
this.appName = appName;
|
|
85
|
+
this.logger = new Logger(this.appName);
|
|
86
|
+
this.controllerGeneratorHelper = new ControllerGeneratorHelper();
|
|
87
|
+
this.serviceGeneratorHelper = new ServiceGeneratorHelper(this.appName);
|
|
88
|
+
this.guardGeneratorHelper = new GuardGeneratorHelper(this.appName);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { GeneratorCLI };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { ControllerType } from '../../enum/controller.enum.js';
|
|
3
|
+
import { validateAndFormatName, populateTemplate, createDirectoryIfNotExists, generateFile } from '../../util/generator-cli.util.js';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
8
|
+
class ControllerGeneratorHelper {
|
|
9
|
+
/**
|
|
10
|
+
* Generates a new controller file and an associated structure based on the provided arguments and controller type.
|
|
11
|
+
* @param args - The arguments for generating the controller, including the optional controller name.
|
|
12
|
+
* @param type - The type of the controller to generate, defined in the `ControllerType` enum.
|
|
13
|
+
* @throws Will throw an error if the controller name is invalid or if the controller type is unsupported.
|
|
14
|
+
*/ generateController(args, type) {
|
|
15
|
+
const { parts, kebabCaseName, className } = validateAndFormatName(args.controllerName);
|
|
16
|
+
const controllerDir = path.join(process.cwd(), 'src', 'controllers', type, ...parts);
|
|
17
|
+
const template = this.buildControllerTemplate(className, type);
|
|
18
|
+
this.generateControllerStructure(controllerDir, kebabCaseName, className, type, template);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Builds the controller template content by populating a template with variables.
|
|
22
|
+
* @param className - The name of the controller class.
|
|
23
|
+
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
24
|
+
* @returns The populated template string for the controller.
|
|
25
|
+
* @throws Will throw an error if the controller type is unsupported.
|
|
26
|
+
*/ buildControllerTemplate(className, type) {
|
|
27
|
+
const templateConfig = this.getTemplateConfig(type, className);
|
|
28
|
+
if (!templateConfig) {
|
|
29
|
+
throw new Error(`Unsupported controller type: ${type}`);
|
|
30
|
+
}
|
|
31
|
+
return populateTemplate(templateConfig.template, templateConfig.variables);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves the template configuration for a specific controller type and class name.
|
|
35
|
+
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
36
|
+
* @param className - The name of the controller class.
|
|
37
|
+
* @returns An object containing the template path and variables, or `undefined` if not found.
|
|
38
|
+
*/ getTemplateConfig(type, className) {
|
|
39
|
+
const baseDir = path.resolve(__dirname$1, '..', 'builder-template', 'controller');
|
|
40
|
+
const templates = {
|
|
41
|
+
[ControllerType.BUTTON]: 'button.controller.template',
|
|
42
|
+
[ControllerType.MODAL_SUBMIT]: 'modal-submit.controller.template',
|
|
43
|
+
[ControllerType.SELECT_MENU]: 'select-menu.controller.template',
|
|
44
|
+
[ControllerType.REACTION]: 'reaction.controller.template',
|
|
45
|
+
[ControllerType.MESSAGE]: 'message.controller.template',
|
|
46
|
+
[ControllerType.CONTEXT_MENU]: 'context-menu.controller.template',
|
|
47
|
+
[ControllerType.SLASH]: 'slash.controller.template'
|
|
48
|
+
};
|
|
49
|
+
const template = templates[type] ? path.resolve(baseDir, templates[type]) : undefined;
|
|
50
|
+
return template ? {
|
|
51
|
+
template,
|
|
52
|
+
variables: {
|
|
53
|
+
className
|
|
54
|
+
}
|
|
55
|
+
} : undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generates the controller file and its associated structure (e.g., builder files, directories).
|
|
59
|
+
* @param controllerDir - The absolute path to the controller directory.
|
|
60
|
+
* @param kebabCaseName - The kebab-case name of the controller file.
|
|
61
|
+
* @param className - The name of the controller class.
|
|
62
|
+
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
63
|
+
* @param controllerTemplate - The populated template string for the controller file.
|
|
64
|
+
*/ generateControllerStructure(controllerDir, kebabCaseName, className, type, controllerTemplate) {
|
|
65
|
+
this.generateBuilderFile(className, type, controllerDir);
|
|
66
|
+
createDirectoryIfNotExists(controllerDir);
|
|
67
|
+
const controllerFilePath = path.join(controllerDir, `${kebabCaseName}.${type}.controller.ts`);
|
|
68
|
+
generateFile(controllerFilePath, controllerTemplate);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Generates a builder file for the specified controller type and stores it in the controller directory.
|
|
72
|
+
* @param className - The name of the controller class.
|
|
73
|
+
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
74
|
+
* @param controllerDir - The absolute path to the controller directory.
|
|
75
|
+
*/ generateBuilderFile(className, type, controllerDir) {
|
|
76
|
+
const builderConfig = this.getBuilderConfig(type, className);
|
|
77
|
+
if (!builderConfig) return;
|
|
78
|
+
const builderTemplate = populateTemplate(builderConfig.template, builderConfig.variables);
|
|
79
|
+
const buildersDir = path.join(controllerDir, 'builders');
|
|
80
|
+
createDirectoryIfNotExists(buildersDir);
|
|
81
|
+
const builderFilePath = path.join(buildersDir, 'sample.builder.ts');
|
|
82
|
+
generateFile(builderFilePath, builderTemplate);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Retrieves the configuration for generating a builder file based on the controller type and class name.
|
|
86
|
+
* @param type - The type of the controller, defined in the `ControllerType` enum.
|
|
87
|
+
* @param className - The name of the controller class.
|
|
88
|
+
* @returns An object containing the builder template path and variables, or `undefined` if not found.
|
|
89
|
+
*/ getBuilderConfig(type, className) {
|
|
90
|
+
const baseDir = path.resolve(__dirname$1, '..', 'builder-template', 'builder');
|
|
91
|
+
const templates = {
|
|
92
|
+
[ControllerType.CONTEXT_MENU]: 'context-menu.builder.template',
|
|
93
|
+
[ControllerType.SLASH]: 'slash.builder.template'
|
|
94
|
+
};
|
|
95
|
+
const template = templates[type] ? path.resolve(baseDir, templates[type]) : undefined;
|
|
96
|
+
return template ? {
|
|
97
|
+
template,
|
|
98
|
+
variables: {
|
|
99
|
+
className
|
|
100
|
+
}
|
|
101
|
+
} : undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { ControllerGeneratorHelper };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { Logger } from '../../common/logger.js';
|
|
3
|
+
import '../../common/theme.js';
|
|
4
|
+
import { validateAndFormatName, buildTemplate, createDirectoryIfNotExists, generateFile } from '../../util/generator-cli.util.js';
|
|
5
|
+
|
|
6
|
+
class GuardGeneratorHelper {
|
|
7
|
+
/**
|
|
8
|
+
* Generates a guard file based on the provided guard name.
|
|
9
|
+
* Validates and formats the guard name, creates the necessary directories,
|
|
10
|
+
* and generates the guard file using a predefined template.
|
|
11
|
+
*
|
|
12
|
+
* @param guardName - The name of the guard to generate.
|
|
13
|
+
* It can include slashes for nested paths.
|
|
14
|
+
* @throws Exits the process if the guard name is not provided or invalid.
|
|
15
|
+
*/ generateGuard(guardName) {
|
|
16
|
+
if (!guardName) {
|
|
17
|
+
this.logger.error('Guard name is required.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const { parts, kebabCaseName, className } = validateAndFormatName(guardName);
|
|
21
|
+
const guardDir = path.join(process.cwd(), 'src', 'guards', ...parts);
|
|
22
|
+
const guardFile = path.join(guardDir, `${kebabCaseName}.guard.ts`);
|
|
23
|
+
const guardTemplate = buildTemplate(className, 'guard.template');
|
|
24
|
+
createDirectoryIfNotExists(guardDir);
|
|
25
|
+
generateFile(guardFile, guardTemplate);
|
|
26
|
+
}
|
|
27
|
+
constructor(appName){
|
|
28
|
+
this.appName = appName;
|
|
29
|
+
this.logger = new Logger(this.appName);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { GuardGeneratorHelper };
|