@julanzw/ttoolbox-discordjs-framework 1.4.0 → 1.5.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/dist/classes/Command.class.js +0 -1
- package/dist/classes/CommandManager.class.d.ts +123 -10
- package/dist/classes/CommandManager.class.js +231 -16
- package/dist/classes/MessageContextMenuCommand.class.d.ts +79 -0
- package/dist/classes/MessageContextMenuCommand.class.js +120 -0
- package/dist/classes/UserContextMenuCommand.class.d.ts +78 -0
- package/dist/classes/UserContextMenuCommand.class.js +119 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/types/loadCommands.d.ts +29 -0
- package/dist/types/loadCommands.js +1 -0
- package/dist/utils/editAndReply.d.ts +2 -2
- package/dist/utils/loadCommands.d.ts +27 -0
- package/dist/utils/loadCommands.js +129 -0
- package/package.json +1 -1
|
@@ -1,36 +1,105 @@
|
|
|
1
|
-
import { ChatInputCommandInteraction, Client,
|
|
1
|
+
import { ChatInputCommandInteraction, Client, MessageContextMenuCommandInteraction, RESTPostAPIApplicationCommandsJSONBody, UserContextMenuCommandInteraction } from 'discord.js';
|
|
2
2
|
import { ILogger } from '../types/logger.js';
|
|
3
3
|
import { Command } from './Command.class.js';
|
|
4
4
|
import { SubcommandGroup } from './SubcommandGroup.class.js';
|
|
5
5
|
import { ErrorReporter } from '../utils/ErrorReporter.js';
|
|
6
|
+
import { MessageContextMenuCommand } from './MessageContextMenuCommand.class.js';
|
|
7
|
+
import { UserContextMenuCommand } from './UserContextMenuCommand.class.js';
|
|
8
|
+
import { LoadCommandsOptions } from '../types/loadCommands.js';
|
|
6
9
|
export declare class CommandManager {
|
|
7
10
|
private commands;
|
|
11
|
+
private userContextMenuCommands;
|
|
12
|
+
private messageContextMenuCommands;
|
|
8
13
|
protected logger?: ILogger;
|
|
9
14
|
protected errorReporter?: ErrorReporter;
|
|
10
15
|
/**
|
|
16
|
+
* Register commands (accepts single, array, or mixed types)
|
|
17
|
+
* @param command - Command(s) to register
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Single command
|
|
21
|
+
* commandManager.registerCommand(new PingCommand());
|
|
22
|
+
*
|
|
23
|
+
* // Array of commands
|
|
24
|
+
* commandManager.registerCommand([cmd1, cmd2, cmd3]);
|
|
25
|
+
*
|
|
26
|
+
* // Mixed types
|
|
27
|
+
* commandManager.registerCommand([
|
|
28
|
+
* new PingCommand(),
|
|
29
|
+
* new UserContextMenu(),
|
|
30
|
+
* new SubcommandGroup(),
|
|
31
|
+
* ]);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
registerCommand(command: Command | SubcommandGroup | UserContextMenuCommand | MessageContextMenuCommand | Array<Command | SubcommandGroup | UserContextMenuCommand | MessageContextMenuCommand | any>): void;
|
|
35
|
+
/**
|
|
36
|
+
* @experimental
|
|
37
|
+
* Automatically load and register all commands from a directory.
|
|
38
|
+
*
|
|
39
|
+
* This is a convenience wrapper around the loadCommands utility.
|
|
40
|
+
*
|
|
41
|
+
* @param dirPath - Absolute path to the directory containing commands
|
|
42
|
+
* @param options - Optional configuration
|
|
43
|
+
* @returns Number of commands successfully loaded
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // Basic usage
|
|
48
|
+
* const count = await commandManager.loadFromDirectory('./src/commands');
|
|
49
|
+
* console.log(`Loaded ${count} commands`);
|
|
50
|
+
*
|
|
51
|
+
* // With options
|
|
52
|
+
* await commandManager.loadFromDirectory('./src/commands', {
|
|
53
|
+
* verbose: true,
|
|
54
|
+
* skipDirs: ['subcommands', 'test'],
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
loadFromDirectory(dirPath: string, options?: LoadCommandsOptions): Promise<number>;
|
|
59
|
+
/**
|
|
60
|
+
* @deprecated Use `registerCommand()` instead
|
|
61
|
+
*
|
|
11
62
|
* Register a single command or subcommand group
|
|
12
63
|
*/
|
|
13
64
|
register(command: Command | SubcommandGroup): this;
|
|
14
65
|
/**
|
|
66
|
+
* @deprecated Use `registerCommand()` with an array instead
|
|
67
|
+
*
|
|
15
68
|
* Register multiple commands at once
|
|
16
69
|
*/
|
|
17
70
|
registerMultiple(commands: Array<Command | SubcommandGroup>): this;
|
|
18
71
|
/**
|
|
19
|
-
* Get a specific command by name
|
|
72
|
+
* Get a specific slash command by name
|
|
20
73
|
*/
|
|
21
74
|
get(name: string): Command | SubcommandGroup | undefined;
|
|
22
75
|
/**
|
|
23
|
-
* Get
|
|
76
|
+
* Get a specific user context menu command by name
|
|
77
|
+
*/
|
|
78
|
+
getUserContextMenu(name: string): UserContextMenuCommand | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Get a specific message context menu command by name
|
|
81
|
+
*/
|
|
82
|
+
getMessageContextMenu(name: string): MessageContextMenuCommand | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Get all registered slash commands
|
|
24
85
|
*/
|
|
25
86
|
getAll(): Array<Command | SubcommandGroup>;
|
|
26
87
|
/**
|
|
27
|
-
* Get all
|
|
88
|
+
* Get all registered user context menu commands
|
|
89
|
+
*/
|
|
90
|
+
getAllUserContextMenus(): UserContextMenuCommand[];
|
|
91
|
+
/**
|
|
92
|
+
* Get all registered message context menu commands
|
|
93
|
+
*/
|
|
94
|
+
getAllMessageContextMenus(): MessageContextMenuCommand[];
|
|
95
|
+
/**
|
|
96
|
+
* Get all slash commands sorted alphabetically by name
|
|
28
97
|
*/
|
|
29
98
|
getAllSorted(): Array<Command | SubcommandGroup>;
|
|
30
99
|
/**
|
|
31
100
|
* Convert all commands to Discord JSON format for registration
|
|
32
101
|
*/
|
|
33
|
-
|
|
102
|
+
toJSON(): RESTPostAPIApplicationCommandsJSONBody[];
|
|
34
103
|
/**
|
|
35
104
|
* Generate paginated help pages for display in help command
|
|
36
105
|
* Returns a 2D array where each inner array is a page of command descriptions
|
|
@@ -48,25 +117,69 @@ export declare class CommandManager {
|
|
|
48
117
|
*/
|
|
49
118
|
executeCommand(commandName: string, interaction: ChatInputCommandInteraction, client: Client): Promise<void>;
|
|
50
119
|
/**
|
|
51
|
-
*
|
|
120
|
+
* Execute a user context menu command.
|
|
121
|
+
*/
|
|
122
|
+
executeUserContextMenu(commandName: string, interaction: UserContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Execute a message context menu command.
|
|
125
|
+
*/
|
|
126
|
+
executeMessageContextMenu(commandName: string, interaction: MessageContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Get total number of registered slash commands
|
|
52
129
|
*/
|
|
53
130
|
get size(): number;
|
|
54
131
|
/**
|
|
55
|
-
*
|
|
132
|
+
* Get total number of all registered commands
|
|
133
|
+
*/
|
|
134
|
+
get totalSize(): number;
|
|
135
|
+
/**
|
|
136
|
+
* Check if a slash command exists
|
|
56
137
|
*/
|
|
57
138
|
has(name: string): boolean;
|
|
58
139
|
/**
|
|
59
|
-
*
|
|
140
|
+
* Check if a user context menu command exists
|
|
141
|
+
*/
|
|
142
|
+
hasUserContextMenu(name: string): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Check if a message context menu command exists
|
|
145
|
+
*/
|
|
146
|
+
hasMessageContextMenu(name: string): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Check if any command (slash, user context, or message context) exists
|
|
149
|
+
*/
|
|
150
|
+
hasAny(name: string): boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Remove a slash command (useful for hot-reloading in dev)
|
|
60
153
|
*/
|
|
61
154
|
unregister(name: string): boolean;
|
|
62
155
|
/**
|
|
63
|
-
*
|
|
156
|
+
* Remove a user context menu command
|
|
157
|
+
*/
|
|
158
|
+
unregisterUserContextMenu(name: string): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Remove a message context menu command
|
|
161
|
+
*/
|
|
162
|
+
unregisterMessageContextMenu(name: string): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Clear all commands (slash commands, user context menus, and message context menus)
|
|
64
165
|
*/
|
|
65
166
|
clear(): void;
|
|
66
167
|
/**
|
|
67
|
-
* Get command names as an array
|
|
168
|
+
* Get slash command names as an array
|
|
68
169
|
*/
|
|
69
170
|
getCommandNames(): string[];
|
|
171
|
+
/**
|
|
172
|
+
* Get user context menu command names as an array
|
|
173
|
+
*/
|
|
174
|
+
getUserContextMenuNames(): string[];
|
|
175
|
+
/**
|
|
176
|
+
* Get message context menu command names as an array
|
|
177
|
+
*/
|
|
178
|
+
getMessageContextMenuNames(): string[];
|
|
179
|
+
/**
|
|
180
|
+
* Get all command names (slash + context menus) as an array
|
|
181
|
+
*/
|
|
182
|
+
getAllCommandNames(): string[];
|
|
70
183
|
setLogger(logger: ILogger): this;
|
|
71
184
|
/**
|
|
72
185
|
* Set the error reporter for all commands
|
|
@@ -1,42 +1,148 @@
|
|
|
1
|
+
import { Command } from './Command.class.js';
|
|
1
2
|
import { SubcommandGroup } from './SubcommandGroup.class.js';
|
|
3
|
+
import { MessageContextMenuCommand } from './MessageContextMenuCommand.class.js';
|
|
4
|
+
import { UserContextMenuCommand } from './UserContextMenuCommand.class.js';
|
|
5
|
+
import { loadCommands } from '../utils/loadCommands.js';
|
|
2
6
|
export class CommandManager {
|
|
3
7
|
constructor() {
|
|
4
8
|
this.commands = new Map();
|
|
9
|
+
this.userContextMenuCommands = new Map();
|
|
10
|
+
this.messageContextMenuCommands = new Map();
|
|
5
11
|
}
|
|
6
12
|
/**
|
|
7
|
-
* Register
|
|
13
|
+
* Register commands (accepts single, array, or mixed types)
|
|
14
|
+
* @param command - Command(s) to register
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Single command
|
|
18
|
+
* commandManager.registerCommand(new PingCommand());
|
|
19
|
+
*
|
|
20
|
+
* // Array of commands
|
|
21
|
+
* commandManager.registerCommand([cmd1, cmd2, cmd3]);
|
|
22
|
+
*
|
|
23
|
+
* // Mixed types
|
|
24
|
+
* commandManager.registerCommand([
|
|
25
|
+
* new PingCommand(),
|
|
26
|
+
* new UserContextMenu(),
|
|
27
|
+
* new SubcommandGroup(),
|
|
28
|
+
* ]);
|
|
29
|
+
* ```
|
|
8
30
|
*/
|
|
9
|
-
|
|
10
|
-
if (
|
|
31
|
+
registerCommand(command) {
|
|
32
|
+
if (Array.isArray(command)) {
|
|
33
|
+
for (const cmd of command) {
|
|
34
|
+
this.registerCommand(cmd);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (this.logger && 'setLogger' in command) {
|
|
11
39
|
command.setLogger(this.logger);
|
|
12
40
|
}
|
|
13
|
-
if (this.errorReporter) {
|
|
41
|
+
if (this.errorReporter && 'setErrorReporter' in command) {
|
|
14
42
|
command.setErrorReporter(this.errorReporter);
|
|
15
43
|
}
|
|
16
|
-
|
|
44
|
+
if (command instanceof Command || command instanceof SubcommandGroup) {
|
|
45
|
+
this.commands.set(command.name, command);
|
|
46
|
+
}
|
|
47
|
+
else if (command instanceof UserContextMenuCommand) {
|
|
48
|
+
this.userContextMenuCommands.set(command.name, command);
|
|
49
|
+
}
|
|
50
|
+
else if (command instanceof MessageContextMenuCommand) {
|
|
51
|
+
this.messageContextMenuCommands.set(command.name, command);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.logger?.warn(`Attempted to register invalid command type: ${command?.constructor?.name || 'unknown'}`, 'command-manager');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @experimental
|
|
59
|
+
* Automatically load and register all commands from a directory.
|
|
60
|
+
*
|
|
61
|
+
* This is a convenience wrapper around the loadCommands utility.
|
|
62
|
+
*
|
|
63
|
+
* @param dirPath - Absolute path to the directory containing commands
|
|
64
|
+
* @param options - Optional configuration
|
|
65
|
+
* @returns Number of commands successfully loaded
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // Basic usage
|
|
70
|
+
* const count = await commandManager.loadFromDirectory('./src/commands');
|
|
71
|
+
* console.log(`Loaded ${count} commands`);
|
|
72
|
+
*
|
|
73
|
+
* // With options
|
|
74
|
+
* await commandManager.loadFromDirectory('./src/commands', {
|
|
75
|
+
* verbose: true,
|
|
76
|
+
* skipDirs: ['subcommands', 'test'],
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
async loadFromDirectory(dirPath, options = {}) {
|
|
81
|
+
const commands = await loadCommands(dirPath, options);
|
|
82
|
+
this.registerCommand(commands);
|
|
83
|
+
const validCount = commands.filter(cmd => cmd instanceof Command ||
|
|
84
|
+
cmd instanceof SubcommandGroup ||
|
|
85
|
+
cmd instanceof UserContextMenuCommand ||
|
|
86
|
+
cmd instanceof MessageContextMenuCommand).length;
|
|
87
|
+
this.logger?.info(`Auto-loaded ${validCount} commands from ${dirPath}`, 'command-manager');
|
|
88
|
+
return validCount;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* @deprecated Use `registerCommand()` instead
|
|
92
|
+
*
|
|
93
|
+
* Register a single command or subcommand group
|
|
94
|
+
*/
|
|
95
|
+
register(command) {
|
|
96
|
+
this.registerCommand(command);
|
|
17
97
|
return this;
|
|
18
98
|
}
|
|
19
99
|
/**
|
|
100
|
+
* @deprecated Use `registerCommand()` with an array instead
|
|
101
|
+
*
|
|
20
102
|
* Register multiple commands at once
|
|
21
103
|
*/
|
|
22
104
|
registerMultiple(commands) {
|
|
23
|
-
|
|
105
|
+
this.registerCommand(commands);
|
|
24
106
|
return this;
|
|
25
107
|
}
|
|
26
108
|
/**
|
|
27
|
-
* Get a specific command by name
|
|
109
|
+
* Get a specific slash command by name
|
|
28
110
|
*/
|
|
29
111
|
get(name) {
|
|
30
112
|
return this.commands.get(name);
|
|
31
113
|
}
|
|
32
114
|
/**
|
|
33
|
-
* Get
|
|
115
|
+
* Get a specific user context menu command by name
|
|
116
|
+
*/
|
|
117
|
+
getUserContextMenu(name) {
|
|
118
|
+
return this.userContextMenuCommands.get(name);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get a specific message context menu command by name
|
|
122
|
+
*/
|
|
123
|
+
getMessageContextMenu(name) {
|
|
124
|
+
return this.messageContextMenuCommands.get(name);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get all registered slash commands
|
|
34
128
|
*/
|
|
35
129
|
getAll() {
|
|
36
130
|
return Array.from(this.commands.values());
|
|
37
131
|
}
|
|
38
132
|
/**
|
|
39
|
-
* Get all
|
|
133
|
+
* Get all registered user context menu commands
|
|
134
|
+
*/
|
|
135
|
+
getAllUserContextMenus() {
|
|
136
|
+
return Array.from(this.userContextMenuCommands.values());
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get all registered message context menu commands
|
|
140
|
+
*/
|
|
141
|
+
getAllMessageContextMenus() {
|
|
142
|
+
return Array.from(this.messageContextMenuCommands.values());
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get all slash commands sorted alphabetically by name
|
|
40
146
|
*/
|
|
41
147
|
getAllSorted() {
|
|
42
148
|
return this.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -44,13 +150,26 @@ export class CommandManager {
|
|
|
44
150
|
/**
|
|
45
151
|
* Convert all commands to Discord JSON format for registration
|
|
46
152
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
153
|
+
toJSON() {
|
|
154
|
+
const slashCommands = Array.from(this.commands.values()).map((cmd) => {
|
|
49
155
|
if (process.env.ENV === 'dev') {
|
|
50
156
|
console.log(`Registering: ${cmd.name}`);
|
|
51
157
|
}
|
|
52
158
|
return cmd.toJSON();
|
|
53
159
|
});
|
|
160
|
+
const userContextMenus = Array.from(this.userContextMenuCommands.values()).map((cmd) => {
|
|
161
|
+
if (process.env.ENV === 'dev') {
|
|
162
|
+
console.log(`Registering user context menu: ${cmd.name}`);
|
|
163
|
+
}
|
|
164
|
+
return cmd.toJSON();
|
|
165
|
+
});
|
|
166
|
+
const messageContextMenus = Array.from(this.messageContextMenuCommands.values()).map((cmd) => {
|
|
167
|
+
if (process.env.ENV === 'dev') {
|
|
168
|
+
console.log(`Registering message context menu: ${cmd.name}`);
|
|
169
|
+
}
|
|
170
|
+
return cmd.toJSON();
|
|
171
|
+
});
|
|
172
|
+
return [...slashCommands, ...userContextMenus, ...messageContextMenus];
|
|
54
173
|
}
|
|
55
174
|
/**
|
|
56
175
|
* Generate paginated help pages for display in help command
|
|
@@ -112,41 +231,131 @@ export class CommandManager {
|
|
|
112
231
|
await command.execute(interaction, client);
|
|
113
232
|
}
|
|
114
233
|
/**
|
|
115
|
-
*
|
|
234
|
+
* Execute a user context menu command.
|
|
235
|
+
*/
|
|
236
|
+
async executeUserContextMenu(commandName, interaction, client) {
|
|
237
|
+
const command = this.userContextMenuCommands.get(commandName);
|
|
238
|
+
if (!command) {
|
|
239
|
+
throw new Error(`User context menu command not found: ${commandName}`);
|
|
240
|
+
}
|
|
241
|
+
await command.execute(interaction, client);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Execute a message context menu command.
|
|
245
|
+
*/
|
|
246
|
+
async executeMessageContextMenu(commandName, interaction, client) {
|
|
247
|
+
const command = this.messageContextMenuCommands.get(commandName);
|
|
248
|
+
if (!command) {
|
|
249
|
+
throw new Error(`Message context menu command not found: ${commandName}`);
|
|
250
|
+
}
|
|
251
|
+
await command.execute(interaction, client);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get total number of registered slash commands
|
|
116
255
|
*/
|
|
117
256
|
get size() {
|
|
118
257
|
return this.commands.size;
|
|
119
258
|
}
|
|
120
259
|
/**
|
|
121
|
-
*
|
|
260
|
+
* Get total number of all registered commands
|
|
261
|
+
*/
|
|
262
|
+
get totalSize() {
|
|
263
|
+
return this.commands.size +
|
|
264
|
+
this.userContextMenuCommands.size +
|
|
265
|
+
this.messageContextMenuCommands.size;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Check if a slash command exists
|
|
122
269
|
*/
|
|
123
270
|
has(name) {
|
|
124
271
|
return this.commands.has(name);
|
|
125
272
|
}
|
|
126
273
|
/**
|
|
127
|
-
*
|
|
274
|
+
* Check if a user context menu command exists
|
|
275
|
+
*/
|
|
276
|
+
hasUserContextMenu(name) {
|
|
277
|
+
return this.userContextMenuCommands.has(name);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Check if a message context menu command exists
|
|
281
|
+
*/
|
|
282
|
+
hasMessageContextMenu(name) {
|
|
283
|
+
return this.messageContextMenuCommands.has(name);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Check if any command (slash, user context, or message context) exists
|
|
287
|
+
*/
|
|
288
|
+
hasAny(name) {
|
|
289
|
+
return this.commands.has(name) ||
|
|
290
|
+
this.userContextMenuCommands.has(name) ||
|
|
291
|
+
this.messageContextMenuCommands.has(name);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Remove a slash command (useful for hot-reloading in dev)
|
|
128
295
|
*/
|
|
129
296
|
unregister(name) {
|
|
130
297
|
return this.commands.delete(name);
|
|
131
298
|
}
|
|
132
299
|
/**
|
|
133
|
-
*
|
|
300
|
+
* Remove a user context menu command
|
|
301
|
+
*/
|
|
302
|
+
unregisterUserContextMenu(name) {
|
|
303
|
+
return this.userContextMenuCommands.delete(name);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Remove a message context menu command
|
|
307
|
+
*/
|
|
308
|
+
unregisterMessageContextMenu(name) {
|
|
309
|
+
return this.messageContextMenuCommands.delete(name);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Clear all commands (slash commands, user context menus, and message context menus)
|
|
134
313
|
*/
|
|
135
314
|
clear() {
|
|
136
315
|
this.commands.clear();
|
|
316
|
+
this.userContextMenuCommands.clear();
|
|
317
|
+
this.messageContextMenuCommands.clear();
|
|
137
318
|
}
|
|
138
319
|
/**
|
|
139
|
-
* Get command names as an array
|
|
320
|
+
* Get slash command names as an array
|
|
140
321
|
*/
|
|
141
322
|
getCommandNames() {
|
|
142
323
|
return Array.from(this.commands.keys());
|
|
143
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Get user context menu command names as an array
|
|
327
|
+
*/
|
|
328
|
+
getUserContextMenuNames() {
|
|
329
|
+
return Array.from(this.userContextMenuCommands.keys());
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get message context menu command names as an array
|
|
333
|
+
*/
|
|
334
|
+
getMessageContextMenuNames() {
|
|
335
|
+
return Array.from(this.messageContextMenuCommands.keys());
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Get all command names (slash + context menus) as an array
|
|
339
|
+
*/
|
|
340
|
+
getAllCommandNames() {
|
|
341
|
+
return [
|
|
342
|
+
...this.getCommandNames(),
|
|
343
|
+
...this.getUserContextMenuNames(),
|
|
344
|
+
...this.getMessageContextMenuNames(),
|
|
345
|
+
];
|
|
346
|
+
}
|
|
144
347
|
setLogger(logger) {
|
|
145
348
|
this.logger = logger;
|
|
146
349
|
// Inject logger into all already-registered commands
|
|
147
350
|
for (const command of this.commands.values()) {
|
|
148
351
|
command.setLogger(logger);
|
|
149
352
|
}
|
|
353
|
+
for (const command of this.userContextMenuCommands.values()) {
|
|
354
|
+
command.setLogger(logger);
|
|
355
|
+
}
|
|
356
|
+
for (const command of this.messageContextMenuCommands.values()) {
|
|
357
|
+
command.setLogger(logger);
|
|
358
|
+
}
|
|
150
359
|
return this;
|
|
151
360
|
}
|
|
152
361
|
/**
|
|
@@ -157,6 +366,12 @@ export class CommandManager {
|
|
|
157
366
|
for (const command of this.commands.values()) {
|
|
158
367
|
command.setErrorReporter(reporter);
|
|
159
368
|
}
|
|
369
|
+
for (const command of this.userContextMenuCommands.values()) {
|
|
370
|
+
command.setErrorReporter(reporter);
|
|
371
|
+
}
|
|
372
|
+
for (const command of this.messageContextMenuCommands.values()) {
|
|
373
|
+
command.setErrorReporter(reporter);
|
|
374
|
+
}
|
|
160
375
|
return this;
|
|
161
376
|
}
|
|
162
377
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { MessageContextMenuCommandInteraction, Client } from 'discord.js';
|
|
2
|
+
import type { ILogger } from '../types/logger.js';
|
|
3
|
+
import type { ErrorReporter } from '../utils/ErrorReporter.js';
|
|
4
|
+
import { PermissionLevel } from '../types/permission.js';
|
|
5
|
+
/**
|
|
6
|
+
* Base class for Message Context Menu Commands.
|
|
7
|
+
*
|
|
8
|
+
* Message context menu commands appear when right-clicking on a message and
|
|
9
|
+
* selecting "Apps" in the context menu.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* export class ReportMessageCommand extends MessageContextMenuCommand {
|
|
14
|
+
* name = 'Report Message';
|
|
15
|
+
* guildOnly = true;
|
|
16
|
+
* permissionLevel = 'user' as const;
|
|
17
|
+
*
|
|
18
|
+
* protected async run(interaction: MessageContextMenuCommandInteraction) {
|
|
19
|
+
* const message = interaction.targetMessage;
|
|
20
|
+
* await reportMessage(message);
|
|
21
|
+
* await interaction.reply({ content: 'Message reported!', ephemeral: true });
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class MessageContextMenuCommand {
|
|
27
|
+
abstract name: string;
|
|
28
|
+
abstract guildOnly: boolean;
|
|
29
|
+
abstract permissionLevel: PermissionLevel;
|
|
30
|
+
protected logger?: ILogger;
|
|
31
|
+
protected errorReporter?: ErrorReporter;
|
|
32
|
+
/**
|
|
33
|
+
* The main execution method - implement your command logic here.
|
|
34
|
+
*/
|
|
35
|
+
protected abstract run(interaction: MessageContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Optional: Called before command execution.
|
|
38
|
+
* Return false to stop execution.
|
|
39
|
+
*/
|
|
40
|
+
protected beforeExecute?(interaction: MessageContextMenuCommandInteraction, client: Client): Promise<boolean | void>;
|
|
41
|
+
/**
|
|
42
|
+
* Optional: Called after successful command execution.
|
|
43
|
+
*/
|
|
44
|
+
protected afterExecute?(interaction: MessageContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Optional: Called when command execution fails.
|
|
47
|
+
*/
|
|
48
|
+
protected onError?(interaction: MessageContextMenuCommandInteraction, error: Error, client: Client): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Set the logger for this command.
|
|
51
|
+
*/
|
|
52
|
+
setLogger(logger: ILogger): void;
|
|
53
|
+
/**
|
|
54
|
+
* Set the error reporter for this command.
|
|
55
|
+
*/
|
|
56
|
+
setErrorReporter(reporter: ErrorReporter): void;
|
|
57
|
+
/**
|
|
58
|
+
* Log a message using the configured logger.
|
|
59
|
+
*/
|
|
60
|
+
protected log(message: string, level: string, scope: string, logToConsole?: boolean): void;
|
|
61
|
+
/**
|
|
62
|
+
* Validate the interaction (guild-only check).
|
|
63
|
+
*/
|
|
64
|
+
protected validate(interaction: MessageContextMenuCommandInteraction): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Check if the user has permission to use this command.
|
|
67
|
+
* Override this method to implement custom permission logic.
|
|
68
|
+
*/
|
|
69
|
+
protected hasPermission(interaction: MessageContextMenuCommandInteraction): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Execute the command with validation and error handling.
|
|
72
|
+
*/
|
|
73
|
+
execute(interaction: MessageContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
74
|
+
private safeExecute;
|
|
75
|
+
/**
|
|
76
|
+
* Convert this command to Discord API JSON format.
|
|
77
|
+
*/
|
|
78
|
+
toJSON(): import("discord.js").RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
|
79
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { ContextMenuCommandBuilder, ApplicationCommandType, } from 'discord.js';
|
|
2
|
+
import { safeReply } from '../utils/editAndReply.js';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for Message Context Menu Commands.
|
|
5
|
+
*
|
|
6
|
+
* Message context menu commands appear when right-clicking on a message and
|
|
7
|
+
* selecting "Apps" in the context menu.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* export class ReportMessageCommand extends MessageContextMenuCommand {
|
|
12
|
+
* name = 'Report Message';
|
|
13
|
+
* guildOnly = true;
|
|
14
|
+
* permissionLevel = 'user' as const;
|
|
15
|
+
*
|
|
16
|
+
* protected async run(interaction: MessageContextMenuCommandInteraction) {
|
|
17
|
+
* const message = interaction.targetMessage;
|
|
18
|
+
* await reportMessage(message);
|
|
19
|
+
* await interaction.reply({ content: 'Message reported!', ephemeral: true });
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class MessageContextMenuCommand {
|
|
25
|
+
/**
|
|
26
|
+
* Set the logger for this command.
|
|
27
|
+
*/
|
|
28
|
+
setLogger(logger) {
|
|
29
|
+
this.logger = logger;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Set the error reporter for this command.
|
|
33
|
+
*/
|
|
34
|
+
setErrorReporter(reporter) {
|
|
35
|
+
this.errorReporter = reporter;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Log a message using the configured logger.
|
|
39
|
+
*/
|
|
40
|
+
log(message, level, scope, logToConsole = false) {
|
|
41
|
+
this.logger?.log(message, level, scope, logToConsole);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate the interaction (guild-only check).
|
|
45
|
+
*/
|
|
46
|
+
validate(interaction) {
|
|
47
|
+
if (this.guildOnly && !interaction.guildId) {
|
|
48
|
+
return 'This command can only be used in a server.';
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if the user has permission to use this command.
|
|
54
|
+
* Override this method to implement custom permission logic.
|
|
55
|
+
*/
|
|
56
|
+
async hasPermission(interaction) {
|
|
57
|
+
// Default: everyone has permission
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Execute the command with validation and error handling.
|
|
62
|
+
*/
|
|
63
|
+
async execute(interaction, client) {
|
|
64
|
+
await this.safeExecute(this.name, interaction, client, async () => {
|
|
65
|
+
const error = this.validate(interaction);
|
|
66
|
+
if (error)
|
|
67
|
+
return await safeReply(interaction, error, true);
|
|
68
|
+
if (!(await this.hasPermission(interaction))) {
|
|
69
|
+
return await safeReply(interaction, 'You do not have permission to use this command.', true);
|
|
70
|
+
}
|
|
71
|
+
if (this.beforeExecute) {
|
|
72
|
+
const shouldContinue = await this.beforeExecute(interaction, client);
|
|
73
|
+
if (shouldContinue === false)
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
await this.run(interaction, client);
|
|
78
|
+
if (this.afterExecute) {
|
|
79
|
+
await this.afterExecute(interaction, client);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (this.onError) {
|
|
84
|
+
await this.onError(interaction, err, client);
|
|
85
|
+
}
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async safeExecute(commandName, interaction, client, fn) {
|
|
91
|
+
const scope = `${commandName}_EXECUTION`;
|
|
92
|
+
try {
|
|
93
|
+
await fn();
|
|
94
|
+
this.log(`${commandName} context menu command executed`, 'info', scope);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
this.log('An Error occurred: ' + err, 'error', scope, true);
|
|
98
|
+
if (this.errorReporter) {
|
|
99
|
+
await this.errorReporter.reportError(err, `Context Menu: ${commandName}`, {
|
|
100
|
+
user: interaction.user.tag,
|
|
101
|
+
userId: interaction.user.id,
|
|
102
|
+
messageAuthor: interaction.targetMessage.author.tag,
|
|
103
|
+
messageContent: interaction.targetMessage.content.slice(0, 100),
|
|
104
|
+
guild: interaction.guild?.name,
|
|
105
|
+
guildId: interaction.guildId,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return await safeReply(interaction, 'An unexpected error occurred.');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Convert this command to Discord API JSON format.
|
|
113
|
+
*/
|
|
114
|
+
toJSON() {
|
|
115
|
+
return new ContextMenuCommandBuilder()
|
|
116
|
+
.setName(this.name)
|
|
117
|
+
.setType(ApplicationCommandType.Message)
|
|
118
|
+
.toJSON();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { UserContextMenuCommandInteraction, Client } from 'discord.js';
|
|
2
|
+
import type { ILogger } from '../types/logger.js';
|
|
3
|
+
import type { ErrorReporter } from '../utils/ErrorReporter.js';
|
|
4
|
+
import { PermissionLevel } from '../types/permission.js';
|
|
5
|
+
/**
|
|
6
|
+
* Base class for User Context Menu Commands.
|
|
7
|
+
*
|
|
8
|
+
* User context menu commands appear when right-clicking on a user and
|
|
9
|
+
* selecting "Apps" in the context menu.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* export class GetUserInfoCommand extends UserContextMenuCommand {
|
|
14
|
+
* name = 'Get User Info';
|
|
15
|
+
* guildOnly = false;
|
|
16
|
+
* permissionLevel = 'user' as const;
|
|
17
|
+
*
|
|
18
|
+
* protected async run(interaction: UserContextMenuCommandInteraction) {
|
|
19
|
+
* const user = interaction.targetUser;
|
|
20
|
+
* await interaction.reply(`User: ${user.tag}\nID: ${user.id}`);
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare abstract class UserContextMenuCommand {
|
|
26
|
+
abstract name: string;
|
|
27
|
+
abstract guildOnly: boolean;
|
|
28
|
+
abstract permissionLevel: PermissionLevel;
|
|
29
|
+
protected logger?: ILogger;
|
|
30
|
+
protected errorReporter?: ErrorReporter;
|
|
31
|
+
/**
|
|
32
|
+
* The main execution method - implement your command logic here.
|
|
33
|
+
*/
|
|
34
|
+
protected abstract run(interaction: UserContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Optional: Called before command execution.
|
|
37
|
+
* Return false to stop execution.
|
|
38
|
+
*/
|
|
39
|
+
protected beforeExecute?(interaction: UserContextMenuCommandInteraction, client: Client): Promise<boolean | void>;
|
|
40
|
+
/**
|
|
41
|
+
* Optional: Called after successful command execution.
|
|
42
|
+
*/
|
|
43
|
+
protected afterExecute?(interaction: UserContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Optional: Called when command execution fails.
|
|
46
|
+
*/
|
|
47
|
+
protected onError?(interaction: UserContextMenuCommandInteraction, error: Error, client: Client): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Set the logger for this command.
|
|
50
|
+
*/
|
|
51
|
+
setLogger(logger: ILogger): void;
|
|
52
|
+
/**
|
|
53
|
+
* Set the error reporter for this command.
|
|
54
|
+
*/
|
|
55
|
+
setErrorReporter(reporter: ErrorReporter): void;
|
|
56
|
+
/**
|
|
57
|
+
* Log a message using the configured logger.
|
|
58
|
+
*/
|
|
59
|
+
protected log(message: string, level: string, scope: string, logToConsole?: boolean): void;
|
|
60
|
+
/**
|
|
61
|
+
* Validate the interaction (guild-only check).
|
|
62
|
+
*/
|
|
63
|
+
protected validate(interaction: UserContextMenuCommandInteraction): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Check if the user has permission to use this command.
|
|
66
|
+
* Override this method to implement custom permission logic.
|
|
67
|
+
*/
|
|
68
|
+
protected hasPermission(interaction: UserContextMenuCommandInteraction): Promise<boolean>;
|
|
69
|
+
/**
|
|
70
|
+
* Execute the command with validation and error handling.
|
|
71
|
+
*/
|
|
72
|
+
execute(interaction: UserContextMenuCommandInteraction, client: Client): Promise<void>;
|
|
73
|
+
private safeExecute;
|
|
74
|
+
/**
|
|
75
|
+
* Convert this command to Discord API JSON format.
|
|
76
|
+
*/
|
|
77
|
+
toJSON(): import("discord.js").RESTPostAPIContextMenuApplicationCommandsJSONBody;
|
|
78
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ContextMenuCommandBuilder, ApplicationCommandType, } from 'discord.js';
|
|
2
|
+
import { safeReply } from '../utils/editAndReply.js';
|
|
3
|
+
/**
|
|
4
|
+
* Base class for User Context Menu Commands.
|
|
5
|
+
*
|
|
6
|
+
* User context menu commands appear when right-clicking on a user and
|
|
7
|
+
* selecting "Apps" in the context menu.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* export class GetUserInfoCommand extends UserContextMenuCommand {
|
|
12
|
+
* name = 'Get User Info';
|
|
13
|
+
* guildOnly = false;
|
|
14
|
+
* permissionLevel = 'user' as const;
|
|
15
|
+
*
|
|
16
|
+
* protected async run(interaction: UserContextMenuCommandInteraction) {
|
|
17
|
+
* const user = interaction.targetUser;
|
|
18
|
+
* await interaction.reply(`User: ${user.tag}\nID: ${user.id}`);
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class UserContextMenuCommand {
|
|
24
|
+
/**
|
|
25
|
+
* Set the logger for this command.
|
|
26
|
+
*/
|
|
27
|
+
setLogger(logger) {
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Set the error reporter for this command.
|
|
32
|
+
*/
|
|
33
|
+
setErrorReporter(reporter) {
|
|
34
|
+
this.errorReporter = reporter;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Log a message using the configured logger.
|
|
38
|
+
*/
|
|
39
|
+
log(message, level, scope, logToConsole = false) {
|
|
40
|
+
this.logger?.log(message, level, scope, logToConsole);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate the interaction (guild-only check).
|
|
44
|
+
*/
|
|
45
|
+
validate(interaction) {
|
|
46
|
+
if (this.guildOnly && !interaction.guildId) {
|
|
47
|
+
return 'This command can only be used in a server.';
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if the user has permission to use this command.
|
|
53
|
+
* Override this method to implement custom permission logic.
|
|
54
|
+
*/
|
|
55
|
+
async hasPermission(interaction) {
|
|
56
|
+
// Default: everyone has permission
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Execute the command with validation and error handling.
|
|
61
|
+
*/
|
|
62
|
+
async execute(interaction, client) {
|
|
63
|
+
await this.safeExecute(this.name, interaction, client, async () => {
|
|
64
|
+
const error = this.validate(interaction);
|
|
65
|
+
if (error)
|
|
66
|
+
return await safeReply(interaction, error, true);
|
|
67
|
+
if (!(await this.hasPermission(interaction))) {
|
|
68
|
+
return await safeReply(interaction, 'You do not have permission to use this command.', true);
|
|
69
|
+
}
|
|
70
|
+
if (this.beforeExecute) {
|
|
71
|
+
const shouldContinue = await this.beforeExecute(interaction, client);
|
|
72
|
+
if (shouldContinue === false)
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await this.run(interaction, client);
|
|
77
|
+
if (this.afterExecute) {
|
|
78
|
+
await this.afterExecute(interaction, client);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (this.onError) {
|
|
83
|
+
await this.onError(interaction, err, client);
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async safeExecute(commandName, interaction, client, fn) {
|
|
90
|
+
const scope = `${commandName}_EXECUTION`;
|
|
91
|
+
try {
|
|
92
|
+
await fn();
|
|
93
|
+
this.log(`${commandName} context menu command executed`, 'info', scope);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
this.log('An Error occurred: ' + err, 'error', scope, true);
|
|
97
|
+
if (this.errorReporter) {
|
|
98
|
+
await this.errorReporter.reportError(err, `Context Menu: ${commandName}`, {
|
|
99
|
+
user: interaction.user.tag,
|
|
100
|
+
userId: interaction.user.id,
|
|
101
|
+
targetUser: interaction.targetUser.tag,
|
|
102
|
+
targetUserId: interaction.targetUser.id,
|
|
103
|
+
guild: interaction.guild?.name,
|
|
104
|
+
guildId: interaction.guildId,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return await safeReply(interaction, 'An unexpected error occurred.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Convert this command to Discord API JSON format.
|
|
112
|
+
*/
|
|
113
|
+
toJSON() {
|
|
114
|
+
return new ContextMenuCommandBuilder()
|
|
115
|
+
.setName(this.name)
|
|
116
|
+
.setType(ApplicationCommandType.User)
|
|
117
|
+
.toJSON();
|
|
118
|
+
}
|
|
119
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,21 +3,25 @@ export { SubcommandGroup } from './classes/SubcommandGroup.class.js';
|
|
|
3
3
|
export { CommandManager } from './classes/CommandManager.class.js';
|
|
4
4
|
export { DiscordHandler } from './classes/DiscordHandler.class.js';
|
|
5
5
|
export { ModalManager } from './classes/ModalManager.class.js';
|
|
6
|
-
export {
|
|
6
|
+
export { UserContextMenuCommand } from './classes/UserContextMenuCommand.class.js';
|
|
7
|
+
export { MessageContextMenuCommand } from './classes/MessageContextMenuCommand.class.js';
|
|
7
8
|
export type { PermissionLevel } from './types/permission.js';
|
|
8
9
|
export type { Modal, ModalField } from './types/modal.js';
|
|
9
10
|
export type { ButtonType } from './types/button.js';
|
|
10
11
|
export type { ILogger } from './types/logger.js';
|
|
11
12
|
export type { AnySelectMenuInteraction, ButtonHandler, SelectMenuHandler, ComponentConfig } from './types/component.js';
|
|
12
13
|
export type { AutocompleteHandler } from './types/autocomplete.js';
|
|
14
|
+
export type { LoadCommandsOptions } from './types/loadCommands.js';
|
|
13
15
|
export { getPermissionsForLevel } from './utils/permissions.js';
|
|
14
16
|
export { embedBuilder, createButton, createButtonsRow, createPaginationButtons, } from './utils/embeds.js';
|
|
15
17
|
export { stringOption, integerOption, booleanOption, userOption, channelOption, roleOption, } from './utils/slashCommandOptions.js';
|
|
16
18
|
export { safeReply, safeEdit } from './utils/editAndReply.js';
|
|
17
19
|
export { formatDuration, formatDateToString, formatDateToYYYYMMDDHHMMSS, formatDateToDDMMYYYY, getDaySuffix, capitalizeFirst, } from './utils/formatting.js';
|
|
20
|
+
export { loadCommands } from './utils/loadCommands.js';
|
|
18
21
|
export { TIMES_MILISECONDS } from './utils/miliseconds.js';
|
|
19
22
|
export { TToolboxLogger } from './utils/TToolboxLogger.class.js';
|
|
20
23
|
export { ErrorReporter } from './utils/ErrorReporter.js';
|
|
21
24
|
export { ComponentManager } from './utils/ComponentManager.class.js';
|
|
22
25
|
export { AutocompleteManager } from './utils/AutocompleteManager.class.js';
|
|
26
|
+
export { PaginatedEmbed } from './utils/PaginatedEmbed.class.js';
|
|
23
27
|
export { InteractionError } from './classes/InteractionError.class.js';
|
package/dist/index.js
CHANGED
|
@@ -4,17 +4,20 @@ export { SubcommandGroup } from './classes/SubcommandGroup.class.js';
|
|
|
4
4
|
export { CommandManager } from './classes/CommandManager.class.js';
|
|
5
5
|
export { DiscordHandler } from './classes/DiscordHandler.class.js';
|
|
6
6
|
export { ModalManager } from './classes/ModalManager.class.js';
|
|
7
|
-
export {
|
|
7
|
+
export { UserContextMenuCommand } from './classes/UserContextMenuCommand.class.js';
|
|
8
|
+
export { MessageContextMenuCommand } from './classes/MessageContextMenuCommand.class.js';
|
|
8
9
|
// Utilities
|
|
9
10
|
export { getPermissionsForLevel } from './utils/permissions.js';
|
|
10
11
|
export { embedBuilder, createButton, createButtonsRow, createPaginationButtons, } from './utils/embeds.js';
|
|
11
12
|
export { stringOption, integerOption, booleanOption, userOption, channelOption, roleOption, } from './utils/slashCommandOptions.js';
|
|
12
13
|
export { safeReply, safeEdit } from './utils/editAndReply.js';
|
|
13
14
|
export { formatDuration, formatDateToString, formatDateToYYYYMMDDHHMMSS, formatDateToDDMMYYYY, getDaySuffix, capitalizeFirst, } from './utils/formatting.js';
|
|
15
|
+
export { loadCommands } from './utils/loadCommands.js';
|
|
14
16
|
export { TIMES_MILISECONDS } from './utils/miliseconds.js';
|
|
15
17
|
export { TToolboxLogger } from './utils/TToolboxLogger.class.js';
|
|
16
18
|
export { ErrorReporter } from './utils/ErrorReporter.js';
|
|
17
19
|
export { ComponentManager } from './utils/ComponentManager.class.js';
|
|
18
20
|
export { AutocompleteManager } from './utils/AutocompleteManager.class.js';
|
|
21
|
+
export { PaginatedEmbed } from './utils/PaginatedEmbed.class.js';
|
|
19
22
|
// Errors
|
|
20
23
|
export { InteractionError } from './classes/InteractionError.class.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for loading commands from a directory
|
|
3
|
+
*/
|
|
4
|
+
export interface LoadCommandsOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to recursively search subdirectories (default: true)
|
|
7
|
+
*/
|
|
8
|
+
recursive?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether to log verbose output to console (default: false)
|
|
11
|
+
*/
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Directory names to skip (default: ['subcommands', 'utils', 'helpers', 'lib'])
|
|
15
|
+
* Case-insensitive partial matching
|
|
16
|
+
*/
|
|
17
|
+
skipDirs?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* File name patterns to skip (default: ['Helper', 'Util', '.test', '.spec'])
|
|
20
|
+
* Case-sensitive partial matching
|
|
21
|
+
*/
|
|
22
|
+
skipFiles?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Custom filter function for fine-grained control (optional)
|
|
25
|
+
* Return true to include the file, false to skip
|
|
26
|
+
* Runs AFTER skipDirs and skipFiles checks
|
|
27
|
+
*/
|
|
28
|
+
filter?: (filePath: string) => boolean;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Stream from 'stream';
|
|
2
|
-
import { ActionRowBuilder, APIAttachment, Attachment, AttachmentBuilder, AttachmentPayload, BufferResolvable, ButtonInteraction, ChannelSelectMenuInteraction, ChatInputCommandInteraction, EmbedBuilder, JSONEncodable, Message, ModalSubmitInteraction, StringSelectMenuInteraction } from 'discord.js';
|
|
2
|
+
import { ActionRowBuilder, APIAttachment, Attachment, AttachmentBuilder, AttachmentPayload, BufferResolvable, ButtonInteraction, ChannelSelectMenuInteraction, ChatInputCommandInteraction, ContextMenuCommandInteraction, EmbedBuilder, JSONEncodable, Message, ModalSubmitInteraction, StringSelectMenuInteraction } from 'discord.js';
|
|
3
3
|
/**
|
|
4
4
|
* Safely replies to an interaction, handling deferred/replied states.
|
|
5
5
|
*
|
|
@@ -23,7 +23,7 @@ import { ActionRowBuilder, APIAttachment, Attachment, AttachmentBuilder, Attachm
|
|
|
23
23
|
* }
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
|
-
export declare function safeReply(interaction: ChatInputCommandInteraction | ButtonInteraction | ModalSubmitInteraction | ChannelSelectMenuInteraction | StringSelectMenuInteraction, content: string, ephemeral?: boolean, embeds?: EmbedBuilder[], components?: ActionRowBuilder<any>[], files?: (BufferResolvable | Stream | JSONEncodable<APIAttachment> | Attachment | AttachmentBuilder | AttachmentPayload)[]): Promise<Message>;
|
|
26
|
+
export declare function safeReply(interaction: ChatInputCommandInteraction | ButtonInteraction | ModalSubmitInteraction | ChannelSelectMenuInteraction | StringSelectMenuInteraction | ContextMenuCommandInteraction, content: string, ephemeral?: boolean, embeds?: EmbedBuilder[], components?: ActionRowBuilder<any>[], files?: (BufferResolvable | Stream | JSONEncodable<APIAttachment> | Attachment | AttachmentBuilder | AttachmentPayload)[]): Promise<Message>;
|
|
27
27
|
/**
|
|
28
28
|
* Safely edits an interaction reply.
|
|
29
29
|
*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LoadCommandsOptions } from '../types/loadCommands';
|
|
2
|
+
/**
|
|
3
|
+
* @experimental
|
|
4
|
+
* Automatically discover and load all commands from a directory.
|
|
5
|
+
*
|
|
6
|
+
* Returns an array of command instances that can be registered with CommandManager.
|
|
7
|
+
*
|
|
8
|
+
* @param dirPath - Absolute path to the directory containing commands
|
|
9
|
+
* @param options - Optional configuration
|
|
10
|
+
* @returns Array of command instances
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { loadCommands } from '@julanzw/ttoolbox-discordjs-framework';
|
|
15
|
+
*
|
|
16
|
+
* // Load all commands
|
|
17
|
+
* const commands = await loadCommands('./src/commands', { verbose: true });
|
|
18
|
+
*
|
|
19
|
+
* // Register them
|
|
20
|
+
* commandManager.registerCommand(commands);
|
|
21
|
+
*
|
|
22
|
+
* // Or filter/process them first
|
|
23
|
+
* const adminCommands = commands.filter(cmd => cmd.name.startsWith('admin'));
|
|
24
|
+
* commandManager.registerCommand(adminCommands);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadCommands(dirPath: string, options?: LoadCommandsOptions): Promise<any[]>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readdir, stat } from 'fs/promises';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
/**
|
|
5
|
+
* @experimental
|
|
6
|
+
* Automatically discover and load all commands from a directory.
|
|
7
|
+
*
|
|
8
|
+
* Returns an array of command instances that can be registered with CommandManager.
|
|
9
|
+
*
|
|
10
|
+
* @param dirPath - Absolute path to the directory containing commands
|
|
11
|
+
* @param options - Optional configuration
|
|
12
|
+
* @returns Array of command instances
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { loadCommands } from '@julanzw/ttoolbox-discordjs-framework';
|
|
17
|
+
*
|
|
18
|
+
* // Load all commands
|
|
19
|
+
* const commands = await loadCommands('./src/commands', { verbose: true });
|
|
20
|
+
*
|
|
21
|
+
* // Register them
|
|
22
|
+
* commandManager.registerCommand(commands);
|
|
23
|
+
*
|
|
24
|
+
* // Or filter/process them first
|
|
25
|
+
* const adminCommands = commands.filter(cmd => cmd.name.startsWith('admin'));
|
|
26
|
+
* commandManager.registerCommand(adminCommands);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export async function loadCommands(dirPath, options = {}) {
|
|
30
|
+
const { recursive = true, verbose = false, skipDirs = ['subcommands', 'utils', 'helpers', 'lib'], skipFiles = ['Helper', 'Util', '.test', '.spec'], filter, } = options;
|
|
31
|
+
const allCommands = [];
|
|
32
|
+
try {
|
|
33
|
+
const files = await getFilesRecursive(dirPath, recursive, skipDirs, skipFiles, filter);
|
|
34
|
+
if (verbose) {
|
|
35
|
+
console.log(`Found ${files.length} potential command files in ${dirPath}`);
|
|
36
|
+
}
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
try {
|
|
39
|
+
const commands = await loadCommandsFromFile(file, verbose);
|
|
40
|
+
allCommands.push(...commands);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (verbose) {
|
|
44
|
+
console.error(`⚠️ Failed to load ${file}: ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (verbose) {
|
|
49
|
+
console.log(`\n✅ Successfully loaded ${allCommands.length} commands`);
|
|
50
|
+
}
|
|
51
|
+
return allCommands;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
throw new Error(`Failed to load commands from ${dirPath}: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Recursively get all .js/.ts files from a directory with smart filtering
|
|
59
|
+
*/
|
|
60
|
+
async function getFilesRecursive(dirPath, recursive, skipDirs, skipFiles, customFilter) {
|
|
61
|
+
const files = [];
|
|
62
|
+
const entries = await readdir(dirPath);
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const fullPath = join(dirPath, entry);
|
|
65
|
+
const stats = await stat(fullPath);
|
|
66
|
+
if (stats.isDirectory()) {
|
|
67
|
+
if (skipDirs.some(skip => entry.toLowerCase().includes(skip.toLowerCase()))) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (recursive) {
|
|
71
|
+
const subFiles = await getFilesRecursive(fullPath, recursive, skipDirs, skipFiles, customFilter);
|
|
72
|
+
files.push(...subFiles);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const ext = extname(entry);
|
|
77
|
+
if (ext !== '.js' && ext !== '.ts')
|
|
78
|
+
continue;
|
|
79
|
+
if (skipFiles.some(skip => entry.includes(skip)))
|
|
80
|
+
continue;
|
|
81
|
+
if (customFilter && !customFilter(fullPath))
|
|
82
|
+
continue;
|
|
83
|
+
files.push(fullPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return files;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Load all command exports from a single file
|
|
90
|
+
*/
|
|
91
|
+
async function loadCommandsFromFile(filePath, verbose) {
|
|
92
|
+
const commands = [];
|
|
93
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
94
|
+
const module = await import(fileUrl);
|
|
95
|
+
if (module.default) {
|
|
96
|
+
commands.push(module.default);
|
|
97
|
+
if (verbose) {
|
|
98
|
+
const type = getCommandType(module.default);
|
|
99
|
+
const name = module.default.name || 'unnamed';
|
|
100
|
+
console.log(` ✓ ${type}: ${name}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
104
|
+
if (exportName === 'default')
|
|
105
|
+
continue;
|
|
106
|
+
commands.push(exportValue);
|
|
107
|
+
if (verbose) {
|
|
108
|
+
const type = getCommandType(exportValue);
|
|
109
|
+
const name = exportValue.name || exportName;
|
|
110
|
+
console.log(` ✓ ${type}: ${name}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return commands;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get a human-readable type name for a command
|
|
117
|
+
*/
|
|
118
|
+
function getCommandType(obj) {
|
|
119
|
+
const constructorName = obj?.constructor?.name;
|
|
120
|
+
if (constructorName?.includes('SubcommandGroup'))
|
|
121
|
+
return 'SubcommandGroup';
|
|
122
|
+
if (constructorName?.includes('UserContextMenu'))
|
|
123
|
+
return 'UserContextMenu';
|
|
124
|
+
if (constructorName?.includes('MessageContextMenu'))
|
|
125
|
+
return 'MessageContextMenu';
|
|
126
|
+
if (constructorName?.includes('Command'))
|
|
127
|
+
return 'Command';
|
|
128
|
+
return 'Unknown';
|
|
129
|
+
}
|
package/package.json
CHANGED