@julanzw/ttoolbox-discordjs-framework 1.1.0 → 1.2.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/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type { PermissionLevel } from './types/permission.js';
|
|
|
8
8
|
export type { Modal, ModalField } from './types/modal.js';
|
|
9
9
|
export type { ButtonType } from './types/button.js';
|
|
10
10
|
export type { ILogger } from './types/logger.js';
|
|
11
|
+
export type { AnySelectMenuInteraction, ButtonHandler, SelectMenuHandler, ComponentConfig } from './types/component.js';
|
|
11
12
|
export { getPermissionsForLevel } from './utils/permissions.js';
|
|
12
13
|
export { embedBuilder, createButton, createButtonsRow, createPaginationButtons, } from './utils/embeds.js';
|
|
13
14
|
export { stringOption, integerOption, booleanOption, userOption, channelOption, roleOption, } from './utils/slashCommandOptions.js';
|
|
@@ -16,4 +17,5 @@ export { formatDuration, formatDateToString, formatDateToYYYYMMDDHHMMSS, formatD
|
|
|
16
17
|
export { TIMES_MILISECONDS } from './utils/miliseconds.js';
|
|
17
18
|
export { TToolboxLogger } from './utils/TToolboxLogger.class.js';
|
|
18
19
|
export { ErrorReporter } from './utils/ErrorReporter.js';
|
|
20
|
+
export { ComponentManager } from './utils/ComponentManager.class.js';
|
|
19
21
|
export { InteractionError } from './classes/InteractionError.class.js';
|
package/dist/index.js
CHANGED
|
@@ -14,5 +14,6 @@ export { formatDuration, formatDateToString, formatDateToYYYYMMDDHHMMSS, formatD
|
|
|
14
14
|
export { TIMES_MILISECONDS } from './utils/miliseconds.js';
|
|
15
15
|
export { TToolboxLogger } from './utils/TToolboxLogger.class.js';
|
|
16
16
|
export { ErrorReporter } from './utils/ErrorReporter.js';
|
|
17
|
+
export { ComponentManager } from './utils/ComponentManager.class.js';
|
|
17
18
|
// Errors
|
|
18
19
|
export { InteractionError } from './classes/InteractionError.class.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { StringSelectMenuInteraction, ChannelSelectMenuInteraction, RoleSelectMenuInteraction, UserSelectMenuInteraction, MentionableSelectMenuInteraction, ButtonInteraction } from "discord.js";
|
|
2
|
+
/**
|
|
3
|
+
* Type for all select menu interactions
|
|
4
|
+
*/
|
|
5
|
+
export type AnySelectMenuInteraction = StringSelectMenuInteraction | ChannelSelectMenuInteraction | RoleSelectMenuInteraction | UserSelectMenuInteraction | MentionableSelectMenuInteraction;
|
|
6
|
+
/**
|
|
7
|
+
* Handler function for button interactions
|
|
8
|
+
*/
|
|
9
|
+
export type ButtonHandler = (interaction: ButtonInteraction) => Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Handler function for select menu interactions
|
|
12
|
+
*/
|
|
13
|
+
export type SelectMenuHandler = (interaction: AnySelectMenuInteraction) => Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Configuration for a component handler
|
|
16
|
+
*/
|
|
17
|
+
export interface ComponentConfig {
|
|
18
|
+
/** Whether this component should be removed after being handled once */
|
|
19
|
+
ephemeral?: boolean;
|
|
20
|
+
/** Optional timeout in milliseconds after which the handler is removed */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { ButtonInteraction, AnySelectMenuInteraction } from 'discord.js';
|
|
2
|
+
import type { ILogger } from '../types/logger.js';
|
|
3
|
+
import { ButtonHandler, ComponentConfig, SelectMenuHandler } from '../types/component.js';
|
|
4
|
+
/**
|
|
5
|
+
* Manages Discord component interactions (buttons and select menus).
|
|
6
|
+
*
|
|
7
|
+
* Provides centralized registration and handling of long living
|
|
8
|
+
* button clicks and select menu interactions.
|
|
9
|
+
*
|
|
10
|
+
* Supports dynamic custom IDs (e.g., "delete-feed:123"),
|
|
11
|
+
* ephemeral handlers (one-time use), and automatic cleanup.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const componentManager = new ComponentManager(logger);
|
|
16
|
+
*
|
|
17
|
+
* // Register a button handler
|
|
18
|
+
* componentManager.registerButton('delete-feed', async (interaction) => {
|
|
19
|
+
* const feedId = interaction.customId.split(':')[1];
|
|
20
|
+
* await deleteFeed(feedId);
|
|
21
|
+
* await interaction.reply('Feed deleted!');
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Register a select menu handler
|
|
25
|
+
* componentManager.registerSelect('choose-feed-type', async (interaction) => {
|
|
26
|
+
* const type = interaction.values[0];
|
|
27
|
+
* await interaction.reply(`You selected: ${type}`);
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Handle interactions
|
|
31
|
+
* client.on('interactionCreate', async (interaction) => {
|
|
32
|
+
* if (interaction.isButton()) {
|
|
33
|
+
* await componentManager.handleButton(interaction);
|
|
34
|
+
* }
|
|
35
|
+
* if (interaction.isAnySelectMenu()) {
|
|
36
|
+
* await componentManager.handleSelect(interaction);
|
|
37
|
+
* }
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare class ComponentManager {
|
|
42
|
+
private logger?;
|
|
43
|
+
private buttonHandlers;
|
|
44
|
+
private selectHandlers;
|
|
45
|
+
private timeouts;
|
|
46
|
+
constructor(logger?: ILogger | undefined);
|
|
47
|
+
/**
|
|
48
|
+
* Register a button click handler.
|
|
49
|
+
*
|
|
50
|
+
* Supports dynamic custom IDs - if the exact ID isn't found, attempts to match
|
|
51
|
+
* using the base ID (before the first colon).\
|
|
52
|
+
* This allows for buttons with dynamic suffixes like "global-delete:123".
|
|
53
|
+
*
|
|
54
|
+
* Its recommended to add a prefix like ```global-``` to the id to fully
|
|
55
|
+
* distingush it from other local component handlers.
|
|
56
|
+
*
|
|
57
|
+
* @param customId - The button's custom ID (or base ID for dynamic buttons)
|
|
58
|
+
* @param handler - Function to call when the button is clicked
|
|
59
|
+
* @param config - Optional configuration (ephemeral, timeout)
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* // Simple button
|
|
64
|
+
* componentManager.registerButton('global-refresh', async (interaction) => {
|
|
65
|
+
* await interaction.reply('Refreshed!');
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // Dynamic button (matches "global-delete:123", "global-delete:456", etc.)
|
|
69
|
+
* componentManager.registerButton('global-delete', async (interaction) => {
|
|
70
|
+
* const id = interaction.customId.split(':')[1];
|
|
71
|
+
* await deleteItem(id);
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* // Ephemeral button (one-time use)
|
|
75
|
+
* componentManager.registerButton('global-confirm', async (interaction) => {
|
|
76
|
+
* await processConfirmation();
|
|
77
|
+
* }, { ephemeral: true });
|
|
78
|
+
*
|
|
79
|
+
* // Button with timeout (auto-remove after 5 minutes)
|
|
80
|
+
* componentManager.registerButton('global-temp-action', async (interaction) => {
|
|
81
|
+
* await doTempAction();
|
|
82
|
+
* }, { timeout: 5 * 60 * 1000 });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
registerButton(customId: string, handler: ButtonHandler, config?: ComponentConfig): void;
|
|
86
|
+
/**
|
|
87
|
+
* Register a select menu handler.
|
|
88
|
+
*
|
|
89
|
+
* Supports dynamic custom IDs - if the exact ID isn't found, attempts to match
|
|
90
|
+
* using the base ID (before the first colon).
|
|
91
|
+
*
|
|
92
|
+
* Its recommended to add a prefix like ```global-``` to the id to fully
|
|
93
|
+
* distingush it from other local component handlers.
|
|
94
|
+
*
|
|
95
|
+
* @param customId - The select menu's custom ID (or base ID for dynamic menus)
|
|
96
|
+
* @param handler - Function to call when a selection is made
|
|
97
|
+
* @param config - Optional configuration (ephemeral, timeout)
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* // String select menu
|
|
102
|
+
* componentManager.registerSelect('global-choose-type', async (interaction) => {
|
|
103
|
+
* const selected = interaction.values[0];
|
|
104
|
+
* await interaction.reply(`You chose: ${selected}`);
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* // Dynamic select menu
|
|
108
|
+
* componentManager.registerSelect('global-choose-item', async (interaction) => {
|
|
109
|
+
* const itemId = interaction.customId.split(':')[1];
|
|
110
|
+
* const selected = interaction.values;
|
|
111
|
+
* await processSelection(itemId, selected);
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
registerSelect(customId: string, handler: SelectMenuHandler, config?: ComponentConfig): void;
|
|
116
|
+
/**
|
|
117
|
+
* Handle a button interaction.
|
|
118
|
+
*
|
|
119
|
+
* Automatically matches dynamic IDs and removes ephemeral handlers after use.
|
|
120
|
+
*
|
|
121
|
+
* @param interaction - The button interaction to handle
|
|
122
|
+
* @throws {Error} If no handler is found for the button
|
|
123
|
+
*/
|
|
124
|
+
handleButton(interaction: ButtonInteraction): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Handle a select menu interaction.
|
|
127
|
+
*
|
|
128
|
+
* Automatically matches dynamic IDs and removes ephemeral handlers after use.
|
|
129
|
+
*
|
|
130
|
+
* @param interaction - The select menu interaction to handle
|
|
131
|
+
* @throws {Error} If no handler is found for the select menu
|
|
132
|
+
*/
|
|
133
|
+
handleSelect(interaction: AnySelectMenuInteraction): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Find a handler by custom ID, supporting dynamic IDs.
|
|
136
|
+
*
|
|
137
|
+
* First tries exact match, then tries base ID (before colon).
|
|
138
|
+
*/
|
|
139
|
+
private findHandler;
|
|
140
|
+
/**
|
|
141
|
+
* Set up automatic removal timeout for a handler.
|
|
142
|
+
*/
|
|
143
|
+
private setupTimeout;
|
|
144
|
+
/**
|
|
145
|
+
* Unregister a button handler.
|
|
146
|
+
*
|
|
147
|
+
* @param customId - The button's custom ID
|
|
148
|
+
* @returns true if the handler was removed, false if it didn't exist
|
|
149
|
+
*/
|
|
150
|
+
unregisterButton(customId: string): boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Unregister a select menu handler.
|
|
153
|
+
*
|
|
154
|
+
* @param customId - The select menu's custom ID
|
|
155
|
+
* @returns true if the handler was removed, false if it didn't exist
|
|
156
|
+
*/
|
|
157
|
+
unregisterSelect(customId: string): boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Check if a button handler is registered.
|
|
160
|
+
*
|
|
161
|
+
* @param customId - The button's custom ID
|
|
162
|
+
* @returns true if a handler exists (exact or base ID match)
|
|
163
|
+
*/
|
|
164
|
+
hasButton(customId: string): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Check if a select menu handler is registered.
|
|
167
|
+
*
|
|
168
|
+
* @param customId - The select menu's custom ID
|
|
169
|
+
* @returns true if a handler exists (exact or base ID match)
|
|
170
|
+
*/
|
|
171
|
+
hasSelect(customId: string): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Clear all registered handlers.
|
|
174
|
+
*
|
|
175
|
+
* Useful for cleanup during bot shutdown or testing.
|
|
176
|
+
*/
|
|
177
|
+
clear(): void;
|
|
178
|
+
/**
|
|
179
|
+
* Get the total number of registered button handlers.
|
|
180
|
+
*/
|
|
181
|
+
get buttonCount(): number;
|
|
182
|
+
/**
|
|
183
|
+
* Get the total number of registered select menu handlers.
|
|
184
|
+
*/
|
|
185
|
+
get selectCount(): number;
|
|
186
|
+
/**
|
|
187
|
+
* Get all registered button IDs.
|
|
188
|
+
*/
|
|
189
|
+
getButtonIds(): string[];
|
|
190
|
+
/**
|
|
191
|
+
* Get all registered select menu IDs.
|
|
192
|
+
*/
|
|
193
|
+
getSelectIds(): string[];
|
|
194
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages Discord component interactions (buttons and select menus).
|
|
3
|
+
*
|
|
4
|
+
* Provides centralized registration and handling of long living
|
|
5
|
+
* button clicks and select menu interactions.
|
|
6
|
+
*
|
|
7
|
+
* Supports dynamic custom IDs (e.g., "delete-feed:123"),
|
|
8
|
+
* ephemeral handlers (one-time use), and automatic cleanup.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const componentManager = new ComponentManager(logger);
|
|
13
|
+
*
|
|
14
|
+
* // Register a button handler
|
|
15
|
+
* componentManager.registerButton('delete-feed', async (interaction) => {
|
|
16
|
+
* const feedId = interaction.customId.split(':')[1];
|
|
17
|
+
* await deleteFeed(feedId);
|
|
18
|
+
* await interaction.reply('Feed deleted!');
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Register a select menu handler
|
|
22
|
+
* componentManager.registerSelect('choose-feed-type', async (interaction) => {
|
|
23
|
+
* const type = interaction.values[0];
|
|
24
|
+
* await interaction.reply(`You selected: ${type}`);
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Handle interactions
|
|
28
|
+
* client.on('interactionCreate', async (interaction) => {
|
|
29
|
+
* if (interaction.isButton()) {
|
|
30
|
+
* await componentManager.handleButton(interaction);
|
|
31
|
+
* }
|
|
32
|
+
* if (interaction.isAnySelectMenu()) {
|
|
33
|
+
* await componentManager.handleSelect(interaction);
|
|
34
|
+
* }
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class ComponentManager {
|
|
39
|
+
constructor(logger) {
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
this.buttonHandlers = new Map();
|
|
42
|
+
this.selectHandlers = new Map();
|
|
43
|
+
this.timeouts = new Map();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Register a button click handler.
|
|
47
|
+
*
|
|
48
|
+
* Supports dynamic custom IDs - if the exact ID isn't found, attempts to match
|
|
49
|
+
* using the base ID (before the first colon).\
|
|
50
|
+
* This allows for buttons with dynamic suffixes like "global-delete:123".
|
|
51
|
+
*
|
|
52
|
+
* Its recommended to add a prefix like ```global-``` to the id to fully
|
|
53
|
+
* distingush it from other local component handlers.
|
|
54
|
+
*
|
|
55
|
+
* @param customId - The button's custom ID (or base ID for dynamic buttons)
|
|
56
|
+
* @param handler - Function to call when the button is clicked
|
|
57
|
+
* @param config - Optional configuration (ephemeral, timeout)
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // Simple button
|
|
62
|
+
* componentManager.registerButton('global-refresh', async (interaction) => {
|
|
63
|
+
* await interaction.reply('Refreshed!');
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* // Dynamic button (matches "global-delete:123", "global-delete:456", etc.)
|
|
67
|
+
* componentManager.registerButton('global-delete', async (interaction) => {
|
|
68
|
+
* const id = interaction.customId.split(':')[1];
|
|
69
|
+
* await deleteItem(id);
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Ephemeral button (one-time use)
|
|
73
|
+
* componentManager.registerButton('global-confirm', async (interaction) => {
|
|
74
|
+
* await processConfirmation();
|
|
75
|
+
* }, { ephemeral: true });
|
|
76
|
+
*
|
|
77
|
+
* // Button with timeout (auto-remove after 5 minutes)
|
|
78
|
+
* componentManager.registerButton('global-temp-action', async (interaction) => {
|
|
79
|
+
* await doTempAction();
|
|
80
|
+
* }, { timeout: 5 * 60 * 1000 });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
registerButton(customId, handler, config = {}) {
|
|
84
|
+
this.buttonHandlers.set(customId, { handler, config });
|
|
85
|
+
// Set up auto-removal timeout if specified
|
|
86
|
+
if (config.timeout) {
|
|
87
|
+
this.setupTimeout('button', customId, config.timeout);
|
|
88
|
+
}
|
|
89
|
+
this.logger?.info(`Registered button handler: ${customId}${config.ephemeral ? ' (ephemeral)' : ''}${config.timeout ? ` (timeout: ${config.timeout}ms)` : ''}`, 'component-manager');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Register a select menu handler.
|
|
93
|
+
*
|
|
94
|
+
* Supports dynamic custom IDs - if the exact ID isn't found, attempts to match
|
|
95
|
+
* using the base ID (before the first colon).
|
|
96
|
+
*
|
|
97
|
+
* Its recommended to add a prefix like ```global-``` to the id to fully
|
|
98
|
+
* distingush it from other local component handlers.
|
|
99
|
+
*
|
|
100
|
+
* @param customId - The select menu's custom ID (or base ID for dynamic menus)
|
|
101
|
+
* @param handler - Function to call when a selection is made
|
|
102
|
+
* @param config - Optional configuration (ephemeral, timeout)
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // String select menu
|
|
107
|
+
* componentManager.registerSelect('global-choose-type', async (interaction) => {
|
|
108
|
+
* const selected = interaction.values[0];
|
|
109
|
+
* await interaction.reply(`You chose: ${selected}`);
|
|
110
|
+
* });
|
|
111
|
+
*
|
|
112
|
+
* // Dynamic select menu
|
|
113
|
+
* componentManager.registerSelect('global-choose-item', async (interaction) => {
|
|
114
|
+
* const itemId = interaction.customId.split(':')[1];
|
|
115
|
+
* const selected = interaction.values;
|
|
116
|
+
* await processSelection(itemId, selected);
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
registerSelect(customId, handler, config = {}) {
|
|
121
|
+
this.selectHandlers.set(customId, { handler, config });
|
|
122
|
+
if (config.timeout) {
|
|
123
|
+
this.setupTimeout('select', customId, config.timeout);
|
|
124
|
+
}
|
|
125
|
+
this.logger?.info(`Registered select menu handler: ${customId}${config.ephemeral ? ' (ephemeral)' : ''}${config.timeout ? ` (timeout: ${config.timeout}ms)` : ''}`, 'component-manager');
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Handle a button interaction.
|
|
129
|
+
*
|
|
130
|
+
* Automatically matches dynamic IDs and removes ephemeral handlers after use.
|
|
131
|
+
*
|
|
132
|
+
* @param interaction - The button interaction to handle
|
|
133
|
+
* @throws {Error} If no handler is found for the button
|
|
134
|
+
*/
|
|
135
|
+
async handleButton(interaction) {
|
|
136
|
+
if (interaction.replied || interaction.deferred) {
|
|
137
|
+
this.logger?.warn(`Button ${interaction.customId} already handled, skipping`, 'component-manager');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const entry = this.findHandler(this.buttonHandlers, interaction.customId);
|
|
141
|
+
if (!entry) {
|
|
142
|
+
throw new Error(`No handler registered for button: ${interaction.customId}`);
|
|
143
|
+
}
|
|
144
|
+
const { handler, config, matchedId } = entry;
|
|
145
|
+
try {
|
|
146
|
+
await handler(interaction);
|
|
147
|
+
// Remove ephemeral handlers after use
|
|
148
|
+
if (config.ephemeral) {
|
|
149
|
+
this.unregisterButton(matchedId);
|
|
150
|
+
this.logger?.info(`Removed ephemeral button handler: ${matchedId}`, 'component-manager');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
this.logger?.error(`Error handling button ${interaction.customId}: ${err.message}`, 'component-manager');
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Handle a select menu interaction.
|
|
160
|
+
*
|
|
161
|
+
* Automatically matches dynamic IDs and removes ephemeral handlers after use.
|
|
162
|
+
*
|
|
163
|
+
* @param interaction - The select menu interaction to handle
|
|
164
|
+
* @throws {Error} If no handler is found for the select menu
|
|
165
|
+
*/
|
|
166
|
+
async handleSelect(interaction) {
|
|
167
|
+
if (interaction.replied || interaction.deferred) {
|
|
168
|
+
this.logger?.warn(`Select menu ${interaction.customId} already handled, skipping`, 'component-manager');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const entry = this.findHandler(this.selectHandlers, interaction.customId);
|
|
172
|
+
if (!entry) {
|
|
173
|
+
throw new Error(`No handler registered for select menu: ${interaction.customId}`);
|
|
174
|
+
}
|
|
175
|
+
const { handler, config, matchedId } = entry;
|
|
176
|
+
try {
|
|
177
|
+
await handler(interaction);
|
|
178
|
+
if (config.ephemeral) {
|
|
179
|
+
this.unregisterSelect(matchedId);
|
|
180
|
+
this.logger?.info(`Removed ephemeral select menu handler: ${matchedId}`, 'component-manager');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
this.logger?.error(`Error handling select menu ${interaction.customId}: ${err.message}`, 'component-manager');
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Find a handler by custom ID, supporting dynamic IDs.
|
|
190
|
+
*
|
|
191
|
+
* First tries exact match, then tries base ID (before colon).
|
|
192
|
+
*/
|
|
193
|
+
findHandler(map, customId) {
|
|
194
|
+
// Try exact match first
|
|
195
|
+
const exact = map.get(customId);
|
|
196
|
+
if (exact) {
|
|
197
|
+
return { ...exact, matchedId: customId };
|
|
198
|
+
}
|
|
199
|
+
// Try base ID (before colon) for dynamic IDs
|
|
200
|
+
const baseId = customId.split(':')[0];
|
|
201
|
+
const base = map.get(baseId);
|
|
202
|
+
if (base) {
|
|
203
|
+
return { ...base, matchedId: baseId };
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Set up automatic removal timeout for a handler.
|
|
209
|
+
*/
|
|
210
|
+
setupTimeout(type, customId, timeout) {
|
|
211
|
+
// Clear existing timeout if any
|
|
212
|
+
const existingTimeout = this.timeouts.get(`${type}:${customId}`);
|
|
213
|
+
if (existingTimeout) {
|
|
214
|
+
clearTimeout(existingTimeout);
|
|
215
|
+
}
|
|
216
|
+
// Set new timeout
|
|
217
|
+
const timeoutId = setTimeout(() => {
|
|
218
|
+
if (type === 'button') {
|
|
219
|
+
this.unregisterButton(customId);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.unregisterSelect(customId);
|
|
223
|
+
}
|
|
224
|
+
this.logger?.info(`Auto-removed ${type} handler after timeout: ${customId}`, 'component-manager');
|
|
225
|
+
}, timeout);
|
|
226
|
+
this.timeouts.set(`${type}:${customId}`, timeoutId);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Unregister a button handler.
|
|
230
|
+
*
|
|
231
|
+
* @param customId - The button's custom ID
|
|
232
|
+
* @returns true if the handler was removed, false if it didn't exist
|
|
233
|
+
*/
|
|
234
|
+
unregisterButton(customId) {
|
|
235
|
+
const removed = this.buttonHandlers.delete(customId);
|
|
236
|
+
// Clear timeout if exists
|
|
237
|
+
const timeoutId = this.timeouts.get(`button:${customId}`);
|
|
238
|
+
if (timeoutId) {
|
|
239
|
+
clearTimeout(timeoutId);
|
|
240
|
+
this.timeouts.delete(`button:${customId}`);
|
|
241
|
+
}
|
|
242
|
+
return removed;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Unregister a select menu handler.
|
|
246
|
+
*
|
|
247
|
+
* @param customId - The select menu's custom ID
|
|
248
|
+
* @returns true if the handler was removed, false if it didn't exist
|
|
249
|
+
*/
|
|
250
|
+
unregisterSelect(customId) {
|
|
251
|
+
const removed = this.selectHandlers.delete(customId);
|
|
252
|
+
const timeoutId = this.timeouts.get(`select:${customId}`);
|
|
253
|
+
if (timeoutId) {
|
|
254
|
+
clearTimeout(timeoutId);
|
|
255
|
+
this.timeouts.delete(`select:${customId}`);
|
|
256
|
+
}
|
|
257
|
+
return removed;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if a button handler is registered.
|
|
261
|
+
*
|
|
262
|
+
* @param customId - The button's custom ID
|
|
263
|
+
* @returns true if a handler exists (exact or base ID match)
|
|
264
|
+
*/
|
|
265
|
+
hasButton(customId) {
|
|
266
|
+
return this.buttonHandlers.has(customId) ||
|
|
267
|
+
this.buttonHandlers.has(customId.split(':')[0]);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Check if a select menu handler is registered.
|
|
271
|
+
*
|
|
272
|
+
* @param customId - The select menu's custom ID
|
|
273
|
+
* @returns true if a handler exists (exact or base ID match)
|
|
274
|
+
*/
|
|
275
|
+
hasSelect(customId) {
|
|
276
|
+
return this.selectHandlers.has(customId) ||
|
|
277
|
+
this.selectHandlers.has(customId.split(':')[0]);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Clear all registered handlers.
|
|
281
|
+
*
|
|
282
|
+
* Useful for cleanup during bot shutdown or testing.
|
|
283
|
+
*/
|
|
284
|
+
clear() {
|
|
285
|
+
this.buttonHandlers.clear();
|
|
286
|
+
this.selectHandlers.clear();
|
|
287
|
+
// Clear all timeouts
|
|
288
|
+
for (const timeoutId of this.timeouts.values()) {
|
|
289
|
+
clearTimeout(timeoutId);
|
|
290
|
+
}
|
|
291
|
+
this.timeouts.clear();
|
|
292
|
+
this.logger?.info('Cleared all component handlers', 'component-manager');
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get the total number of registered button handlers.
|
|
296
|
+
*/
|
|
297
|
+
get buttonCount() {
|
|
298
|
+
return this.buttonHandlers.size;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Get the total number of registered select menu handlers.
|
|
302
|
+
*/
|
|
303
|
+
get selectCount() {
|
|
304
|
+
return this.selectHandlers.size;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get all registered button IDs.
|
|
308
|
+
*/
|
|
309
|
+
getButtonIds() {
|
|
310
|
+
return Array.from(this.buttonHandlers.keys());
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get all registered select menu IDs.
|
|
314
|
+
*/
|
|
315
|
+
getSelectIds() {
|
|
316
|
+
return Array.from(this.selectHandlers.keys());
|
|
317
|
+
}
|
|
318
|
+
}
|
package/package.json
CHANGED