@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,241 @@
|
|
|
1
|
+
import { Client, Interaction, Message, MessageReaction, PartialMessageReaction, PartialUser, User, OmitPartialGroupDMChannel, PartialMessage, Awaitable, ClientEvents } from 'discord.js';
|
|
2
|
+
import { PrismaClient } from '@prisma/client';
|
|
3
|
+
import { TToolboxLogger } from '../utils/TToolboxLogger.class.js';
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base class for Discord bot event handling.
|
|
6
|
+
*
|
|
7
|
+
* Provides a structured way to handle Discord.js events with automatic setup,
|
|
8
|
+
* error handling, and graceful shutdown capabilities. Child classes should
|
|
9
|
+
* override the protected handler methods for the events they want to handle.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* class MyBotHandler extends DiscordHandler {
|
|
14
|
+
* protected async handleInteractionCreation(interaction: Interaction) {
|
|
15
|
+
* if (interaction.isChatInputCommand()) {
|
|
16
|
+
* await executeCommand(interaction);
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* const handler = new MyBotHandler(client);
|
|
22
|
+
* handler.setupErrorHandlers();
|
|
23
|
+
* await handler.setupOtherHandlers();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class DiscordHandler {
|
|
27
|
+
/** The Discord.js client instance */
|
|
28
|
+
client: Client;
|
|
29
|
+
/** The Prisma database client instance, Optional */
|
|
30
|
+
private prisma?;
|
|
31
|
+
/** The TToolboxLogger instance used for logging */
|
|
32
|
+
private logger;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new DiscordHandler instance.
|
|
35
|
+
*
|
|
36
|
+
* @param client - The Discord.js client instance
|
|
37
|
+
* @param prisma - The Prisma database client instance
|
|
38
|
+
*/
|
|
39
|
+
constructor(client: Client, logger: TToolboxLogger, prisma?: PrismaClient);
|
|
40
|
+
/**
|
|
41
|
+
* Sets up process-level error handlers and graceful shutdown handlers.
|
|
42
|
+
*
|
|
43
|
+
* This method registers handlers for:
|
|
44
|
+
* - Uncaught exceptions (exits process)
|
|
45
|
+
* - Unhandled promise rejections (logs warning)
|
|
46
|
+
* - SIGINT and SIGTERM signals (graceful shutdown)
|
|
47
|
+
*
|
|
48
|
+
* Should be called once during bot initialization.
|
|
49
|
+
*/
|
|
50
|
+
setupErrorHandlers(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Handles graceful shutdown of the bot.
|
|
53
|
+
*
|
|
54
|
+
* Properly cleans up resources by:
|
|
55
|
+
* 1. Destroying the Discord client connection
|
|
56
|
+
* 2. Disconnecting from the Prisma database
|
|
57
|
+
* 3. Exiting the process
|
|
58
|
+
*
|
|
59
|
+
* @param signal - The signal that triggered the shutdown (e.g., 'SIGINT', 'SIGTERM')
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
private gracefulShutdown;
|
|
63
|
+
/**
|
|
64
|
+
* Automatically sets up Discord event handlers for all implemented handler methods.
|
|
65
|
+
*
|
|
66
|
+
* This method checks which handler methods have been overridden in the child class
|
|
67
|
+
* and only registers event listeners for those handlers. This allows child classes
|
|
68
|
+
* to selectively implement only the events they need.
|
|
69
|
+
*
|
|
70
|
+
* Should be called once during bot initialization, after setupErrorHandlers().
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const handler = new MyBotHandler(client, prisma);
|
|
75
|
+
* handler.setupErrorHandlers();
|
|
76
|
+
* await handler.setupOtherHandlers();
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
setupOtherHandlers(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Sets up the Discord MessageCreate event listener.
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
private setupMessageCreateHandler;
|
|
85
|
+
/**
|
|
86
|
+
* Handles the MessageCreate event.
|
|
87
|
+
*
|
|
88
|
+
* Override this method to implement custom message handling logic.
|
|
89
|
+
* This is called whenever a message is created in any channel the bot can see.
|
|
90
|
+
*
|
|
91
|
+
* @param message - The message that was created
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* protected async handleMessageCreation(message: Message) {
|
|
96
|
+
* if (message.content.startsWith('!ping')) {
|
|
97
|
+
* await message.reply('Pong!');
|
|
98
|
+
* }
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
protected abstract handleMessageCreation(message: Message<boolean>): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Sets up the Discord InteractionCreate event listener.
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
private setupInteractionCreateHandler;
|
|
108
|
+
/**
|
|
109
|
+
* Handles the InteractionCreate event.
|
|
110
|
+
*
|
|
111
|
+
* Override this method to implement custom interaction handling logic.
|
|
112
|
+
* This is called for all types of interactions: slash commands, buttons,
|
|
113
|
+
* select menus, modals, etc.
|
|
114
|
+
*
|
|
115
|
+
* @param interaction - The interaction that was created
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* protected async handleInteractionCreation(interaction: Interaction) {
|
|
120
|
+
* if (interaction.isChatInputCommand()) {
|
|
121
|
+
* await commandManager.executeCommand(interaction.commandName, interaction, client);
|
|
122
|
+
* }
|
|
123
|
+
* if (interaction.isButton()) {
|
|
124
|
+
* await handleButtonClick(interaction);
|
|
125
|
+
* }
|
|
126
|
+
* }
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
protected abstract handleInteractionCreation(interaction: Interaction): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Sets up the Discord MessageReactionAdd event listener.
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
private setupMessageReactionAddHandler;
|
|
135
|
+
/**
|
|
136
|
+
* Handles the MessageReactionAdd event.
|
|
137
|
+
*
|
|
138
|
+
* Override this method to implement custom reaction handling logic.
|
|
139
|
+
* This is called whenever a user adds a reaction to a message.
|
|
140
|
+
*
|
|
141
|
+
* @param reaction - The reaction that was added (may be partial)
|
|
142
|
+
* @param user - The user who added the reaction (may be partial)
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* protected async handleReactionAdded(reaction: MessageReaction, user: User) {
|
|
147
|
+
* if (reaction.emoji.name === '⭐' && reaction.count >= 5) {
|
|
148
|
+
* await addToStarboard(reaction.message);
|
|
149
|
+
* }
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
protected abstract handleReactionAdded(reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser): Promise<void>;
|
|
154
|
+
/**
|
|
155
|
+
* Sets up the Discord MessageReactionRemove event listener.
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
private setupMessageReactionRemoveHandler;
|
|
159
|
+
/**
|
|
160
|
+
* Handles the MessageReactionRemove event.
|
|
161
|
+
*
|
|
162
|
+
* Override this method to implement custom reaction removal handling logic.
|
|
163
|
+
* This is called whenever a user removes a reaction from a message.
|
|
164
|
+
*
|
|
165
|
+
* @param reaction - The reaction that was removed (may be partial)
|
|
166
|
+
* @param user - The user who removed the reaction (may be partial)
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* protected async handleReactionRemoval(reaction: MessageReaction, user: User) {
|
|
171
|
+
* if (reaction.emoji.name === '⭐' && reaction.count < 5) {
|
|
172
|
+
* await removeFromStarboard(reaction.message);
|
|
173
|
+
* }
|
|
174
|
+
* }
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
protected abstract handleReactionRemoval(reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser): Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Sets up the Discord MessageDelete event listener.
|
|
180
|
+
* @private
|
|
181
|
+
*/
|
|
182
|
+
private setupMessageDeleteHandler;
|
|
183
|
+
/**
|
|
184
|
+
* Handles the MessageDelete event.
|
|
185
|
+
*
|
|
186
|
+
* Override this method to implement custom message deletion handling logic.
|
|
187
|
+
* This is called whenever a message is deleted.
|
|
188
|
+
*
|
|
189
|
+
* @param message - The message that was deleted (may be partial)
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* protected async handleMessageDeletion(message: Message) {
|
|
194
|
+
* await logDeletedMessage(message);
|
|
195
|
+
* }
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
protected abstract handleMessageDeletion(message: OmitPartialGroupDMChannel<Message<boolean> | PartialMessage>): Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* Register a custom event handler for any Discord.js event not covered by the base handlers.
|
|
201
|
+
*
|
|
202
|
+
* This allows you to listen to any Discord.js event beyond the standard ones
|
|
203
|
+
* (MessageCreate, InteractionCreate, etc.) that are built into the base class.
|
|
204
|
+
*
|
|
205
|
+
* @param event - The Discord.js event name (e.g., Events.GuildMemberAdd)
|
|
206
|
+
* @param handler - The handler function to call when the event fires
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```typescript
|
|
210
|
+
* this.registerCustomHandler(Events.GuildMemberAdd, async (member) => {
|
|
211
|
+
* await sendWelcomeMessage(member);
|
|
212
|
+
* });
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
protected registerCustomHandler<K extends keyof ClientEvents>(event: K, handler: (...args: ClientEvents[K]) => Awaitable<void>): void;
|
|
216
|
+
/**
|
|
217
|
+
* Register multiple custom event handlers at once.
|
|
218
|
+
*
|
|
219
|
+
* Convenience method for registering several custom event handlers in a single call.
|
|
220
|
+
*
|
|
221
|
+
* @param handlers - Array of event/handler pairs to register
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* this.registerCustomHandlers([
|
|
226
|
+
* {
|
|
227
|
+
* event: Events.GuildMemberAdd,
|
|
228
|
+
* handler: async (member) => await sendWelcomeMessage(member),
|
|
229
|
+
* },
|
|
230
|
+
* {
|
|
231
|
+
* event: Events.VoiceStateUpdate,
|
|
232
|
+
* handler: async (oldState, newState) => await handleVoiceChange(oldState, newState),
|
|
233
|
+
* },
|
|
234
|
+
* ]);
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
protected registerCustomHandlers(handlers: Array<{
|
|
238
|
+
event: keyof ClientEvents;
|
|
239
|
+
handler: (...args: any[]) => Awaitable<void>;
|
|
240
|
+
}>): void;
|
|
241
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
4
|
+
import { Events, } from 'discord.js';
|
|
5
|
+
/**
|
|
6
|
+
* Abstract base class for Discord bot event handling.
|
|
7
|
+
*
|
|
8
|
+
* Provides a structured way to handle Discord.js events with automatic setup,
|
|
9
|
+
* error handling, and graceful shutdown capabilities. Child classes should
|
|
10
|
+
* override the protected handler methods for the events they want to handle.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* class MyBotHandler extends DiscordHandler {
|
|
15
|
+
* protected async handleInteractionCreation(interaction: Interaction) {
|
|
16
|
+
* if (interaction.isChatInputCommand()) {
|
|
17
|
+
* await executeCommand(interaction);
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* const handler = new MyBotHandler(client);
|
|
23
|
+
* handler.setupErrorHandlers();
|
|
24
|
+
* await handler.setupOtherHandlers();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class DiscordHandler {
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new DiscordHandler instance.
|
|
30
|
+
*
|
|
31
|
+
* @param client - The Discord.js client instance
|
|
32
|
+
* @param prisma - The Prisma database client instance
|
|
33
|
+
*/
|
|
34
|
+
constructor(client, logger, prisma) {
|
|
35
|
+
this.client = client;
|
|
36
|
+
this.prisma = prisma;
|
|
37
|
+
this.logger = logger;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Sets up process-level error handlers and graceful shutdown handlers.
|
|
41
|
+
*
|
|
42
|
+
* This method registers handlers for:
|
|
43
|
+
* - Uncaught exceptions (exits process)
|
|
44
|
+
* - Unhandled promise rejections (logs warning)
|
|
45
|
+
* - SIGINT and SIGTERM signals (graceful shutdown)
|
|
46
|
+
*
|
|
47
|
+
* Should be called once during bot initialization.
|
|
48
|
+
*/
|
|
49
|
+
setupErrorHandlers() {
|
|
50
|
+
process.on('uncaughtException', (err) => {
|
|
51
|
+
this.logger.error(`Uncaught Exception: ${err}`, 'errorhandler', true);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
54
|
+
process.on('unhandledRejection', (reason) => {
|
|
55
|
+
this.logger.warn(`Unhandled Rejection: ${reason}`, 'errorhandler');
|
|
56
|
+
});
|
|
57
|
+
process.on('SIGINT', () => void this.gracefulShutdown('SIGINT'));
|
|
58
|
+
process.on('SIGTERM', () => void this.gracefulShutdown('SIGTERM'));
|
|
59
|
+
this.logger.info('Error handlers set up.', 'startup');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Handles graceful shutdown of the bot.
|
|
63
|
+
*
|
|
64
|
+
* Properly cleans up resources by:
|
|
65
|
+
* 1. Destroying the Discord client connection
|
|
66
|
+
* 2. Disconnecting from the Prisma database
|
|
67
|
+
* 3. Exiting the process
|
|
68
|
+
*
|
|
69
|
+
* @param signal - The signal that triggered the shutdown (e.g., 'SIGINT', 'SIGTERM')
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
async gracefulShutdown(signal) {
|
|
73
|
+
const scope = 'shutdown';
|
|
74
|
+
this.logger.info(`${signal} received. Cleaning up...`, scope);
|
|
75
|
+
try {
|
|
76
|
+
if (this.client.isReady()) {
|
|
77
|
+
this.logger.info(`Destroying Discord client...`, scope);
|
|
78
|
+
await this.client.destroy();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
this.logger.error(`Failed to destroy Discord client: ${err}`, scope, true);
|
|
83
|
+
}
|
|
84
|
+
if (this.prisma) {
|
|
85
|
+
try {
|
|
86
|
+
this.logger.info(`Disconnecting Prisma...`, scope);
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
88
|
+
await this.prisma.$disconnect();
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
this.logger.error(`Failed to disconnect Prisma: ${err}`, scope, true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Automatically sets up Discord event handlers for all implemented handler methods.
|
|
98
|
+
*
|
|
99
|
+
* This method checks which handler methods have been overridden in the child class
|
|
100
|
+
* and only registers event listeners for those handlers. This allows child classes
|
|
101
|
+
* to selectively implement only the events they need.
|
|
102
|
+
*
|
|
103
|
+
* Should be called once during bot initialization, after setupErrorHandlers().
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const handler = new MyBotHandler(client, prisma);
|
|
108
|
+
* handler.setupErrorHandlers();
|
|
109
|
+
* await handler.setupOtherHandlers();
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
async setupOtherHandlers() {
|
|
113
|
+
const baseClass = Object.getPrototypeOf(this.constructor);
|
|
114
|
+
if (this.handleMessageCreation !== baseClass.prototype.handleMessageCreation) {
|
|
115
|
+
await this.setupMessageCreateHandler();
|
|
116
|
+
}
|
|
117
|
+
if (this.handleInteractionCreation !==
|
|
118
|
+
baseClass.prototype.handleInteractionCreation) {
|
|
119
|
+
await this.setupInteractionCreateHandler();
|
|
120
|
+
}
|
|
121
|
+
if (this.handleReactionAdded !== baseClass.prototype.handleReactionAdded) {
|
|
122
|
+
await this.setupMessageReactionAddHandler();
|
|
123
|
+
}
|
|
124
|
+
if (this.handleReactionRemoval !== baseClass.prototype.handleReactionRemoval) {
|
|
125
|
+
await this.setupMessageReactionRemoveHandler();
|
|
126
|
+
}
|
|
127
|
+
if (this.handleMessageDeletion !== baseClass.prototype.handleMessageDeletion) {
|
|
128
|
+
await this.setupMessageDeleteHandler();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sets up the Discord MessageCreate event listener.
|
|
133
|
+
* @private
|
|
134
|
+
*/
|
|
135
|
+
async setupMessageCreateHandler() {
|
|
136
|
+
this.client.on(Events.MessageCreate, async (message) => {
|
|
137
|
+
await this.handleMessageCreation(message);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Sets up the Discord InteractionCreate event listener.
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
async setupInteractionCreateHandler() {
|
|
145
|
+
this.client.on(Events.InteractionCreate, async (interaction) => {
|
|
146
|
+
await this.handleInteractionCreation(interaction);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Sets up the Discord MessageReactionAdd event listener.
|
|
151
|
+
* @private
|
|
152
|
+
*/
|
|
153
|
+
async setupMessageReactionAddHandler() {
|
|
154
|
+
this.client.on(Events.MessageReactionAdd, async (reaction, user) => {
|
|
155
|
+
await this.handleReactionAdded(reaction, user);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Sets up the Discord MessageReactionRemove event listener.
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
async setupMessageReactionRemoveHandler() {
|
|
163
|
+
this.client.on(Events.MessageReactionRemove, async (reaction, user) => {
|
|
164
|
+
await this.handleReactionRemoval(reaction, user);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Sets up the Discord MessageDelete event listener.
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
async setupMessageDeleteHandler() {
|
|
172
|
+
this.client.on(Events.MessageDelete, async (message) => {
|
|
173
|
+
await this.handleMessageDeletion(message);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Register a custom event handler for any Discord.js event not covered by the base handlers.
|
|
178
|
+
*
|
|
179
|
+
* This allows you to listen to any Discord.js event beyond the standard ones
|
|
180
|
+
* (MessageCreate, InteractionCreate, etc.) that are built into the base class.
|
|
181
|
+
*
|
|
182
|
+
* @param event - The Discord.js event name (e.g., Events.GuildMemberAdd)
|
|
183
|
+
* @param handler - The handler function to call when the event fires
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* this.registerCustomHandler(Events.GuildMemberAdd, async (member) => {
|
|
188
|
+
* await sendWelcomeMessage(member);
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
registerCustomHandler(event, handler) {
|
|
193
|
+
this.client.on(event, handler);
|
|
194
|
+
this.logger.info(`Registered custom handler for event: ${event}`, 'handler');
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Register multiple custom event handlers at once.
|
|
198
|
+
*
|
|
199
|
+
* Convenience method for registering several custom event handlers in a single call.
|
|
200
|
+
*
|
|
201
|
+
* @param handlers - Array of event/handler pairs to register
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* this.registerCustomHandlers([
|
|
206
|
+
* {
|
|
207
|
+
* event: Events.GuildMemberAdd,
|
|
208
|
+
* handler: async (member) => await sendWelcomeMessage(member),
|
|
209
|
+
* },
|
|
210
|
+
* {
|
|
211
|
+
* event: Events.VoiceStateUpdate,
|
|
212
|
+
* handler: async (oldState, newState) => await handleVoiceChange(oldState, newState),
|
|
213
|
+
* },
|
|
214
|
+
* ]);
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
registerCustomHandlers(handlers) {
|
|
218
|
+
for (const { event, handler } of handlers) {
|
|
219
|
+
this.registerCustomHandler(event, handler);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error for interaction failures
|
|
3
|
+
*/
|
|
4
|
+
export declare class InteractionError extends Error {
|
|
5
|
+
readonly interactionId: string;
|
|
6
|
+
readonly reason: 'expired' | 'failed';
|
|
7
|
+
constructor(message: string, interactionId: string, reason: 'expired' | 'failed');
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error for interaction failures
|
|
3
|
+
*/
|
|
4
|
+
export class InteractionError extends Error {
|
|
5
|
+
constructor(message, interactionId, reason) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.interactionId = interactionId;
|
|
8
|
+
this.reason = reason;
|
|
9
|
+
this.name = 'InteractionError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { ModalBuilder, ModalSubmitInteraction } from 'discord.js';
|
|
2
|
+
import { Modal } from '../types/modal.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 declare class ModalManager {
|
|
37
|
+
private modals;
|
|
38
|
+
/**
|
|
39
|
+
* Builds a Discord ModalBuilder and registers the modal for submission handling.
|
|
40
|
+
*
|
|
41
|
+
* Creates a modal with the specified fields and registers it so that when
|
|
42
|
+
* a user submits it, the onSubmit handler will be called.
|
|
43
|
+
*
|
|
44
|
+
* @param data - The modal configuration including fields and submission handler
|
|
45
|
+
* @returns A ModalBuilder ready to be shown to the user
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const modal = modalManager.buildAndRegister({
|
|
50
|
+
* id: 'edit-reminder',
|
|
51
|
+
* title: 'Edit Reminder',
|
|
52
|
+
* ephemeral: true,
|
|
53
|
+
* fields: [
|
|
54
|
+
* {
|
|
55
|
+
* customId: 'message',
|
|
56
|
+
* name: 'Reminder Message',
|
|
57
|
+
* style: TextInputStyle.Short,
|
|
58
|
+
* required: true,
|
|
59
|
+
* value: existingMessage,
|
|
60
|
+
* }
|
|
61
|
+
* ],
|
|
62
|
+
* onSubmit: async (interaction) => {
|
|
63
|
+
* const newMessage = interaction.fields.getTextInputValue('message');
|
|
64
|
+
* await updateReminder(id, newMessage);
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* await buttonInteraction.showModal(modal);
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
buildAndRegister(data: Modal): ModalBuilder;
|
|
72
|
+
/**
|
|
73
|
+
* Retrieves a registered modal by its ID.
|
|
74
|
+
*
|
|
75
|
+
* Supports dynamic IDs - if the exact ID isn't found, attempts to match
|
|
76
|
+
* using the base ID (before the first colon). This allows for modals with
|
|
77
|
+
* dynamic suffixes like "edit-reminder:123".
|
|
78
|
+
*
|
|
79
|
+
* @param id - The modal ID to look up
|
|
80
|
+
* @returns The modal configuration, or undefined if not found
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // Register with base ID
|
|
85
|
+
* modalManager.buildAndRegister({ id: 'edit-reminder', ... });
|
|
86
|
+
*
|
|
87
|
+
* // Can retrieve with dynamic ID
|
|
88
|
+
* const modal = modalManager.get('edit-reminder:123'); // Works!
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
get(id: string): Modal | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Removes a modal from the registry.
|
|
94
|
+
*
|
|
95
|
+
* Useful for cleaning up ephemeral modals after they've been submitted,
|
|
96
|
+
* or for unregistering modals that are no longer needed.
|
|
97
|
+
*
|
|
98
|
+
* @param id - The modal ID to remove
|
|
99
|
+
* @returns true if the modal was removed, false if it didn't exist
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // Clean up after submission
|
|
104
|
+
* await modalManager.handleSubmit(interaction);
|
|
105
|
+
* modalManager.remove(interaction.customId); // If ephemeral
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
remove(id: string): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Handles a modal submission by calling the registered onSubmit handler.
|
|
111
|
+
*
|
|
112
|
+
* Looks up the modal by ID, calls its onSubmit handler, and automatically
|
|
113
|
+
* removes ephemeral modals from the registry after submission.
|
|
114
|
+
*
|
|
115
|
+
* @param interaction - The modal submit interaction
|
|
116
|
+
* @throws {Error} If no modal is found for the interaction's custom ID
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* // In your interaction handler
|
|
121
|
+
* client.on('interactionCreate', async (interaction) => {
|
|
122
|
+
* if (interaction.isModalSubmit()) {
|
|
123
|
+
* await modalManager.handleSubmit(interaction);
|
|
124
|
+
* }
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
handleSubmit(interaction: ModalSubmitInteraction): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Checks if a modal with the given ID is registered.
|
|
131
|
+
*
|
|
132
|
+
* @param id - The modal ID to check
|
|
133
|
+
* @returns true if the modal exists in the registry
|
|
134
|
+
*/
|
|
135
|
+
has(id: string): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Clears all registered modals from the registry.
|
|
138
|
+
*
|
|
139
|
+
* Useful for cleanup during bot shutdown or for testing.
|
|
140
|
+
*/
|
|
141
|
+
clear(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Gets the total number of registered modals.
|
|
144
|
+
*
|
|
145
|
+
* @returns The count of modals in the registry
|
|
146
|
+
*/
|
|
147
|
+
get size(): number;
|
|
148
|
+
/**
|
|
149
|
+
* Gets all registered modal IDs.
|
|
150
|
+
*
|
|
151
|
+
* @returns Array of modal IDs currently in the registry
|
|
152
|
+
*/
|
|
153
|
+
getModalIds(): string[];
|
|
154
|
+
}
|