@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// core/utils/ModalManager.js
|
|
2
|
+
import { ActionRowBuilder, TextInputBuilder, ModalBuilder, } from 'discord.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manages modal creation, registration, and submission handling.
|
|
5
|
+
*
|
|
6
|
+
* Provides a centralized way to create and track modals throughout the bot.
|
|
7
|
+
* Modals can be registered with their submission handlers and automatically
|
|
8
|
+
* cleaned up after use if marked as ephemeral.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const modalManager = new ModalManager();
|
|
13
|
+
*
|
|
14
|
+
* // Create and register a modal
|
|
15
|
+
* const modal = modalManager.buildAndRegister({
|
|
16
|
+
* id: 'feedback-modal',
|
|
17
|
+
* title: 'Submit Feedback',
|
|
18
|
+
* ephemeral: true,
|
|
19
|
+
* fields: [
|
|
20
|
+
* {
|
|
21
|
+
* customId: 'message',
|
|
22
|
+
* name: 'Your Feedback',
|
|
23
|
+
* style: TextInputStyle.Paragraph,
|
|
24
|
+
* required: true,
|
|
25
|
+
* }
|
|
26
|
+
* ],
|
|
27
|
+
* onSubmit: async (interaction) => {
|
|
28
|
+
* const feedback = interaction.fields.getTextInputValue('message');
|
|
29
|
+
* await interaction.reply('Thank you for your feedback!');
|
|
30
|
+
* }
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* await interaction.showModal(modal);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class ModalManager {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.modals = new Map();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds a Discord ModalBuilder and registers the modal for submission handling.
|
|
42
|
+
*
|
|
43
|
+
* Creates a modal with the specified fields and registers it so that when
|
|
44
|
+
* a user submits it, the onSubmit handler will be called.
|
|
45
|
+
*
|
|
46
|
+
* @param data - The modal configuration including fields and submission handler
|
|
47
|
+
* @returns A ModalBuilder ready to be shown to the user
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const modal = modalManager.buildAndRegister({
|
|
52
|
+
* id: 'edit-reminder',
|
|
53
|
+
* title: 'Edit Reminder',
|
|
54
|
+
* ephemeral: true,
|
|
55
|
+
* fields: [
|
|
56
|
+
* {
|
|
57
|
+
* customId: 'message',
|
|
58
|
+
* name: 'Reminder Message',
|
|
59
|
+
* style: TextInputStyle.Short,
|
|
60
|
+
* required: true,
|
|
61
|
+
* value: existingMessage,
|
|
62
|
+
* }
|
|
63
|
+
* ],
|
|
64
|
+
* onSubmit: async (interaction) => {
|
|
65
|
+
* const newMessage = interaction.fields.getTextInputValue('message');
|
|
66
|
+
* await updateReminder(id, newMessage);
|
|
67
|
+
* }
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* await buttonInteraction.showModal(modal);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
buildAndRegister(data) {
|
|
74
|
+
const modal = new ModalBuilder().setCustomId(data.id).setTitle(data.title);
|
|
75
|
+
for (const field of data.fields) {
|
|
76
|
+
const input = new TextInputBuilder()
|
|
77
|
+
.setCustomId(field.customId)
|
|
78
|
+
.setLabel(field.name)
|
|
79
|
+
.setStyle(field.style)
|
|
80
|
+
.setRequired(field.required ?? true);
|
|
81
|
+
if (field.placeholder)
|
|
82
|
+
input.setPlaceholder(field.placeholder);
|
|
83
|
+
if (field.minLength)
|
|
84
|
+
input.setMinLength(field.minLength);
|
|
85
|
+
if (field.maxLength)
|
|
86
|
+
input.setMaxLength(field.maxLength);
|
|
87
|
+
if (field.value)
|
|
88
|
+
input.setValue(field.value);
|
|
89
|
+
const row = new ActionRowBuilder().addComponents(input);
|
|
90
|
+
modal.addComponents(row);
|
|
91
|
+
}
|
|
92
|
+
this.modals.set(data.id, data);
|
|
93
|
+
return modal;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Retrieves a registered modal by its ID.
|
|
97
|
+
*
|
|
98
|
+
* Supports dynamic IDs - if the exact ID isn't found, attempts to match
|
|
99
|
+
* using the base ID (before the first colon). This allows for modals with
|
|
100
|
+
* dynamic suffixes like "edit-reminder:123".
|
|
101
|
+
*
|
|
102
|
+
* @param id - The modal ID to look up
|
|
103
|
+
* @returns The modal configuration, or undefined if not found
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* // Register with base ID
|
|
108
|
+
* modalManager.buildAndRegister({ id: 'edit-reminder', ... });
|
|
109
|
+
*
|
|
110
|
+
* // Can retrieve with dynamic ID
|
|
111
|
+
* const modal = modalManager.get('edit-reminder:123'); // Works!
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
get(id) {
|
|
115
|
+
// Try exact match first
|
|
116
|
+
const modal = this.modals.get(id);
|
|
117
|
+
if (modal)
|
|
118
|
+
return modal;
|
|
119
|
+
// Try base ID (before colon) for dynamic IDs
|
|
120
|
+
const baseId = id.split(':')[0];
|
|
121
|
+
return this.modals.get(baseId);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Removes a modal from the registry.
|
|
125
|
+
*
|
|
126
|
+
* Useful for cleaning up ephemeral modals after they've been submitted,
|
|
127
|
+
* or for unregistering modals that are no longer needed.
|
|
128
|
+
*
|
|
129
|
+
* @param id - The modal ID to remove
|
|
130
|
+
* @returns true if the modal was removed, false if it didn't exist
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* // Clean up after submission
|
|
135
|
+
* await modalManager.handleSubmit(interaction);
|
|
136
|
+
* modalManager.remove(interaction.customId); // If ephemeral
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
remove(id) {
|
|
140
|
+
return this.modals.delete(id);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Handles a modal submission by calling the registered onSubmit handler.
|
|
144
|
+
*
|
|
145
|
+
* Looks up the modal by ID, calls its onSubmit handler, and automatically
|
|
146
|
+
* removes ephemeral modals from the registry after submission.
|
|
147
|
+
*
|
|
148
|
+
* @param interaction - The modal submit interaction
|
|
149
|
+
* @throws {Error} If no modal is found for the interaction's custom ID
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* // In your interaction handler
|
|
154
|
+
* client.on('interactionCreate', async (interaction) => {
|
|
155
|
+
* if (interaction.isModalSubmit()) {
|
|
156
|
+
* await modalManager.handleSubmit(interaction);
|
|
157
|
+
* }
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
async handleSubmit(interaction) {
|
|
162
|
+
const modal = this.get(interaction.customId);
|
|
163
|
+
if (!modal) {
|
|
164
|
+
throw new Error(`Modal not found: ${interaction.customId}`);
|
|
165
|
+
}
|
|
166
|
+
await modal.onSubmit(interaction);
|
|
167
|
+
// Clean up ephemeral modals
|
|
168
|
+
if (modal.ephemeral) {
|
|
169
|
+
this.remove(interaction.customId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Checks if a modal with the given ID is registered.
|
|
174
|
+
*
|
|
175
|
+
* @param id - The modal ID to check
|
|
176
|
+
* @returns true if the modal exists in the registry
|
|
177
|
+
*/
|
|
178
|
+
has(id) {
|
|
179
|
+
return this.modals.has(id) || this.modals.has(id.split(':')[0]);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Clears all registered modals from the registry.
|
|
183
|
+
*
|
|
184
|
+
* Useful for cleanup during bot shutdown or for testing.
|
|
185
|
+
*/
|
|
186
|
+
clear() {
|
|
187
|
+
this.modals.clear();
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Gets the total number of registered modals.
|
|
191
|
+
*
|
|
192
|
+
* @returns The count of modals in the registry
|
|
193
|
+
*/
|
|
194
|
+
get size() {
|
|
195
|
+
return this.modals.size;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Gets all registered modal IDs.
|
|
199
|
+
*
|
|
200
|
+
* @returns Array of modal IDs currently in the registry
|
|
201
|
+
*/
|
|
202
|
+
getModalIds() {
|
|
203
|
+
return Array.from(this.modals.keys());
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ChatInputCommandInteraction, Client, RESTPostAPIChatInputApplicationCommandsJSONBody } from 'discord.js';
|
|
2
|
+
import { ILogger } from '../types/logger.js';
|
|
3
|
+
import { Command } from './Command.class.js';
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base class for Discord slash command groups with subcommands.
|
|
6
|
+
*
|
|
7
|
+
* Use this when you have multiple related commands that should be grouped together
|
|
8
|
+
* under a single parent command.
|
|
9
|
+
*
|
|
10
|
+
* For example, `/birthday set` and `/birthday calendar`
|
|
11
|
+
* would both be subcommands of a `BirthdayCommands` subcommand group.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* export class BirthdayCommands extends SubcommandGroup {
|
|
16
|
+
* name = 'birthday';
|
|
17
|
+
* description = 'All commands related to birthdays';
|
|
18
|
+
*
|
|
19
|
+
* protected subcommands = new Map<string, Command>([
|
|
20
|
+
* ['set', new SetBirthdayCommand()],
|
|
21
|
+
* ['calendar', new CalendarCommand()],
|
|
22
|
+
* ]);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class SubcommandGroup {
|
|
27
|
+
/** The parent command name (e.g., 'birthday' for `/birthday set`) */
|
|
28
|
+
abstract name: string;
|
|
29
|
+
/** A brief description of the command group */
|
|
30
|
+
abstract description: string;
|
|
31
|
+
/** Map of subcommand names to Command instances */
|
|
32
|
+
protected abstract subcommands: Map<string, Command>;
|
|
33
|
+
/** The logger instance used in the subcommand group */
|
|
34
|
+
protected logger?: ILogger;
|
|
35
|
+
/**
|
|
36
|
+
* Safely executes a function with error handling and logging.
|
|
37
|
+
*
|
|
38
|
+
* Wraps the execution in a try-catch block, logs successful executions,
|
|
39
|
+
* and automatically handles errors by logging them and sending a user-friendly
|
|
40
|
+
* error message.
|
|
41
|
+
*
|
|
42
|
+
* @param commandName - The name of the parent command
|
|
43
|
+
* @param scope - The logging scope for this execution
|
|
44
|
+
* @param interaction - The command interaction
|
|
45
|
+
* @param fn - The function to execute
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
private safeExecute;
|
|
49
|
+
/**
|
|
50
|
+
* Executes the appropriate subcommand based on the user's interaction.
|
|
51
|
+
*
|
|
52
|
+
* This method:
|
|
53
|
+
* 1. Determines which subcommand was invoked
|
|
54
|
+
* 2. Looks up the corresponding Command instance
|
|
55
|
+
* 3. Executes the subcommand with error handling
|
|
56
|
+
*
|
|
57
|
+
* Called automatically by the CommandManager when this command group is invoked.
|
|
58
|
+
*
|
|
59
|
+
* @param interaction - The command interaction
|
|
60
|
+
* @param client - The Discord client instance
|
|
61
|
+
* @throws {Error} If the subcommand name doesn't exist in the subcommands map
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* When a user runs `/birthday set`, this method:
|
|
65
|
+
* - Gets "set" from interaction.options.getSubcommand()
|
|
66
|
+
* - Looks up the SetBirthdayCommand in the subcommands map
|
|
67
|
+
* - Calls SetBirthdayCommand.execute()
|
|
68
|
+
*/
|
|
69
|
+
execute(interaction: ChatInputCommandInteraction, client: Client): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Converts the command group to Discord API JSON format for registration.
|
|
72
|
+
*
|
|
73
|
+
* This method:
|
|
74
|
+
* 1. Creates a SlashCommandBuilder with the group's name and description
|
|
75
|
+
* 2. Adds each subcommand from the subcommands map
|
|
76
|
+
* 3. Applies any custom options from each subcommand's `customize` method
|
|
77
|
+
* 4. Returns the JSON representation needed for Discord's API
|
|
78
|
+
*
|
|
79
|
+
* Called automatically by CommandManager when registering commands.
|
|
80
|
+
*
|
|
81
|
+
* @returns The command group in Discord API JSON format
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* For a birthday command group with "set" and "calendar" subcommands,
|
|
85
|
+
* this creates the structure for:
|
|
86
|
+
* - `/birthday set <options>`
|
|
87
|
+
* - `/birthday calendar`
|
|
88
|
+
*/
|
|
89
|
+
toJSON(): RESTPostAPIChatInputApplicationCommandsJSONBody;
|
|
90
|
+
/**
|
|
91
|
+
* Gets a list of all subcommands with their names and descriptions.
|
|
92
|
+
*
|
|
93
|
+
* Useful for generating help text or documentation about available subcommands.
|
|
94
|
+
* Used by CommandManager's `getHelpPages` method to display subcommands in the help command.
|
|
95
|
+
*
|
|
96
|
+
* @returns Array of objects containing subcommand names and descriptions
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const subcommands = birthdayCommands.getSubcommandList();
|
|
101
|
+
* // Returns:
|
|
102
|
+
* // [
|
|
103
|
+
* // { name: 'set', description: 'Set your birthday' },
|
|
104
|
+
* // { name: 'calendar', description: 'View birthday calendar' }
|
|
105
|
+
* // ]
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
getSubcommandList(): Array<{
|
|
109
|
+
name: string;
|
|
110
|
+
description: string;
|
|
111
|
+
}>;
|
|
112
|
+
/**
|
|
113
|
+
* Sets the logger for this subcommand group.
|
|
114
|
+
*
|
|
115
|
+
* @param logger - Logger instance implementing ILogger interface
|
|
116
|
+
*/
|
|
117
|
+
setLogger(logger: ILogger): void;
|
|
118
|
+
/**
|
|
119
|
+
* Log a message using the configured logger.
|
|
120
|
+
*
|
|
121
|
+
* @param message - The message to log
|
|
122
|
+
* @param level - The log level
|
|
123
|
+
* @param scope - The scope/context
|
|
124
|
+
* @param logToConsole - Whether to also log to console
|
|
125
|
+
*/
|
|
126
|
+
protected log(message: string, level: string, scope: string, logToConsole?: boolean): void;
|
|
127
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { SlashCommandBuilder, } from 'discord.js';
|
|
2
|
+
import { safeReply } from '../utils/editAndReply.js';
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for Discord slash command groups with subcommands.
|
|
5
|
+
*
|
|
6
|
+
* Use this when you have multiple related commands that should be grouped together
|
|
7
|
+
* under a single parent command.
|
|
8
|
+
*
|
|
9
|
+
* For example, `/birthday set` and `/birthday calendar`
|
|
10
|
+
* would both be subcommands of a `BirthdayCommands` subcommand group.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* export class BirthdayCommands extends SubcommandGroup {
|
|
15
|
+
* name = 'birthday';
|
|
16
|
+
* description = 'All commands related to birthdays';
|
|
17
|
+
*
|
|
18
|
+
* protected subcommands = new Map<string, Command>([
|
|
19
|
+
* ['set', new SetBirthdayCommand()],
|
|
20
|
+
* ['calendar', new CalendarCommand()],
|
|
21
|
+
* ]);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class SubcommandGroup {
|
|
26
|
+
/**
|
|
27
|
+
* Safely executes a function with error handling and logging.
|
|
28
|
+
*
|
|
29
|
+
* Wraps the execution in a try-catch block, logs successful executions,
|
|
30
|
+
* and automatically handles errors by logging them and sending a user-friendly
|
|
31
|
+
* error message.
|
|
32
|
+
*
|
|
33
|
+
* @param commandName - The name of the parent command
|
|
34
|
+
* @param scope - The logging scope for this execution
|
|
35
|
+
* @param interaction - The command interaction
|
|
36
|
+
* @param fn - The function to execute
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
async safeExecute(commandName, scope, interaction, fn) {
|
|
40
|
+
try {
|
|
41
|
+
await fn();
|
|
42
|
+
const subcommandName = interaction.options.getSubcommand(false);
|
|
43
|
+
this.logger?.log(`${commandName} ${subcommandName ? `(${subcommandName}) ` : ``}command executed`, 'info', scope);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
this.logger?.log('An Error occured' + err, 'error', scope, true);
|
|
47
|
+
return await safeReply(interaction, 'An unexpected error occurred.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Executes the appropriate subcommand based on the user's interaction.
|
|
52
|
+
*
|
|
53
|
+
* This method:
|
|
54
|
+
* 1. Determines which subcommand was invoked
|
|
55
|
+
* 2. Looks up the corresponding Command instance
|
|
56
|
+
* 3. Executes the subcommand with error handling
|
|
57
|
+
*
|
|
58
|
+
* Called automatically by the CommandManager when this command group is invoked.
|
|
59
|
+
*
|
|
60
|
+
* @param interaction - The command interaction
|
|
61
|
+
* @param client - The Discord client instance
|
|
62
|
+
* @throws {Error} If the subcommand name doesn't exist in the subcommands map
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* When a user runs `/birthday set`, this method:
|
|
66
|
+
* - Gets "set" from interaction.options.getSubcommand()
|
|
67
|
+
* - Looks up the SetBirthdayCommand in the subcommands map
|
|
68
|
+
* - Calls SetBirthdayCommand.execute()
|
|
69
|
+
*/
|
|
70
|
+
async execute(interaction, client) {
|
|
71
|
+
const subcommandName = interaction.options.getSubcommand();
|
|
72
|
+
const subcommand = this.subcommands.get(subcommandName);
|
|
73
|
+
if (!subcommand) {
|
|
74
|
+
throw new Error(`Unknown subcommand: ${subcommandName}`);
|
|
75
|
+
}
|
|
76
|
+
const scope = `${subcommand.name}_EXECUTION`;
|
|
77
|
+
await this.safeExecute(this.name, scope, interaction, () => subcommand.execute(interaction, client));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Converts the command group to Discord API JSON format for registration.
|
|
81
|
+
*
|
|
82
|
+
* This method:
|
|
83
|
+
* 1. Creates a SlashCommandBuilder with the group's name and description
|
|
84
|
+
* 2. Adds each subcommand from the subcommands map
|
|
85
|
+
* 3. Applies any custom options from each subcommand's `customize` method
|
|
86
|
+
* 4. Returns the JSON representation needed for Discord's API
|
|
87
|
+
*
|
|
88
|
+
* Called automatically by CommandManager when registering commands.
|
|
89
|
+
*
|
|
90
|
+
* @returns The command group in Discord API JSON format
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* For a birthday command group with "set" and "calendar" subcommands,
|
|
94
|
+
* this creates the structure for:
|
|
95
|
+
* - `/birthday set <options>`
|
|
96
|
+
* - `/birthday calendar`
|
|
97
|
+
*/
|
|
98
|
+
toJSON() {
|
|
99
|
+
const builder = new SlashCommandBuilder()
|
|
100
|
+
.setName(this.name)
|
|
101
|
+
.setDescription(this.description);
|
|
102
|
+
for (const cmd of this.subcommands.values()) {
|
|
103
|
+
builder.addSubcommand((sc) => {
|
|
104
|
+
sc.setName(cmd.name).setDescription(cmd.description);
|
|
105
|
+
if (cmd.customize) {
|
|
106
|
+
cmd.customize(sc);
|
|
107
|
+
}
|
|
108
|
+
return sc;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return builder.toJSON();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Gets a list of all subcommands with their names and descriptions.
|
|
115
|
+
*
|
|
116
|
+
* Useful for generating help text or documentation about available subcommands.
|
|
117
|
+
* Used by CommandManager's `getHelpPages` method to display subcommands in the help command.
|
|
118
|
+
*
|
|
119
|
+
* @returns Array of objects containing subcommand names and descriptions
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const subcommands = birthdayCommands.getSubcommandList();
|
|
124
|
+
* // Returns:
|
|
125
|
+
* // [
|
|
126
|
+
* // { name: 'set', description: 'Set your birthday' },
|
|
127
|
+
* // { name: 'calendar', description: 'View birthday calendar' }
|
|
128
|
+
* // ]
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
getSubcommandList() {
|
|
132
|
+
return Array.from(this.subcommands.values()).map((sub) => ({
|
|
133
|
+
name: sub.name,
|
|
134
|
+
description: sub.description,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Sets the logger for this subcommand group.
|
|
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { Command } from './classes/Command.class.js';
|
|
2
|
+
export { SubcommandGroup } from './classes/SubcommandGroup.class.js';
|
|
3
|
+
export { CommandManager } from './classes/CommandManager.class.js';
|
|
4
|
+
export { DiscordHandler } from './classes/DiscordHandler.class.js';
|
|
5
|
+
export { ModalManager } from './classes/ModalManager.class.js';
|
|
6
|
+
export { PaginatedEmbed } from './utils/PaginatedEmbed.class.js';
|
|
7
|
+
export type { PermissionLevel } from './types/permission.js';
|
|
8
|
+
export type { Modal, ModalField } from './types/modal.js';
|
|
9
|
+
export type { ButtonType } from './types/button.js';
|
|
10
|
+
export type { ILogger } from './types/logger.js';
|
|
11
|
+
export { getPermissionsForLevel } from './utils/permissions.js';
|
|
12
|
+
export { embedBuilder, createButton, createButtonsRow, createPaginationButtons, } from './utils/embeds.js';
|
|
13
|
+
export { stringOption, integerOption, booleanOption, userOption, channelOption, roleOption, } from './utils/slashCommandOptions.js';
|
|
14
|
+
export { safeReply, safeEdit } from './utils/editAndReply.js';
|
|
15
|
+
export { formatDuration, formatDateToString, formatDateToYYYYMMDDHHMMSS, formatDateToDDMMYYYY, getDaySuffix, capitalizeFirst, } from './utils/formatting.js';
|
|
16
|
+
export { TIMES_MILISECONDS } from './utils/miliseconds.js';
|
|
17
|
+
export { TToolboxLogger } from './utils/TToolboxLogger.class.js';
|
|
18
|
+
export { InteractionError } from './classes/InteractionError.class.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Classes
|
|
2
|
+
export { Command } from './classes/Command.class.js';
|
|
3
|
+
export { SubcommandGroup } from './classes/SubcommandGroup.class.js';
|
|
4
|
+
export { CommandManager } from './classes/CommandManager.class.js';
|
|
5
|
+
export { DiscordHandler } from './classes/DiscordHandler.class.js';
|
|
6
|
+
export { ModalManager } from './classes/ModalManager.class.js';
|
|
7
|
+
export { PaginatedEmbed } from './utils/PaginatedEmbed.class.js';
|
|
8
|
+
// Utilities
|
|
9
|
+
export { getPermissionsForLevel } from './utils/permissions.js';
|
|
10
|
+
export { embedBuilder, createButton, createButtonsRow, createPaginationButtons, } from './utils/embeds.js';
|
|
11
|
+
export { stringOption, integerOption, booleanOption, userOption, channelOption, roleOption, } from './utils/slashCommandOptions.js';
|
|
12
|
+
export { safeReply, safeEdit } from './utils/editAndReply.js';
|
|
13
|
+
export { formatDuration, formatDateToString, formatDateToYYYYMMDDHHMMSS, formatDateToDDMMYYYY, getDaySuffix, capitalizeFirst, } from './utils/formatting.js';
|
|
14
|
+
export { TIMES_MILISECONDS } from './utils/miliseconds.js';
|
|
15
|
+
export { TToolboxLogger } from './utils/TToolboxLogger.class.js';
|
|
16
|
+
// Errors
|
|
17
|
+
export { InteractionError } from './classes/InteractionError.class.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ChannelType } from 'discord.js';
|
|
2
|
+
export type AllowedChannelTypeChannelOption = ChannelType.GuildText | ChannelType.GuildVoice | ChannelType.GuildCategory | ChannelType.GuildAnnouncement | ChannelType.AnnouncementThread | ChannelType.PublicThread | ChannelType.PrivateThread | ChannelType.GuildStageVoice | ChannelType.GuildForum | ChannelType.GuildMedia;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for TToolboxLogger.
|
|
3
|
+
*/
|
|
4
|
+
export type LoggerOptions = {
|
|
5
|
+
/** Directory where log files should be stored */
|
|
6
|
+
logDir?: string;
|
|
7
|
+
/** Name of the log file */
|
|
8
|
+
logFileName?: string;
|
|
9
|
+
/** Custom log levels to use instead of defaults */
|
|
10
|
+
customLevels?: Record<string, string>;
|
|
11
|
+
/** Whether to extend default levels or replace them entirely */
|
|
12
|
+
extendDefaultLevels?: boolean;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Logger interface for TToolbox framework.
|
|
16
|
+
*
|
|
17
|
+
* Allows any logger implementation to be used with the framework,
|
|
18
|
+
* as long as it provides these core methods.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const customLogger: ILogger = {
|
|
23
|
+
* log: (msg, level, scope, console) => myCustomLog(msg),
|
|
24
|
+
* info: (msg, scope, console) => console.log(msg),
|
|
25
|
+
* warn: (msg, scope, console) => console.warn(msg),
|
|
26
|
+
* error: (msg, scope, console) => console.error(msg),
|
|
27
|
+
* };
|
|
28
|
+
*
|
|
29
|
+
* commandManager.setLogger(customLogger);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export interface ILogger {
|
|
33
|
+
log(message: string, level: string, scope: string, logToConsole?: boolean): void;
|
|
34
|
+
info(message: string, scope: string, logToConsole?: boolean): void;
|
|
35
|
+
warn(message: string, scope: string, logToConsole?: boolean): void;
|
|
36
|
+
error(message: string, scope: string, logToConsole?: boolean): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { TextInputStyle, ModalSubmitInteraction } from 'discord.js';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a single input field within a Discord modal.
|
|
4
|
+
*/
|
|
5
|
+
export type ModalField = {
|
|
6
|
+
/**
|
|
7
|
+
* The label text shown above the input field.
|
|
8
|
+
*/
|
|
9
|
+
name: string;
|
|
10
|
+
/**
|
|
11
|
+
* The type of input to display.
|
|
12
|
+
* Use {@link TextInputStyle.Short} for a single-line field
|
|
13
|
+
* or {@link TextInputStyle.Paragraph} for a multi-line field.
|
|
14
|
+
*/
|
|
15
|
+
style: TextInputStyle;
|
|
16
|
+
/**
|
|
17
|
+
* Unique identifier for this field within the modal.
|
|
18
|
+
* Used to retrieve the value in the `onSubmit` handler.
|
|
19
|
+
*/
|
|
20
|
+
customId: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional placeholder text shown when the field is empty.
|
|
23
|
+
*/
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Whether this field is required to be filled out.
|
|
27
|
+
* Defaults to `true`.
|
|
28
|
+
*/
|
|
29
|
+
required?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Minimum number of characters the input must contain.
|
|
32
|
+
*/
|
|
33
|
+
minLength?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Maximum number of characters the input can contain.
|
|
36
|
+
*/
|
|
37
|
+
maxLength?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Pre-filled value shown in the input field when the modal opens.
|
|
40
|
+
*/
|
|
41
|
+
value?: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Represents a Discord modal dialog configuration.
|
|
45
|
+
*/
|
|
46
|
+
export type Modal = {
|
|
47
|
+
/**
|
|
48
|
+
* The custom ID of the modal, used to match submissions.
|
|
49
|
+
*
|
|
50
|
+
* When using dynamic IDs (e.g., `edit-reminder:123`), the ModalManager
|
|
51
|
+
* will match based on the base ID before the colon.
|
|
52
|
+
*/
|
|
53
|
+
id: string;
|
|
54
|
+
/**
|
|
55
|
+
* Indicates whether this modal is **ephemeral** — meaning it's tied to specific
|
|
56
|
+
* context or data from its creation (e.g., a user, reminder ID, or other state).
|
|
57
|
+
*
|
|
58
|
+
* When `true`, the modal is automatically removed from the registry after
|
|
59
|
+
* submission to prevent reuse or context leakage. This is typically used for
|
|
60
|
+
* one-shot modals whose handlers depend on creation-time data.
|
|
61
|
+
*/
|
|
62
|
+
ephemeral: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* The title displayed at the top of the modal window.
|
|
65
|
+
*/
|
|
66
|
+
title: string;
|
|
67
|
+
/**
|
|
68
|
+
* The input fields displayed within the modal.
|
|
69
|
+
*/
|
|
70
|
+
fields: ModalField[];
|
|
71
|
+
/**
|
|
72
|
+
* Handler function called when the modal is submitted.
|
|
73
|
+
* Provides the `ModalSubmitInteraction` to access input values
|
|
74
|
+
* and reply to the user.
|
|
75
|
+
*/
|
|
76
|
+
onSubmit: (interaction: ModalSubmitInteraction) => Promise<any>;
|
|
77
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|