meocord 2.1.1 → 3.1.0
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/AUTHOR.md +2 -3
- package/README.md +289 -41
- package/dist/cjs/_shared/controller.decorator-MUHA_A3z.cjs +529 -0
- package/dist/cjs/core/index.cjs +288 -85
- package/dist/cjs/decorator/index.cjs +5 -1
- package/dist/cjs/enum/index.cjs +17 -4
- package/dist/cjs/testing/index.cjs +88 -4
- package/dist/esm/bin/builder-template/builder/primary-entry-point.builder.template +26 -0
- package/dist/esm/bin/builder-template/controller/autocomplete.controller.template +16 -0
- package/dist/esm/bin/builder-template/controller/channel-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/context-menu.controller.template +2 -2
- package/dist/esm/bin/builder-template/controller/mentionable-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/modal-submit.controller.template +1 -1
- package/dist/esm/bin/builder-template/controller/primary-entry-point.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/role-select-menu.controller.template +12 -0
- package/dist/esm/bin/builder-template/controller/slash.controller.template +1 -1
- package/dist/esm/bin/builder-template/controller/user-select-menu.controller.template +12 -0
- package/dist/esm/bin/generator.js +4 -9
- package/dist/esm/bin/helper/controller-generator.helper.js +24 -7
- package/dist/esm/core/meocord.app.js +289 -85
- package/dist/esm/decorator/controller.decorator.js +157 -24
- package/dist/esm/decorator/index.js +1 -1
- package/dist/esm/enum/controller.enum.js +23 -4
- package/dist/esm/testing/mock-interaction.js +89 -5
- package/dist/esm/util/interaction.util.js +174 -0
- package/dist/types/controller.enum-DYfhYaat.d.ts +36 -0
- package/dist/types/core/index.d.ts +94 -0
- package/dist/types/decorator/index.d.ts +74 -41
- package/dist/types/enum/index.d.ts +1 -1
- package/dist/types/interface/index.d.ts +87 -3
- package/dist/types/testing/index.d.ts +3 -21
- package/package.json +2 -2
- package/dist/cjs/_shared/controller.decorator-DX5lFlPZ.cjs +0 -216
- package/dist/types/controller.enum-QA-IuReF.d.ts +0 -18
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kinds of interaction a `@Command` method can be bound to.
|
|
3
|
+
*
|
|
4
|
+
* Each member names one Discord interaction shape rather than a family of them, so a
|
|
5
|
+
* handler's parameter type follows from its command type alone. That is why the four
|
|
6
|
+
* entity select menus are separate members instead of one `SELECT_MENU`: Discord sends
|
|
7
|
+
* them as distinct component types (5-8) carrying different resolved data, and
|
|
8
|
+
* collapsing them would leave the handler with a union it has to re-narrow by hand.
|
|
9
|
+
*/
|
|
10
|
+
declare enum CommandType {
|
|
11
|
+
/** Chat input command, or one subcommand of it. */
|
|
12
|
+
SLASH = "SLASH",
|
|
13
|
+
/** User or message context menu command. */
|
|
14
|
+
CONTEXT_MENU = "CONTEXT_MENU",
|
|
15
|
+
/** Activity launch command (`ApplicationCommandType.PrimaryEntryPoint`). */
|
|
16
|
+
PRIMARY_ENTRY_POINT = "PRIMARY_ENTRY_POINT",
|
|
17
|
+
BUTTON = "BUTTON",
|
|
18
|
+
/** String select menu — the one whose options the application defines itself. */
|
|
19
|
+
SELECT_MENU = "SELECT_MENU",
|
|
20
|
+
USER_SELECT_MENU = "USER_SELECT_MENU",
|
|
21
|
+
ROLE_SELECT_MENU = "ROLE_SELECT_MENU",
|
|
22
|
+
MENTIONABLE_SELECT_MENU = "MENTIONABLE_SELECT_MENU",
|
|
23
|
+
CHANNEL_SELECT_MENU = "CHANNEL_SELECT_MENU",
|
|
24
|
+
MODAL_SUBMIT = "MODAL_SUBMIT"
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Enum representing actions that can be performed on a message reaction.
|
|
28
|
+
*/
|
|
29
|
+
declare enum ReactionHandlerAction {
|
|
30
|
+
/** Reaction added to a message. */
|
|
31
|
+
ADD = "ADD",
|
|
32
|
+
/** Reaction removed from a message. */
|
|
33
|
+
REMOVE = "REMOVE"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { CommandType as C, ReactionHandlerAction as R };
|
|
@@ -19,10 +19,104 @@ declare class MeoCordApp {
|
|
|
19
19
|
private activityInterval;
|
|
20
20
|
private controllerInstancesCache;
|
|
21
21
|
constructor(controllerClasses: (new (...args: any[]) => any)[], container: Container, discordClient: Client, discordToken: string, activities?: ActivityOptions[] | undefined);
|
|
22
|
+
/**
|
|
23
|
+
* Runs an event handler so a failure inside it cannot take the process down.
|
|
24
|
+
*
|
|
25
|
+
* discord.js calls listeners without awaiting them, so a rejection escaping one has
|
|
26
|
+
* nothing left to settle it: Node reports an unhandled rejection, which terminates
|
|
27
|
+
* the process by default. Losing the whole bot because one reaction landed on a
|
|
28
|
+
* deleted message, or one controller could not be resolved, is a worse failure than
|
|
29
|
+
* the one that caused it -- every other user is served by the same process.
|
|
30
|
+
*
|
|
31
|
+
* Nothing is silenced. The error is logged against the event that produced it, so a
|
|
32
|
+
* genuine misconfiguration -- an unbound controller, a missing dependency -- shows
|
|
33
|
+
* up on the very first interaction rather than staying hidden.
|
|
34
|
+
*
|
|
35
|
+
* @param event - The gateway event being handled, named in the log.
|
|
36
|
+
* @param run - The handler to run.
|
|
37
|
+
*/
|
|
38
|
+
private runListener;
|
|
39
|
+
/**
|
|
40
|
+
* Rotates the bot's activity.
|
|
41
|
+
*
|
|
42
|
+
* Guarded separately from {@link runListener}: this runs on a timer rather than an
|
|
43
|
+
* event, and a throw from a timer callback is an uncaught exception no listener
|
|
44
|
+
* wrapper can reach.
|
|
45
|
+
*/
|
|
46
|
+
private updateActivity;
|
|
22
47
|
private getInstance;
|
|
23
48
|
start(): Promise<void>;
|
|
24
49
|
registerCommands(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Every pattern-matched route, ordered most specific first.
|
|
52
|
+
*
|
|
53
|
+
* Built once and cached, so dispatch stays a single ordered walk with an early exit
|
|
54
|
+
* rather than paying to rank anything per interaction. Ordering here is what lets
|
|
55
|
+
* `gi-profile-summary-{ownerId}-{uid}` keep the ids it owns when
|
|
56
|
+
* `gi-profile-{uuid}-{uid}` would also match them — without it, the winner would be
|
|
57
|
+
* whichever controller happened to be registered first.
|
|
58
|
+
*/
|
|
59
|
+
private componentRoutes?;
|
|
60
|
+
private getComponentRoutes;
|
|
61
|
+
/**
|
|
62
|
+
* Warns rather than throws: an app whose patterns overlap boots and works today, and
|
|
63
|
+
* refusing to start would turn a latent mis-route into an outage on upgrade.
|
|
64
|
+
*/
|
|
65
|
+
private reportAmbiguousRoutes;
|
|
66
|
+
/**
|
|
67
|
+
* Every `@Autocomplete` handler, ordered so an option-specific handler is found
|
|
68
|
+
* before a command-wide one.
|
|
69
|
+
*
|
|
70
|
+
* Cached alongside the component table: autocomplete fires on every keystroke, and
|
|
71
|
+
* rebuilding the list per keystroke would put reflection on the hottest path the
|
|
72
|
+
* framework has.
|
|
73
|
+
*/
|
|
74
|
+
private autocompleteRoutes?;
|
|
75
|
+
private getAutocompleteRoutes;
|
|
76
|
+
/**
|
|
77
|
+
* Dispatches an interaction, and makes sure a failure anywhere in that still reaches
|
|
78
|
+
* the person who triggered it.
|
|
79
|
+
*
|
|
80
|
+
* {@link executeCommand} already reports what a handler throws, but everything
|
|
81
|
+
* *before* the handler can fail too — resolving a controller through the container
|
|
82
|
+
* is the common case — and a component that fails there would otherwise look dead
|
|
83
|
+
* with nothing said to the user and nothing in the log.
|
|
84
|
+
*/
|
|
25
85
|
private handleInteraction;
|
|
86
|
+
private dispatchInteraction;
|
|
87
|
+
/**
|
|
88
|
+
* The names a command interaction can be handled under, most specific first.
|
|
89
|
+
*
|
|
90
|
+
* Empty for anything that is not a registered command, which is how a component
|
|
91
|
+
* whose customId matched no pattern falls through to the unmatched warning instead
|
|
92
|
+
* of being looked up under a name it does not have.
|
|
93
|
+
*/
|
|
94
|
+
private resolveNameRoutes;
|
|
95
|
+
/**
|
|
96
|
+
* Answers an autocomplete interaction from the `@Autocomplete` handler that claims it.
|
|
97
|
+
*
|
|
98
|
+
* Discord closes the window after three seconds and shows a loading state until
|
|
99
|
+
* something arrives, so an unclaimed option is answered with an empty list rather
|
|
100
|
+
* than left to time out -- a visibly empty menu is a better failure than a stuck one,
|
|
101
|
+
* and the warning says which option is missing a handler.
|
|
102
|
+
*/
|
|
103
|
+
private handleAutocomplete;
|
|
104
|
+
/** Closes an autocomplete window that nothing else answered. */
|
|
105
|
+
private respondEmpty;
|
|
106
|
+
/**
|
|
107
|
+
* Runs a resolved command, shared by both dispatch paths so a pattern-matched
|
|
108
|
+
* component and a named slash command behave identically once the route is chosen.
|
|
109
|
+
*/
|
|
110
|
+
private executeCommand;
|
|
111
|
+
/**
|
|
112
|
+
* Tells the user something went wrong, if the interaction can still hear it.
|
|
113
|
+
*
|
|
114
|
+
* A handler that replies and *then* throws is the common shape of a failure, and
|
|
115
|
+
* replying twice throws in turn -- out of the catch block, where nothing is left to
|
|
116
|
+
* handle it. Whatever the interaction's state, reporting an error must not be able
|
|
117
|
+
* to become a second, worse one.
|
|
118
|
+
*/
|
|
119
|
+
private replyWithError;
|
|
26
120
|
private handleMessage;
|
|
27
121
|
private handleReaction;
|
|
28
122
|
private gracefulShutdown;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { BuildableCommandType, CommandBuilderBase, CommandBuilderConstructor, CommandInteractionType, ReactionHandlerOptions, AutocompleteMetadata, CommandMetadata, GuardInterface } from '../interface/index.js';
|
|
2
|
+
import { AutocompleteInteraction, OmitPartialGroupDMChannel, Message, MessageReaction, PartialMessageReaction, ClientOptions, ActivityOptions } from 'discord.js';
|
|
1
3
|
import { CommandType } from '../enum/index.js';
|
|
2
|
-
import { SlashCommandBuilder, SlashCommandSubcommandsOnlyBuilder, ContextMenuCommandBuilder, ButtonInteraction, StringSelectMenuInteraction, ChatInputCommandInteraction, UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction, ModalSubmitInteraction, OmitPartialGroupDMChannel, Message, MessageReaction, PartialMessageReaction, ClientOptions, ActivityOptions } from 'discord.js';
|
|
3
|
-
import { ReactionHandlerOptions, GuardInterface } from '../interface/index.js';
|
|
4
4
|
import { ServiceIdentifier } from 'inversify';
|
|
5
5
|
import 'webpack';
|
|
6
6
|
|
|
@@ -29,37 +29,6 @@ import 'webpack';
|
|
|
29
29
|
*/
|
|
30
30
|
declare function Service<T>(): (target: new (...args: any[]) => T) => void;
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* MeoCord Framework
|
|
34
|
-
* Copyright (c) 2025 Ukasyah Rahmatullah Zada
|
|
35
|
-
* SPDX-License-Identifier: MIT
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Base interface for a command builder.
|
|
40
|
-
*/
|
|
41
|
-
interface CommandBuilderBase<T extends CommandType.SLASH | CommandType.CONTEXT_MENU = CommandType.SLASH | CommandType.CONTEXT_MENU> {
|
|
42
|
-
/**
|
|
43
|
-
* Builds the command structure using the specified command name.
|
|
44
|
-
*
|
|
45
|
-
* @param commandName - The name of the command.
|
|
46
|
-
* @returns A SlashCommandBuilder or ContextMenuCommandBuilder instance.
|
|
47
|
-
*/
|
|
48
|
-
build: (commandName: string) => T extends CommandType.SLASH ? SlashCommandBuilder | SlashCommandSubcommandsOnlyBuilder : T extends CommandType.CONTEXT_MENU ? ContextMenuCommandBuilder : never;
|
|
49
|
-
}
|
|
50
|
-
type CommandBuilderConstructor<T extends CommandType.SLASH | CommandType.CONTEXT_MENU> = new () => CommandBuilderBase<T>;
|
|
51
|
-
/**
|
|
52
|
-
* Command metadata describing a registered command method.
|
|
53
|
-
*/
|
|
54
|
-
interface CommandMetadata<T extends string = string> {
|
|
55
|
-
methodName: string;
|
|
56
|
-
builder: ReturnType<CommandBuilderBase['build']> | undefined;
|
|
57
|
-
type: CommandType;
|
|
58
|
-
regex?: RegExp;
|
|
59
|
-
dynamicParams?: T[];
|
|
60
|
-
}
|
|
61
|
-
type CommandInteractionType<CBC extends CommandType.SLASH | CommandType.CONTEXT_MENU, T extends CommandBuilderConstructor<CBC> | CommandType> = T extends CommandType.BUTTON ? ButtonInteraction : T extends CommandType.SELECT_MENU ? StringSelectMenuInteraction : T extends CommandBuilderConstructor<CommandType.SLASH> ? ChatInputCommandInteraction : T extends CommandBuilderConstructor<CommandType.CONTEXT_MENU> ? UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction : T extends CommandType.MODAL_SUBMIT ? ModalSubmitInteraction : never;
|
|
62
|
-
|
|
63
32
|
/**
|
|
64
33
|
* MeoCord Framework
|
|
65
34
|
* Copyright (c) 2025 Ukasyah Rahmatullah Zada
|
|
@@ -84,7 +53,7 @@ type CommandInteractionType<CBC extends CommandType.SLASH | CommandType.CONTEXT_
|
|
|
84
53
|
* @returns A decorator function that makes the target class injectable
|
|
85
54
|
* and assigns the `commandType` metadata.
|
|
86
55
|
*/
|
|
87
|
-
declare function CommandBuilder<T extends
|
|
56
|
+
declare function CommandBuilder<T extends BuildableCommandType>(commandType: T): (target: new () => CommandBuilderBase<T>) => void;
|
|
88
57
|
|
|
89
58
|
/**
|
|
90
59
|
* MeoCord Framework
|
|
@@ -150,11 +119,18 @@ declare function getMessageHandlers(controller: any): {
|
|
|
150
119
|
keyword: string | undefined;
|
|
151
120
|
method: string;
|
|
152
121
|
}[];
|
|
122
|
+
/** The character a parameter will not cross, so one pattern segment maps to one value. */
|
|
123
|
+
declare const PARAM_SEPARATOR = "/";
|
|
153
124
|
/**
|
|
154
125
|
* Decorator to register command methods in a controller.
|
|
155
126
|
*
|
|
156
|
-
* @param commandName -
|
|
157
|
-
*
|
|
127
|
+
* @param commandName - What the command is addressed by. Commands registered with
|
|
128
|
+
* Discord use their name, and a subcommand its full path — `settings notify email`,
|
|
129
|
+
* parts separated by a space, the way Discord displays it. Components use a customId
|
|
130
|
+
* pattern, where `{name}` captures one `/`-separated segment.
|
|
131
|
+
* @param builderOrType - A command builder class, or a `CommandType` for a handler that
|
|
132
|
+
* registers nothing of its own: every component, and every subcommand of a command
|
|
133
|
+
* whose builder already describes it.
|
|
158
134
|
*
|
|
159
135
|
* @example
|
|
160
136
|
* ```typescript
|
|
@@ -163,13 +139,23 @@ declare function getMessageHandlers(controller: any): {
|
|
|
163
139
|
* await interaction.reply('This is the help command!')
|
|
164
140
|
* }
|
|
165
141
|
*
|
|
166
|
-
* @Command('
|
|
167
|
-
* public async
|
|
168
|
-
* await
|
|
142
|
+
* @Command('settings notify email', CommandType.SLASH)
|
|
143
|
+
* public async handleNotifyEmail(interaction: ChatInputCommandInteraction, { enabled }) {
|
|
144
|
+
* await interaction.reply(`Email notifications ${enabled ? 'on' : 'off'}`)
|
|
145
|
+
* }
|
|
146
|
+
*
|
|
147
|
+
* @Command('stats/{id}', CommandType.BUTTON)
|
|
148
|
+
* public async handleStats(interaction: ButtonInteraction, { id }) {
|
|
149
|
+
* await interaction.reply(`Fetching stats for ID: ${id}`);
|
|
150
|
+
* }
|
|
151
|
+
*
|
|
152
|
+
* @Command('assign/{taskId}', CommandType.USER_SELECT_MENU)
|
|
153
|
+
* public async handleAssign(interaction: UserSelectMenuInteraction, { taskId }) {
|
|
154
|
+
* await interaction.reply(`Assigned ${interaction.users.size} user(s) to ${taskId}`)
|
|
169
155
|
* }
|
|
170
156
|
* ```
|
|
171
157
|
*/
|
|
172
|
-
declare function Command<CBC extends
|
|
158
|
+
declare function Command<CBC extends BuildableCommandType, T extends CommandBuilderConstructor<CBC> | CommandType>(commandName: string, builderOrType: T): <P extends Record<string, any>, R extends Promise<void> | void>(target: object, propertyKey: string, _descriptor: TypedPropertyDescriptor<(interaction: CommandInteractionType<CBC, T>, params: P) => R> | TypedPropertyDescriptor<(interaction: CommandInteractionType<CBC, T>) => R>) => void;
|
|
173
159
|
/**
|
|
174
160
|
* Retrieves the command map for a given controller.
|
|
175
161
|
*
|
|
@@ -177,6 +163,41 @@ declare function Command<CBC extends CommandType.SLASH | CommandType.CONTEXT_MEN
|
|
|
177
163
|
* @returns A record containing command metadata indexed by command names.
|
|
178
164
|
*/
|
|
179
165
|
declare function getCommandMap<T extends string>(controller: any): Record<string, CommandMetadata<T>[]>;
|
|
166
|
+
/**
|
|
167
|
+
* Decorator to register an autocomplete handler for a chat input command's option.
|
|
168
|
+
*
|
|
169
|
+
* Autocomplete is a separate interaction from the command it belongs to, and Discord
|
|
170
|
+
* sends it while the user is still typing. It is not a `@Command`: nothing is
|
|
171
|
+
* registered for it — the option's own `setAutocomplete(true)` is what turns it on —
|
|
172
|
+
* and it is answered with `interaction.respond()` rather than a reply. Leaving it
|
|
173
|
+
* unhandled is not silent to the user: the client shows a loading state until the
|
|
174
|
+
* three-second window closes.
|
|
175
|
+
*
|
|
176
|
+
* @param commandPath - The command to complete, e.g. `settings` or `settings notify email`
|
|
177
|
+
* for a subcommand. Parts are separated by a single space, as Discord displays them.
|
|
178
|
+
* @param optionName - The option to complete. Omit to handle every option of the command,
|
|
179
|
+
* branching on `interaction.options.getFocused(true)`.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* @Autocomplete('search', 'query')
|
|
184
|
+
* async completeQuery(interaction: AutocompleteInteraction) {
|
|
185
|
+
* const { value } = interaction.options.getFocused(true)
|
|
186
|
+
* await interaction.respond(this.search(value).map(name => ({ name, value: name })))
|
|
187
|
+
* }
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
declare function Autocomplete<R extends void | Promise<void>>(commandPath: string, optionName?: string): <P extends Record<string, any>>(target: object, propertyKey: string, _descriptor: TypedPropertyDescriptor<(interaction: AutocompleteInteraction, params: P) => R> | TypedPropertyDescriptor<(interaction: AutocompleteInteraction) => R>) => void;
|
|
191
|
+
/**
|
|
192
|
+
* Retrieves autocomplete handler metadata from a given controller.
|
|
193
|
+
*
|
|
194
|
+
* Handlers naming an option come first, so a command-wide handler acts as the fallback
|
|
195
|
+
* for options no specific handler claimed rather than shadowing them by declaration order.
|
|
196
|
+
*
|
|
197
|
+
* @param controller - The controller class instance.
|
|
198
|
+
* @returns The registered autocomplete handlers, most specific first.
|
|
199
|
+
*/
|
|
200
|
+
declare function getAutocompleteHandlers(controller: any): AutocompleteMetadata[];
|
|
180
201
|
/**
|
|
181
202
|
* Decorator to mark a class as a controller that can later be registered to the App class `(app.ts)` using the `@MeoCord` decorator.
|
|
182
203
|
*
|
|
@@ -195,6 +216,18 @@ declare function getCommandMap<T extends string>(controller: any): Record<string
|
|
|
195
216
|
* ```
|
|
196
217
|
*/
|
|
197
218
|
declare function Controller(): (target: any) => void;
|
|
219
|
+
/**
|
|
220
|
+
* Finds pattern pairs that can both match one customId.
|
|
221
|
+
*
|
|
222
|
+
* Patterns of different segment counts are disjoint, because a parameter cannot cross
|
|
223
|
+
* `/`. Within the same count, two patterns overlap unless some position holds literals
|
|
224
|
+
* that differ: `a/{x}/c` and `a/b/{y}` both take `a/b/c`, and neither is more literal
|
|
225
|
+
* than the other, so ranking cannot settle it either.
|
|
226
|
+
*
|
|
227
|
+
* @param patterns - The registered patterns.
|
|
228
|
+
* @returns Each ambiguous pair, once, in the order the patterns were given.
|
|
229
|
+
*/
|
|
230
|
+
declare function findAmbiguousRoutes(patterns: string[]): [string, string][];
|
|
198
231
|
|
|
199
232
|
/**
|
|
200
233
|
* MeoCord Framework
|
|
@@ -334,4 +367,4 @@ declare function MeoCord(options: {
|
|
|
334
367
|
services?: ServiceIdentifier[];
|
|
335
368
|
}): (target: any) => void;
|
|
336
369
|
|
|
337
|
-
export { Command, CommandBuilder, Controller, Guard, MeoCord, MessageHandler, ReactionHandler, Service, UseGuard, getCommandMap, getMessageHandlers, getReactionHandlers };
|
|
370
|
+
export { Autocomplete, Command, CommandBuilder, Controller, Guard, MeoCord, MessageHandler, PARAM_SEPARATOR, ReactionHandler, Service, UseGuard, findAmbiguousRoutes, getAutocompleteHandlers, getCommandMap, getMessageHandlers, getReactionHandlers };
|
|
@@ -1,6 +1,90 @@
|
|
|
1
|
-
import { User, PartialUser, BaseInteraction, Message, MessageReaction } from 'discord.js';
|
|
1
|
+
import { SlashCommandBuilder, SlashCommandSubcommandsOnlyBuilder, ContextMenuCommandBuilder, RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody, ChatInputCommandInteraction, UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction, PrimaryEntryPointCommandInteraction, ButtonInteraction, StringSelectMenuInteraction, UserSelectMenuInteraction, RoleSelectMenuInteraction, MentionableSelectMenuInteraction, ChannelSelectMenuInteraction, ModalSubmitInteraction, User, PartialUser, BaseInteraction, Message, MessageReaction } from 'discord.js';
|
|
2
2
|
import { Configuration } from 'webpack';
|
|
3
|
-
import { ReactionHandlerAction } from '../enum/index.js';
|
|
3
|
+
import { CommandType, ReactionHandlerAction } from '../enum/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MeoCord Framework
|
|
7
|
+
* Copyright (c) 2025 Ukasyah Rahmatullah Zada
|
|
8
|
+
* SPDX-License-Identifier: MIT
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The command types that are registered with Discord, and so need a builder.
|
|
13
|
+
*
|
|
14
|
+
* Components are addressed by a customId the application makes up, so they have
|
|
15
|
+
* nothing to register; these three are published through `application.commands.set`.
|
|
16
|
+
*/
|
|
17
|
+
type BuildableCommandType = CommandType.SLASH | CommandType.CONTEXT_MENU | CommandType.PRIMARY_ENTRY_POINT;
|
|
18
|
+
/**
|
|
19
|
+
* The body an entry point command is registered with.
|
|
20
|
+
*
|
|
21
|
+
* `description` is added back on top of the discord-api-types body: that type omits it
|
|
22
|
+
* from every non-chat-input command, but the API accepts one for entry point commands
|
|
23
|
+
* — Discord's own default activity command ships with it set.
|
|
24
|
+
*/
|
|
25
|
+
type PrimaryEntryPointCommandData = RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody & {
|
|
26
|
+
description?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The payload `build()` returns for a given command type.
|
|
30
|
+
*
|
|
31
|
+
* `PRIMARY_ENTRY_POINT` yields a raw REST body rather than a builder because
|
|
32
|
+
* `@discordjs/builders` ships none for it; `ApplicationCommandManager#set` accepts the
|
|
33
|
+
* JSON body directly, so nothing is lost.
|
|
34
|
+
*/
|
|
35
|
+
type CommandBuildResult<T extends BuildableCommandType> = T extends CommandType.SLASH ? SlashCommandBuilder | SlashCommandSubcommandsOnlyBuilder : T extends CommandType.CONTEXT_MENU ? ContextMenuCommandBuilder : T extends CommandType.PRIMARY_ENTRY_POINT ? PrimaryEntryPointCommandData : never;
|
|
36
|
+
/**
|
|
37
|
+
* Base interface for a command builder.
|
|
38
|
+
*/
|
|
39
|
+
interface CommandBuilderBase<T extends BuildableCommandType = BuildableCommandType> {
|
|
40
|
+
/**
|
|
41
|
+
* Builds the command structure using the specified command name.
|
|
42
|
+
*
|
|
43
|
+
* @param commandName - The name of the command.
|
|
44
|
+
* @returns A builder instance, or a raw command body for entry point commands.
|
|
45
|
+
*/
|
|
46
|
+
build: (commandName: string) => CommandBuildResult<T>;
|
|
47
|
+
}
|
|
48
|
+
type CommandBuilderConstructor<T extends BuildableCommandType> = new () => CommandBuilderBase<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Command metadata describing a registered command method.
|
|
51
|
+
*/
|
|
52
|
+
interface CommandMetadata<T extends string = string> {
|
|
53
|
+
methodName: string;
|
|
54
|
+
builder: ReturnType<CommandBuilderBase['build']> | undefined;
|
|
55
|
+
type: CommandType;
|
|
56
|
+
regex?: RegExp;
|
|
57
|
+
dynamicParams?: T[];
|
|
58
|
+
/**
|
|
59
|
+
* How specific this pattern is; higher wins when more than one route matches the
|
|
60
|
+
* same customId. A greedy parameter can overlap a more literal sibling —
|
|
61
|
+
* `gi-profile-{uuid:*}-{uid}` also matches an id meant for
|
|
62
|
+
* `gi-profile-summary-{ownerId}-{uid}` — so dispatch cannot rely on declaration order.
|
|
63
|
+
*/
|
|
64
|
+
specificity?: number;
|
|
65
|
+
}
|
|
66
|
+
/** Metadata describing one `@Autocomplete` handler. */
|
|
67
|
+
interface AutocompleteMetadata {
|
|
68
|
+
/** The command path the handler serves, e.g. `settings` or `settings notify email`. */
|
|
69
|
+
commandPath: string;
|
|
70
|
+
/** The option it completes, or `undefined` to complete every option of that command. */
|
|
71
|
+
optionName?: string;
|
|
72
|
+
methodName: string;
|
|
73
|
+
}
|
|
74
|
+
/** The interaction class each non-buildable command type hands to its handler. */
|
|
75
|
+
interface ComponentInteractionMap {
|
|
76
|
+
[CommandType.BUTTON]: ButtonInteraction;
|
|
77
|
+
[CommandType.SELECT_MENU]: StringSelectMenuInteraction;
|
|
78
|
+
[CommandType.USER_SELECT_MENU]: UserSelectMenuInteraction;
|
|
79
|
+
[CommandType.ROLE_SELECT_MENU]: RoleSelectMenuInteraction;
|
|
80
|
+
[CommandType.MENTIONABLE_SELECT_MENU]: MentionableSelectMenuInteraction;
|
|
81
|
+
[CommandType.CHANNEL_SELECT_MENU]: ChannelSelectMenuInteraction;
|
|
82
|
+
[CommandType.MODAL_SUBMIT]: ModalSubmitInteraction;
|
|
83
|
+
[CommandType.SLASH]: ChatInputCommandInteraction;
|
|
84
|
+
[CommandType.CONTEXT_MENU]: UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction;
|
|
85
|
+
[CommandType.PRIMARY_ENTRY_POINT]: PrimaryEntryPointCommandInteraction;
|
|
86
|
+
}
|
|
87
|
+
type CommandInteractionType<CBC extends BuildableCommandType, T extends CommandBuilderConstructor<CBC> | CommandType> = T extends CommandBuilderConstructor<CommandType.SLASH> ? ChatInputCommandInteraction : T extends CommandBuilderConstructor<CommandType.CONTEXT_MENU> ? UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction : T extends CommandBuilderConstructor<CommandType.PRIMARY_ENTRY_POINT> ? PrimaryEntryPointCommandInteraction : T extends keyof ComponentInteractionMap ? ComponentInteractionMap[T] : never;
|
|
4
88
|
|
|
5
89
|
/**
|
|
6
90
|
* MeoCord Framework
|
|
@@ -92,4 +176,4 @@ interface MeoCordConfig {
|
|
|
92
176
|
webpack?: (config: MeoCordWebpackConfig) => MeoCordWebpackConfig | undefined;
|
|
93
177
|
}
|
|
94
178
|
|
|
95
|
-
export type { GuardInterface, MeoCordConfig, MeoCordWebpackConfig, ReactionHandlerOptions };
|
|
179
|
+
export type { AutocompleteMetadata, BuildableCommandType, CommandBuildResult, CommandBuilderBase, CommandBuilderConstructor, CommandInteractionType, CommandMetadata, GuardInterface, MeoCordConfig, MeoCordWebpackConfig, ReactionHandlerOptions };
|
|
@@ -2,7 +2,7 @@ import { ServiceIdentifier, Container } from 'inversify';
|
|
|
2
2
|
import { GuardInterface } from '../interface/index.js';
|
|
3
3
|
import { CacheType, CommandInteractionOptionResolver, Channel, Client, Guild, Message, User } from 'discord.js';
|
|
4
4
|
import 'webpack';
|
|
5
|
-
import '../controller.enum-
|
|
5
|
+
import '../controller.enum-DYfhYaat.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* MeoCord Framework
|
|
@@ -277,30 +277,12 @@ declare function createMockMessage(): DeepMocked<Message>;
|
|
|
277
277
|
interface ChatInputOptions {
|
|
278
278
|
subcommandGroup?: string | null;
|
|
279
279
|
subcommand?: string | null;
|
|
280
|
+
/** The option the user is currently typing, for autocomplete interactions. */
|
|
281
|
+
focused?: string | null;
|
|
280
282
|
[name: string]: string | number | boolean | {
|
|
281
283
|
id: string;
|
|
282
284
|
} | null | undefined;
|
|
283
285
|
}
|
|
284
|
-
/**
|
|
285
|
-
* Builds a typed options resolver from a plain record. Mirrors how the real
|
|
286
|
-
* `CommandInteractionOptionResolver` works: declare what options the command
|
|
287
|
-
* was invoked with, and the resolver finds them by name.
|
|
288
|
-
*
|
|
289
|
-
* All explicit methods are mock functions — override per test with `.mockReturnValue()`.
|
|
290
|
-
* Methods not listed (e.g. `getAttachment`) are auto-stubbed by the Proxy.
|
|
291
|
-
*
|
|
292
|
-
* @example
|
|
293
|
-
* ```ts
|
|
294
|
-
* const interaction = createMockInteraction(ChatInputCommandInteraction)
|
|
295
|
-
* interaction.options = createChatInputOptions({
|
|
296
|
-
* subcommandGroup: 'daily',
|
|
297
|
-
* subcommand: 'notes',
|
|
298
|
-
* uid: 12345678,
|
|
299
|
-
* })
|
|
300
|
-
* interaction.options.getSubcommand() // → 'notes'
|
|
301
|
-
* interaction.options.getNumber('uid') // → 12345678
|
|
302
|
-
* ```
|
|
303
|
-
*/
|
|
304
286
|
declare function createChatInputOptions<Cached extends CacheType = any>(opts?: ChatInputOptions): DeepMocked<CommandInteractionOptionResolver<Cached>>;
|
|
305
287
|
|
|
306
288
|
export { MeoCordTestingModule, TestingModule, TestingModuleBuilder, createChatInputOptions, createMock, createMockChannel, createMockClient, createMockFn, createMockGuild, createMockInteraction, createMockMessage, createMockUser, isMockFunction };
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meocord",
|
|
3
3
|
"description": "Decorator-based Discord bot framework built on discord.js. Brings NestJS-style controllers, dependency injection, guards, and testing utilities to bot development — with a full CLI and TypeScript-first design.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.1.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"lint": "eslint --fix . && tsc --noEmit && tsc --noEmit --project tsconfig.test.json",
|
|
10
|
+
"lint": "prettier --write \"**/*.md\" && eslint --fix . && tsc --noEmit && tsc --noEmit --project tsconfig.test.json",
|
|
11
11
|
"build": "rm -rf ./dist && rollup -c",
|
|
12
12
|
"prepare": "husky",
|
|
13
13
|
"test": "vitest run",
|