@julanzw/ttoolbox-discordjs-framework 1.0.1
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/LICENSE +675 -0
- package/README.md +77 -0
- package/dist/classes/Command.class.d.ts +169 -0
- package/dist/classes/Command.class.js +156 -0
- package/dist/classes/CommandManager.class.d.ts +69 -0
- package/dist/classes/CommandManager.class.js +149 -0
- package/dist/classes/DiscordHandler.class.d.ts +241 -0
- package/dist/classes/DiscordHandler.class.js +222 -0
- package/dist/classes/InteractionError.class.d.ts +8 -0
- package/dist/classes/InteractionError.class.js +11 -0
- package/dist/classes/ModalManager.class.d.ts +154 -0
- package/dist/classes/ModalManager.class.js +205 -0
- package/dist/classes/SubcommandGroup.class.d.ts +127 -0
- package/dist/classes/SubcommandGroup.class.js +156 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/types/button.d.ts +2 -0
- package/dist/types/button.js +1 -0
- package/dist/types/channel.d.ts +2 -0
- package/dist/types/channel.js +1 -0
- package/dist/types/logger.d.ts +37 -0
- package/dist/types/logger.js +1 -0
- package/dist/types/modal.d.ts +77 -0
- package/dist/types/modal.js +1 -0
- package/dist/types/permission.d.ts +2 -0
- package/dist/types/permission.js +1 -0
- package/dist/utils/PaginatedEmbed.class.d.ts +25 -0
- package/dist/utils/PaginatedEmbed.class.js +102 -0
- package/dist/utils/TToolboxLogger.class.d.ts +176 -0
- package/dist/utils/TToolboxLogger.class.js +252 -0
- package/dist/utils/cooldown.d.ts +13 -0
- package/dist/utils/cooldown.js +43 -0
- package/dist/utils/editAndReply.d.ts +37 -0
- package/dist/utils/editAndReply.js +85 -0
- package/dist/utils/embeds.d.ts +55 -0
- package/dist/utils/embeds.js +94 -0
- package/dist/utils/formatting.d.ts +44 -0
- package/dist/utils/formatting.js +87 -0
- package/dist/utils/miliseconds.d.ts +10 -0
- package/dist/utils/miliseconds.js +11 -0
- package/dist/utils/permissions.d.ts +8 -0
- package/dist/utils/permissions.js +24 -0
- package/dist/utils/slashCommandOptions.d.ts +8 -0
- package/dist/utils/slashCommandOptions.js +11 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# TToolbox Discord Framework
|
|
2
|
+
|
|
3
|
+
A TypeScript-first Discord.js command framework with built-in handlers, logging, and utilities.
|
|
4
|
+
|
|
5
|
+
I made this for my own bots, but feel free to use it yourself!
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Class-based commands** - Clean, extensible command structure
|
|
10
|
+
- **Permission system** - Built-in user/admin/owner permission levels
|
|
11
|
+
- **Logger** - Flexible logging with custom levels and colors
|
|
12
|
+
- **Pagination** - Easy paginated embeds with buttons
|
|
13
|
+
- **Modal helpers** - Simplified modal management
|
|
14
|
+
- **Error handling** - Built-in error handling and validation
|
|
15
|
+
- **TypeScript** - Full type safety
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @julanzw/ttoolbox-discord-framework discord.js
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Client, GatewayIntentBits } from 'discord.js';
|
|
27
|
+
import { Command, CommandManager, TToolboxLogger } from '@julanzw/ttoolbox-discord-framework';
|
|
28
|
+
|
|
29
|
+
const client = new Client({
|
|
30
|
+
intents: [GatewayIntentBits.Guilds]
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const logger = new TToolboxLogger();
|
|
34
|
+
const commandManager = new CommandManager();
|
|
35
|
+
|
|
36
|
+
// Create a command
|
|
37
|
+
class PingCommand extends Command {
|
|
38
|
+
name = 'ping';
|
|
39
|
+
description = 'Pong!';
|
|
40
|
+
guildOnly = false;
|
|
41
|
+
permissionLevel = 'user' as const;
|
|
42
|
+
|
|
43
|
+
protected async run(interaction) {
|
|
44
|
+
await interaction.reply('Pong!');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Register commands
|
|
49
|
+
commandManager
|
|
50
|
+
.setLogger(logger)
|
|
51
|
+
.register(new PingCommand());
|
|
52
|
+
|
|
53
|
+
// Handle interactions
|
|
54
|
+
client.on('interactionCreate', async (interaction) => {
|
|
55
|
+
if (interaction.isChatInputCommand()) {
|
|
56
|
+
await commandManager.executeCommand(
|
|
57
|
+
interaction.commandName,
|
|
58
|
+
interaction,
|
|
59
|
+
client
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
client.login(process.env.DISCORD_TOKEN);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Documentation
|
|
68
|
+
|
|
69
|
+
(hopefully soon)
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
AGPL-3.0 - See [LICENSE](./LICENSE) for details
|
|
74
|
+
|
|
75
|
+
## Contributing
|
|
76
|
+
|
|
77
|
+
Issues and pull requests are welcome!
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { ChatInputCommandInteraction, Client, SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js';
|
|
2
|
+
import { PermissionLevel } from '../types/permission.js';
|
|
3
|
+
import { ILogger } from '../types/logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base class for Discord slash commands.
|
|
6
|
+
*
|
|
7
|
+
* Provides a structured way to create slash commands with built-in validation,
|
|
8
|
+
* error handling, and permission management. Child classes should extend this
|
|
9
|
+
* and implement the required abstract properties and the `run` method.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* export class PingCommand extends Command {
|
|
14
|
+
* name = 'ping';
|
|
15
|
+
* description = 'Check bot latency';
|
|
16
|
+
* guildOnly = false;
|
|
17
|
+
* permissionLevel = 'user' as const;
|
|
18
|
+
*
|
|
19
|
+
* protected async run(interaction: ChatInputCommandInteraction) {
|
|
20
|
+
* await safeReply(interaction, `Pong! ${interaction.client.ws.ping}ms`);
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare abstract class Command {
|
|
26
|
+
/** The command name (used to invoke the command) */
|
|
27
|
+
abstract name: string;
|
|
28
|
+
/** A brief description of what the command does */
|
|
29
|
+
abstract description: string;
|
|
30
|
+
/** Whether this command can only be used in a guild (server) */
|
|
31
|
+
abstract guildOnly: boolean;
|
|
32
|
+
/** The minimum permission level required to use this command */
|
|
33
|
+
abstract permissionLevel: PermissionLevel;
|
|
34
|
+
/** Optional cooldown in milliseconds between command uses per user */
|
|
35
|
+
cooldown?: number;
|
|
36
|
+
/** Logger instance to use inside the command */
|
|
37
|
+
protected logger?: ILogger;
|
|
38
|
+
/**
|
|
39
|
+
* Validates whether the command can be executed in the current context.
|
|
40
|
+
*
|
|
41
|
+
* Checks if the command is being used in the correct context (guild vs DM)
|
|
42
|
+
* and calls additionalValidation for any custom validation logic.
|
|
43
|
+
*
|
|
44
|
+
* @param interaction - The command interaction to validate
|
|
45
|
+
* @returns Error message if validation fails, null if validation passes
|
|
46
|
+
* @protected
|
|
47
|
+
*/
|
|
48
|
+
protected validate(interaction: ChatInputCommandInteraction): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Hook for additional custom validation logic.
|
|
51
|
+
*
|
|
52
|
+
* Override this method in child classes to add command-specific validation.
|
|
53
|
+
* For example, checking if a user has opted in/out, premium status, etc.
|
|
54
|
+
*
|
|
55
|
+
* @param interaction - The command interaction to validate
|
|
56
|
+
* @returns Error message if validation fails, null if validation passes
|
|
57
|
+
* @protected
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* protected additionalValidation(interaction: ChatInputCommandInteraction): string | null {
|
|
62
|
+
* if (hasOptedOut(interaction.user.id)) {
|
|
63
|
+
* return 'You have opted out of this feature.';
|
|
64
|
+
* }
|
|
65
|
+
* return null;
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
protected additionalValidation(interaction: ChatInputCommandInteraction): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Safely executes a function with error handling and logging.
|
|
72
|
+
*
|
|
73
|
+
* Wraps the execution in a try-catch block, logs successful executions,
|
|
74
|
+
* and automatically handles errors by logging them and sending a user-friendly
|
|
75
|
+
* error message.
|
|
76
|
+
*
|
|
77
|
+
* @param commandName - The name of the command being executed
|
|
78
|
+
* @param interaction - The command interaction
|
|
79
|
+
* @param fn - The function to execute
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
private safeExecute;
|
|
83
|
+
/**
|
|
84
|
+
* Executes the command with validation and error handling.
|
|
85
|
+
*
|
|
86
|
+
* This is the main entry point when a command is invoked. It performs
|
|
87
|
+
* validation, then calls the `run` method if validation passes.
|
|
88
|
+
* Should not be overridden - override `run` instead.
|
|
89
|
+
*
|
|
90
|
+
* @param interaction - The command interaction
|
|
91
|
+
* @param client - The Discord client instance
|
|
92
|
+
*/
|
|
93
|
+
execute(interaction: ChatInputCommandInteraction, client: Client): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* The main command logic - implement this in child classes.
|
|
96
|
+
*
|
|
97
|
+
* This method is called after all validation passes. Put your command's
|
|
98
|
+
* actual functionality here.
|
|
99
|
+
*
|
|
100
|
+
* @param interaction - The command interaction
|
|
101
|
+
* @param client - The Discord client instance
|
|
102
|
+
* @protected
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* protected async run(interaction: ChatInputCommandInteraction) {
|
|
107
|
+
* const user = interaction.options.getUser('user', true);
|
|
108
|
+
* await safeReply(interaction, `Hello, ${user.tag}!`);
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
protected abstract run(interaction: ChatInputCommandInteraction, client: Client): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Customize the slash command builder with options, choices, etc.
|
|
115
|
+
*
|
|
116
|
+
* Override this method to add options (string, integer, user, etc.) to your command.
|
|
117
|
+
* Works with both standalone commands and subcommands.
|
|
118
|
+
*
|
|
119
|
+
* @param builder - The slash command builder to customize
|
|
120
|
+
* @returns The customized builder
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* customize(builder: SlashCommandBuilder) {
|
|
125
|
+
* return builder
|
|
126
|
+
* .addStringOption(option =>
|
|
127
|
+
* option
|
|
128
|
+
* .setName('message')
|
|
129
|
+
* .setDescription('The message to send')
|
|
130
|
+
* .setRequired(true)
|
|
131
|
+
* )
|
|
132
|
+
* .addUserOption(option =>
|
|
133
|
+
* option
|
|
134
|
+
* .setName('user')
|
|
135
|
+
* .setDescription('The user to mention')
|
|
136
|
+
* .setRequired(false)
|
|
137
|
+
* );
|
|
138
|
+
* }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
customize?(builder: SlashCommandBuilder | SlashCommandSubcommandBuilder): SlashCommandBuilder | SlashCommandSubcommandBuilder;
|
|
142
|
+
/**
|
|
143
|
+
* Converts the command to Discord API JSON format for registration.
|
|
144
|
+
*
|
|
145
|
+
* This method builds the SlashCommandBuilder with the command's metadata
|
|
146
|
+
* (name, description, permissions) and any custom options, then returns
|
|
147
|
+
* the JSON representation needed for Discord's API.
|
|
148
|
+
*
|
|
149
|
+
* Called automatically by CommandManager when registering commands.
|
|
150
|
+
*
|
|
151
|
+
* @returns The command in Discord API JSON format
|
|
152
|
+
*/
|
|
153
|
+
toJSON(): import("discord.js").RESTPostAPIChatInputApplicationCommandsJSONBody;
|
|
154
|
+
/**
|
|
155
|
+
* Sets the logger for this command.
|
|
156
|
+
*
|
|
157
|
+
* @param logger - Logger instance implementing ILogger interface
|
|
158
|
+
*/
|
|
159
|
+
setLogger(logger: ILogger): void;
|
|
160
|
+
/**
|
|
161
|
+
* Log a message using the configured logger.
|
|
162
|
+
*
|
|
163
|
+
* @param message - The message to log
|
|
164
|
+
* @param level - The log level
|
|
165
|
+
* @param scope - The scope/context
|
|
166
|
+
* @param logToConsole - Whether to also log to console
|
|
167
|
+
*/
|
|
168
|
+
protected log(message: string, level: string, scope: string, logToConsole?: boolean): void;
|
|
169
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { SlashCommandBuilder, } from 'discord.js';
|
|
3
|
+
import { safeReply } from '../utils/editAndReply.js';
|
|
4
|
+
import { getPermissionsForLevel } from '../utils/permissions.js';
|
|
5
|
+
import { checkCooldown } from '../utils/cooldown.js';
|
|
6
|
+
import { formatDuration } from '../utils/formatting.js';
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for Discord slash commands.
|
|
9
|
+
*
|
|
10
|
+
* Provides a structured way to create slash commands with built-in validation,
|
|
11
|
+
* error handling, and permission management. Child classes should extend this
|
|
12
|
+
* and implement the required abstract properties and the `run` method.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* export class PingCommand extends Command {
|
|
17
|
+
* name = 'ping';
|
|
18
|
+
* description = 'Check bot latency';
|
|
19
|
+
* guildOnly = false;
|
|
20
|
+
* permissionLevel = 'user' as const;
|
|
21
|
+
*
|
|
22
|
+
* protected async run(interaction: ChatInputCommandInteraction) {
|
|
23
|
+
* await safeReply(interaction, `Pong! ${interaction.client.ws.ping}ms`);
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class Command {
|
|
29
|
+
/**
|
|
30
|
+
* Validates whether the command can be executed in the current context.
|
|
31
|
+
*
|
|
32
|
+
* Checks if the command is being used in the correct context (guild vs DM)
|
|
33
|
+
* and calls additionalValidation for any custom validation logic.
|
|
34
|
+
*
|
|
35
|
+
* @param interaction - The command interaction to validate
|
|
36
|
+
* @returns Error message if validation fails, null if validation passes
|
|
37
|
+
* @protected
|
|
38
|
+
*/
|
|
39
|
+
validate(interaction) {
|
|
40
|
+
if (this.guildOnly && !interaction.guildId) {
|
|
41
|
+
return 'This command can only be used in a server.';
|
|
42
|
+
}
|
|
43
|
+
const secondsRemaining = checkCooldown(this.name, this.cooldown, interaction.user.id);
|
|
44
|
+
if (secondsRemaining > 0) {
|
|
45
|
+
return `You need to wait ${formatDuration(secondsRemaining)} before using this command again.`;
|
|
46
|
+
}
|
|
47
|
+
return this.additionalValidation(interaction);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Hook for additional custom validation logic.
|
|
51
|
+
*
|
|
52
|
+
* Override this method in child classes to add command-specific validation.
|
|
53
|
+
* For example, checking if a user has opted in/out, premium status, etc.
|
|
54
|
+
*
|
|
55
|
+
* @param interaction - The command interaction to validate
|
|
56
|
+
* @returns Error message if validation fails, null if validation passes
|
|
57
|
+
* @protected
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* protected additionalValidation(interaction: ChatInputCommandInteraction): string | null {
|
|
62
|
+
* if (hasOptedOut(interaction.user.id)) {
|
|
63
|
+
* return 'You have opted out of this feature.';
|
|
64
|
+
* }
|
|
65
|
+
* return null;
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
additionalValidation(interaction) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Safely executes a function with error handling and logging.
|
|
74
|
+
*
|
|
75
|
+
* Wraps the execution in a try-catch block, logs successful executions,
|
|
76
|
+
* and automatically handles errors by logging them and sending a user-friendly
|
|
77
|
+
* error message.
|
|
78
|
+
*
|
|
79
|
+
* @param commandName - The name of the command being executed
|
|
80
|
+
* @param interaction - The command interaction
|
|
81
|
+
* @param fn - The function to execute
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
async safeExecute(commandName, interaction, fn) {
|
|
85
|
+
const scope = `${commandName}_EXECUTION`;
|
|
86
|
+
try {
|
|
87
|
+
await fn();
|
|
88
|
+
const subcommandName = interaction.options.getSubcommand(false);
|
|
89
|
+
this.logger?.log(`${commandName} ${subcommandName ? `(${subcommandName}) ` : ``}command executed`, 'info', scope);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
this.logger?.log(
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
94
|
+
`An Error occurred: ${err.message ?? err}`, 'error', scope, true);
|
|
95
|
+
return await safeReply(interaction, 'An unexpected error occurred.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Executes the command with validation and error handling.
|
|
100
|
+
*
|
|
101
|
+
* This is the main entry point when a command is invoked. It performs
|
|
102
|
+
* validation, then calls the `run` method if validation passes.
|
|
103
|
+
* Should not be overridden - override `run` instead.
|
|
104
|
+
*
|
|
105
|
+
* @param interaction - The command interaction
|
|
106
|
+
* @param client - The Discord client instance
|
|
107
|
+
*/
|
|
108
|
+
async execute(interaction, client) {
|
|
109
|
+
await this.safeExecute(this.name, interaction, async () => {
|
|
110
|
+
const error = this.validate(interaction);
|
|
111
|
+
if (error)
|
|
112
|
+
return await safeReply(interaction, error, true);
|
|
113
|
+
await this.run(interaction, client);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Converts the command to Discord API JSON format for registration.
|
|
118
|
+
*
|
|
119
|
+
* This method builds the SlashCommandBuilder with the command's metadata
|
|
120
|
+
* (name, description, permissions) and any custom options, then returns
|
|
121
|
+
* the JSON representation needed for Discord's API.
|
|
122
|
+
*
|
|
123
|
+
* Called automatically by CommandManager when registering commands.
|
|
124
|
+
*
|
|
125
|
+
* @returns The command in Discord API JSON format
|
|
126
|
+
*/
|
|
127
|
+
toJSON() {
|
|
128
|
+
const builder = new SlashCommandBuilder()
|
|
129
|
+
.setName(this.name)
|
|
130
|
+
.setDescription(this.description)
|
|
131
|
+
.setDefaultMemberPermissions(getPermissionsForLevel(this.permissionLevel));
|
|
132
|
+
if (this.customize) {
|
|
133
|
+
this.customize(builder);
|
|
134
|
+
}
|
|
135
|
+
return builder.toJSON();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Sets the logger for this command.
|
|
139
|
+
*
|
|
140
|
+
* @param logger - Logger instance implementing ILogger interface
|
|
141
|
+
*/
|
|
142
|
+
setLogger(logger) {
|
|
143
|
+
this.logger = logger;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Log a message using the configured logger.
|
|
147
|
+
*
|
|
148
|
+
* @param message - The message to log
|
|
149
|
+
* @param level - The log level
|
|
150
|
+
* @param scope - The scope/context
|
|
151
|
+
* @param logToConsole - Whether to also log to console
|
|
152
|
+
*/
|
|
153
|
+
log(message, level, scope, logToConsole = false) {
|
|
154
|
+
this.logger?.log(message, level, scope, logToConsole);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ChatInputCommandInteraction, Client, RESTPostAPIChatInputApplicationCommandsJSONBody } from 'discord.js';
|
|
2
|
+
import { ILogger } from '../types/logger.js';
|
|
3
|
+
import { Command } from './Command.class.js';
|
|
4
|
+
import { SubcommandGroup } from './SubcommandGroup.class.js';
|
|
5
|
+
export declare class CommandManager {
|
|
6
|
+
private commands;
|
|
7
|
+
protected logger?: ILogger;
|
|
8
|
+
/**
|
|
9
|
+
* Register a single command or subcommand group
|
|
10
|
+
*/
|
|
11
|
+
register(command: Command | SubcommandGroup): this;
|
|
12
|
+
/**
|
|
13
|
+
* Register multiple commands at once
|
|
14
|
+
*/
|
|
15
|
+
registerMultiple(commands: Array<Command | SubcommandGroup>): this;
|
|
16
|
+
/**
|
|
17
|
+
* Get a specific command by name
|
|
18
|
+
*/
|
|
19
|
+
get(name: string): Command | SubcommandGroup | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Get all registered commands
|
|
22
|
+
*/
|
|
23
|
+
getAll(): Array<Command | SubcommandGroup>;
|
|
24
|
+
/**
|
|
25
|
+
* Get all commands sorted alphabetically by name
|
|
26
|
+
*/
|
|
27
|
+
getAllSorted(): Array<Command | SubcommandGroup>;
|
|
28
|
+
/**
|
|
29
|
+
* Convert all commands to Discord JSON format for registration
|
|
30
|
+
*/
|
|
31
|
+
toDiscordJSON(): RESTPostAPIChatInputApplicationCommandsJSONBody[];
|
|
32
|
+
/**
|
|
33
|
+
* Generate paginated help pages for display in help command
|
|
34
|
+
* Returns a 2D array where each inner array is a page of command descriptions
|
|
35
|
+
*
|
|
36
|
+
* @param commandsPerPage - How many regular commands to show per page (default: 10)
|
|
37
|
+
* @returns 2D array of command info for pagination
|
|
38
|
+
*/
|
|
39
|
+
getHelpPages(commandsPerPage?: number): Array<Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
value: string;
|
|
42
|
+
}>>;
|
|
43
|
+
/**
|
|
44
|
+
* Execute a command by name
|
|
45
|
+
* This is called from your interaction handler
|
|
46
|
+
*/
|
|
47
|
+
executeCommand(commandName: string, interaction: ChatInputCommandInteraction, client: Client): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Get total number of registered commands
|
|
50
|
+
*/
|
|
51
|
+
get size(): number;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a command exists
|
|
54
|
+
*/
|
|
55
|
+
has(name: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Remove a command (useful for hot-reloading in dev)
|
|
58
|
+
*/
|
|
59
|
+
unregister(name: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Clear all commands
|
|
62
|
+
*/
|
|
63
|
+
clear(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Get command names as an array
|
|
66
|
+
*/
|
|
67
|
+
getCommandNames(): string[];
|
|
68
|
+
setLogger(logger: ILogger): this;
|
|
69
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { SubcommandGroup } from './SubcommandGroup.class.js';
|
|
2
|
+
export class CommandManager {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.commands = new Map();
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Register a single command or subcommand group
|
|
8
|
+
*/
|
|
9
|
+
register(command) {
|
|
10
|
+
if (this.logger) {
|
|
11
|
+
command.setLogger(this.logger);
|
|
12
|
+
}
|
|
13
|
+
this.commands.set(command.name, command);
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register multiple commands at once
|
|
18
|
+
*/
|
|
19
|
+
registerMultiple(commands) {
|
|
20
|
+
commands.forEach((cmd) => this.register(cmd));
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get a specific command by name
|
|
25
|
+
*/
|
|
26
|
+
get(name) {
|
|
27
|
+
return this.commands.get(name);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get all registered commands
|
|
31
|
+
*/
|
|
32
|
+
getAll() {
|
|
33
|
+
return Array.from(this.commands.values());
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get all commands sorted alphabetically by name
|
|
37
|
+
*/
|
|
38
|
+
getAllSorted() {
|
|
39
|
+
return this.getAll().sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convert all commands to Discord JSON format for registration
|
|
43
|
+
*/
|
|
44
|
+
toDiscordJSON() {
|
|
45
|
+
return this.getAll().map((cmd) => {
|
|
46
|
+
if (process.env.ENV === 'dev') {
|
|
47
|
+
console.log(`Registering: ${cmd.name}`);
|
|
48
|
+
}
|
|
49
|
+
return cmd.toJSON();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate paginated help pages for display in help command
|
|
54
|
+
* Returns a 2D array where each inner array is a page of command descriptions
|
|
55
|
+
*
|
|
56
|
+
* @param commandsPerPage - How many regular commands to show per page (default: 10)
|
|
57
|
+
* @returns 2D array of command info for pagination
|
|
58
|
+
*/
|
|
59
|
+
getHelpPages(commandsPerPage = 5) {
|
|
60
|
+
const subcommandPages = [];
|
|
61
|
+
const otherCommands = [];
|
|
62
|
+
for (const command of this.getAllSorted()) {
|
|
63
|
+
if (command instanceof SubcommandGroup) {
|
|
64
|
+
// Each subcommand group gets its own page
|
|
65
|
+
const page = [
|
|
66
|
+
{
|
|
67
|
+
name: `─── ${command.name.toUpperCase()} ───`,
|
|
68
|
+
value: command.description || 'No description.',
|
|
69
|
+
},
|
|
70
|
+
...command.getSubcommandList().map((sub) => ({
|
|
71
|
+
name: `› ${sub.name}`,
|
|
72
|
+
value: sub.description,
|
|
73
|
+
})),
|
|
74
|
+
];
|
|
75
|
+
subcommandPages.push(page);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Regular commands are grouped together
|
|
79
|
+
// Add a header when starting a new page
|
|
80
|
+
if (otherCommands.length % commandsPerPage === 0) {
|
|
81
|
+
otherCommands.push({
|
|
82
|
+
name: `─── OTHER ───`,
|
|
83
|
+
value: 'Other commands',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
otherCommands.push({
|
|
87
|
+
name: `› ${command.name}`,
|
|
88
|
+
value: command.description,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Combine subcommand pages first, then regular command pages
|
|
93
|
+
const allPages = [...subcommandPages];
|
|
94
|
+
// Split regular commands into pages
|
|
95
|
+
while (otherCommands.length) {
|
|
96
|
+
allPages.push(otherCommands.splice(0, commandsPerPage));
|
|
97
|
+
}
|
|
98
|
+
return allPages;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Execute a command by name
|
|
102
|
+
* This is called from your interaction handler
|
|
103
|
+
*/
|
|
104
|
+
async executeCommand(commandName, interaction, client) {
|
|
105
|
+
const command = this.get(commandName);
|
|
106
|
+
if (!command) {
|
|
107
|
+
throw new Error(`Command not found: ${commandName}`);
|
|
108
|
+
}
|
|
109
|
+
await command.execute(interaction, client);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get total number of registered commands
|
|
113
|
+
*/
|
|
114
|
+
get size() {
|
|
115
|
+
return this.commands.size;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check if a command exists
|
|
119
|
+
*/
|
|
120
|
+
has(name) {
|
|
121
|
+
return this.commands.has(name);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Remove a command (useful for hot-reloading in dev)
|
|
125
|
+
*/
|
|
126
|
+
unregister(name) {
|
|
127
|
+
return this.commands.delete(name);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Clear all commands
|
|
131
|
+
*/
|
|
132
|
+
clear() {
|
|
133
|
+
this.commands.clear();
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get command names as an array
|
|
137
|
+
*/
|
|
138
|
+
getCommandNames() {
|
|
139
|
+
return Array.from(this.commands.keys());
|
|
140
|
+
}
|
|
141
|
+
setLogger(logger) {
|
|
142
|
+
this.logger = logger;
|
|
143
|
+
// Inject logger into all already-registered commands
|
|
144
|
+
for (const command of this.commands.values()) {
|
|
145
|
+
command.setLogger(logger);
|
|
146
|
+
}
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
}
|