@minesa-org/mini-interaction 0.1.13 → 0.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.
@@ -1,25 +1,23 @@
1
1
  import type { APIActionRowComponent } from "discord-api-types/v10";
2
2
  import type { JSONEncodable } from "./shared.js";
3
- import type { MiniComponentActionRow } from "../types/ComponentTypes.js";
3
+ import type { ActionRowComponent } from "../types/ComponentTypes.js";
4
4
  /** Values accepted when composing component action rows. */
5
- export type ActionRowComponentLike<T extends MiniComponentActionRow> = JSONEncodable<T> | T;
6
- /** Builder for Discord action row components. */
7
- export declare class ActionRowBuilder<T extends MiniComponentActionRow = MiniComponentActionRow> implements JSONEncodable<APIActionRowComponent<T>> {
5
+ export type ActionRowComponentLike<T extends ActionRowComponent> = JSONEncodable<T> | T;
6
+ /** Builder for creating Action Row components. */
7
+ export declare class ActionRowBuilder<T extends ActionRowComponent> implements JSONEncodable<APIActionRowComponent<T>> {
8
8
  private components;
9
+ constructor(data?: Partial<APIActionRowComponent<T>>);
9
10
  /**
10
- * Creates a new action row builder with optional seed components.
11
+ * Adds components to this action row.
12
+ *
13
+ * @param components - The components to add (can be builders or raw objects).
11
14
  */
12
- constructor(components?: Iterable<ActionRowComponentLike<T>>);
15
+ addComponents(...components: (T | JSONEncodable<T>)[]): this;
13
16
  /**
14
- * Appends additional components to the action row.
15
- */
16
- addComponents(...components: ActionRowComponentLike<T>[]): this;
17
- /**
18
- * Replaces the current action row contents.
19
- */
20
- setComponents(components: Iterable<ActionRowComponentLike<T>>): this;
21
- /**
22
- * Serialises the builder into an API compatible action row payload.
17
+ * Sets the components for this action row, replacing any existing ones.
18
+ *
19
+ * @param components - The new components to set.
23
20
  */
21
+ setComponents(...components: (T | JSONEncodable<T>)[]): this;
24
22
  toJSON(): APIActionRowComponent<T>;
25
23
  }
@@ -1,35 +1,33 @@
1
1
  import { ComponentType } from "discord-api-types/v10";
2
2
  import { resolveJSONEncodable } from "./shared.js";
3
- /** Builder for Discord action row components. */
3
+ /** Builder for creating Action Row components. */
4
4
  export class ActionRowBuilder {
5
- components;
6
- /**
7
- * Creates a new action row builder with optional seed components.
8
- */
9
- constructor(components = []) {
10
- this.components = Array.from(components);
5
+ components = [];
6
+ constructor(data = {}) {
7
+ this.components = [...(data.components ?? [])];
11
8
  }
12
9
  /**
13
- * Appends additional components to the action row.
10
+ * Adds components to this action row.
11
+ *
12
+ * @param components - The components to add (can be builders or raw objects).
14
13
  */
15
14
  addComponents(...components) {
16
- this.components.push(...components);
15
+ this.components.push(...components.map((c) => resolveJSONEncodable(c)));
17
16
  return this;
18
17
  }
19
18
  /**
20
- * Replaces the current action row contents.
19
+ * Sets the components for this action row, replacing any existing ones.
20
+ *
21
+ * @param components - The new components to set.
21
22
  */
22
- setComponents(components) {
23
- this.components = Array.from(components);
23
+ setComponents(...components) {
24
+ this.components = components.map((c) => resolveJSONEncodable(c));
24
25
  return this;
25
26
  }
26
- /**
27
- * Serialises the builder into an API compatible action row payload.
28
- */
29
27
  toJSON() {
30
28
  return {
31
29
  type: ComponentType.ActionRow,
32
- components: this.components.map((component) => resolveJSONEncodable(component)),
30
+ components: [...this.components],
33
31
  };
34
32
  }
35
33
  }
@@ -1,12 +1,12 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { APIInteractionResponse, RESTPostAPIChatInputApplicationCommandsJSONBody, RESTPostAPIContextMenuApplicationCommandsJSONBody, RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody } from "discord-api-types/v10";
3
- import type { MiniInteractionCommand } from "../types/Commands.js";
3
+ import type { InteractionCommand } from "../types/Commands.js";
4
4
  import { RoleConnectionMetadataTypes } from "../types/RoleConnectionMetadataTypes.js";
5
5
  import { type MessageComponentInteraction, type ButtonInteraction, type StringSelectInteraction, type RoleSelectInteraction, type UserSelectInteraction, type ChannelSelectInteraction, type MentionableSelectInteraction } from "../utils/MessageComponentInteraction.js";
6
6
  import { type ModalSubmitInteraction } from "../utils/ModalSubmitInteraction.js";
7
7
  import { type OAuthConfig, type OAuthTokens, type DiscordUser } from "../oauth/DiscordOAuth.js";
8
8
  /** Configuration parameters for the MiniInteraction client. */
9
- export type MiniInteractionOptions = {
9
+ export type InteractionClientOptions = {
10
10
  applicationId: string;
11
11
  publicKey: string;
12
12
  commandsDirectory?: string | false;
@@ -26,13 +26,13 @@ export type RoleConnectionMetadataField = {
26
26
  /**
27
27
  * HTTP request information needed to validate and handle Discord interaction payloads.
28
28
  */
29
- export type MiniInteractionRequest = {
29
+ export type InteractionRequest = {
30
30
  body: string | Uint8Array;
31
31
  signature?: string;
32
32
  timestamp?: string;
33
33
  };
34
34
  /** Result payload returned by request handlers when processing an interaction. */
35
- export type MiniInteractionHandlerResult = {
35
+ export type InteractionHandlerResult = {
36
36
  status: number;
37
37
  body: APIInteractionResponse | {
38
38
  error: string;
@@ -46,25 +46,36 @@ export type InteractionTimeoutConfig = {
46
46
  enableTimeoutWarnings?: boolean;
47
47
  /** Whether to force deferReply for slow operations (default: true) */
48
48
  autoDeferSlowOperations?: boolean;
49
+ /** Whether to enable debug logging for interaction responses (default: false) */
50
+ enableResponseDebugLogging?: boolean;
51
+ };
52
+ /** Enhanced timeout configuration with response acknowledgment settings. */
53
+ export type InteractionTimeoutConfigV2 = InteractionTimeoutConfig & {
54
+ /** Time to wait for Discord acknowledgment after sending response (default: 500ms) */
55
+ responseAcknowledgmentTimeout?: number;
56
+ /** Maximum retries for response acknowledgment (default: 2) */
57
+ responseAcknowledgmentRetries?: number;
58
+ /** Delay between acknowledgment retries (default: 100ms) */
59
+ responseAcknowledgmentRetryDelay?: number;
49
60
  };
50
61
  /** Handler signature invoked for Discord button interactions. */
51
- export type MiniInteractionButtonHandler = (interaction: ButtonInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
62
+ export type ButtonComponentHandler = (interaction: ButtonInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
52
63
  /** Handler signature invoked for Discord string select menu interactions. */
53
- export type MiniInteractionStringSelectHandler = (interaction: StringSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
64
+ export type StringSelectComponentHandler = (interaction: StringSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
54
65
  /** Handler signature invoked for Discord role select menu interactions. */
55
- export type MiniInteractionRoleSelectHandler = (interaction: RoleSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
66
+ export type RoleSelectComponentHandler = (interaction: RoleSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
56
67
  /** Handler signature invoked for Discord user select menu interactions. */
57
- export type MiniInteractionUserSelectHandler = (interaction: UserSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
68
+ export type UserSelectComponentHandler = (interaction: UserSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
58
69
  /** Handler signature invoked for Discord channel select menu interactions. */
59
- export type MiniInteractionChannelSelectHandler = (interaction: ChannelSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
70
+ export type ChannelSelectComponentHandler = (interaction: ChannelSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
60
71
  /** Handler signature invoked for Discord mentionable select menu interactions. */
61
- export type MiniInteractionMentionableSelectHandler = (interaction: MentionableSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
72
+ export type MentionableSelectComponentHandler = (interaction: MentionableSelectInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
62
73
  /** Handler signature invoked for Discord message component interactions (generic). */
63
- export type MiniInteractionComponentHandler = (interaction: MessageComponentInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
74
+ export type ComponentHandler = (interaction: MessageComponentInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
64
75
  /** Handler signature invoked for Discord modal submit interactions. */
65
- export type MiniInteractionModalHandler = (interaction: ModalSubmitInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
76
+ export type ModalHandler = (interaction: ModalSubmitInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
66
77
  /** Unified handler signature that accepts any component or modal interaction. */
67
- export type MiniInteractionHandler = MiniInteractionButtonHandler | MiniInteractionStringSelectHandler | MiniInteractionRoleSelectHandler | MiniInteractionUserSelectHandler | MiniInteractionChannelSelectHandler | MiniInteractionMentionableSelectHandler | MiniInteractionComponentHandler | MiniInteractionModalHandler;
78
+ export type InteractionHandler = ButtonComponentHandler | StringSelectComponentHandler | RoleSelectComponentHandler | UserSelectComponentHandler | ChannelSelectComponentHandler | MentionableSelectComponentHandler | ComponentHandler | ModalHandler;
68
79
  type CommandDataPayload = RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIContextMenuApplicationCommandsJSONBody | RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody;
69
80
  /**
70
81
  * Structure describing a component or modal handler mapped to a custom id.
@@ -73,19 +84,19 @@ type CommandDataPayload = RESTPostAPIChatInputApplicationCommandsJSONBody | REST
73
84
  * - Other files are treated as component handlers
74
85
  * You can use this type for both - the system will figure out which one it is.
75
86
  */
76
- export type MiniInteractionComponent = {
87
+ export type ComponentCommand = {
77
88
  customId: string;
78
- handler: MiniInteractionHandler;
89
+ handler: InteractionHandler;
79
90
  };
80
91
  /** Structure describing a modal handler mapped to a custom id. */
81
- export type MiniInteractionModal = {
92
+ export type ModalCommand = {
82
93
  customId: string;
83
- handler: MiniInteractionModalHandler;
94
+ handler: ModalHandler;
84
95
  };
85
96
  /** Node.js HTTP handler compatible with frameworks like Express or Next.js API routes. */
86
- export type MiniInteractionNodeHandler = (request: IncomingMessage, response: ServerResponse) => void;
97
+ export type InteractionNodeHandler = (request: IncomingMessage, response: ServerResponse) => void;
87
98
  /** Web Fetch API compatible request handler for platforms such as Cloudflare Workers. */
88
- export type MiniInteractionFetchHandler = (request: Request) => Promise<Response>;
99
+ export type InteractionFetchHandler = (request: Request) => Promise<Response>;
89
100
  /** Context passed to OAuth success handlers and templates. */
90
101
  export type DiscordOAuthAuthorizeContext = {
91
102
  tokens: OAuthTokens;
@@ -162,6 +173,7 @@ export declare class MiniInteraction {
162
173
  private readonly componentHandlers;
163
174
  private readonly modalHandlers;
164
175
  private readonly htmlTemplateCache;
176
+ private readonly interactionStates;
165
177
  private commandsLoaded;
166
178
  private loadCommandsPromise;
167
179
  private componentsLoaded;
@@ -171,7 +183,52 @@ export declare class MiniInteraction {
171
183
  /**
172
184
  * Creates a new MiniInteraction client with optional command auto-loading and custom runtime hooks.
173
185
  */
174
- constructor({ applicationId, publicKey, commandsDirectory, componentsDirectory, utilsDirectory, fetchImplementation, verifyKeyImplementation, timeoutConfig, }: MiniInteractionOptions);
186
+ constructor({ applicationId, publicKey, commandsDirectory, componentsDirectory, utilsDirectory, fetchImplementation, verifyKeyImplementation, timeoutConfig, }: InteractionClientOptions);
187
+ /**
188
+ * Tracks the state of an interaction to prevent race conditions and double responses.
189
+ */
190
+ private trackInteractionState;
191
+ /**
192
+ * Checks if an interaction can still respond (not expired and not already responded).
193
+ */
194
+ private canRespond;
195
+ /**
196
+ * Logs response timing and acknowledgment for debugging.
197
+ */
198
+ private logResponseTiming;
199
+ /**
200
+ * Simulates waiting for Discord acknowledgment to help debug timing issues.
201
+ * Note: This is a best-effort simulation since actual acknowledgment happens at the HTTP level.
202
+ */
203
+ private simulateAcknowledgmentWait;
204
+ /**
205
+ * Enables or disables debug logging for interaction responses and timing.
206
+ * Useful for troubleshooting "didn't respond in time" errors.
207
+ *
208
+ * @param enabled - Whether to enable debug logging
209
+ */
210
+ setResponseDebugLogging(enabled: boolean): void;
211
+ /**
212
+ * Gets the current state of an interaction.
213
+ */
214
+ private getInteractionState;
215
+ /**
216
+ * Gets the current state of an interaction for debugging purposes.
217
+ *
218
+ * @param interactionId - The interaction ID to check
219
+ * @returns The current interaction state or null if not found
220
+ */
221
+ getInteractionStateInfo(interactionId: string): {
222
+ state: "pending" | "deferred" | "responded" | "expired";
223
+ timestamp: number;
224
+ token: string;
225
+ responseCount: number;
226
+ } | null;
227
+ /**
228
+ * Clears expired interaction states to prevent memory leaks.
229
+ * Call this periodically to clean up old interaction data.
230
+ */
231
+ cleanupExpiredInteractions(): number;
175
232
  private normalizeCommandData;
176
233
  private registerCommand;
177
234
  /**
@@ -179,37 +236,37 @@ export declare class MiniInteraction {
179
236
  *
180
237
  * @param command - The command definition to register.
181
238
  */
182
- useCommand(command: MiniInteractionCommand): this;
239
+ useCommand(command: InteractionCommand): this;
183
240
  /**
184
241
  * Registers multiple command handlers with the client.
185
242
  *
186
243
  * @param commands - The command definitions to register.
187
244
  */
188
- useCommands(commands: MiniInteractionCommand[]): this;
245
+ useCommands(commands: InteractionCommand[]): this;
189
246
  /**
190
247
  * Registers a single component handler mapped to a custom identifier.
191
248
  *
192
249
  * @param component - The component definition to register.
193
250
  */
194
- useComponent(component: MiniInteractionComponent): this;
251
+ useComponent(component: ComponentCommand): this;
195
252
  /**
196
253
  * Registers multiple component handlers in a single call.
197
254
  *
198
255
  * @param components - The component definitions to register.
199
256
  */
200
- useComponents(components: MiniInteractionComponent[]): this;
257
+ useComponents(components: ComponentCommand[]): this;
201
258
  /**
202
259
  * Registers a single modal handler mapped to a custom identifier.
203
260
  *
204
261
  * @param modal - The modal definition to register.
205
262
  */
206
- useModal(modal: MiniInteractionModal): this;
263
+ useModal(modal: ModalCommand): this;
207
264
  /**
208
265
  * Registers multiple modal handlers in a single call.
209
266
  *
210
267
  * @param modals - The modal definitions to register.
211
268
  */
212
- useModals(modals: MiniInteractionModal[]): this;
269
+ useModals(modals: ModalCommand[]): this;
213
270
  /**
214
271
  * Recursively loads components from the configured components directory.
215
272
  *
@@ -245,12 +302,12 @@ export declare class MiniInteraction {
245
302
  *
246
303
  * @param request - The request payload containing headers and body data.
247
304
  */
248
- handleRequest(request: MiniInteractionRequest): Promise<MiniInteractionHandlerResult>;
305
+ handleRequest(request: InteractionRequest): Promise<InteractionHandlerResult>;
249
306
  /**
250
307
  * Creates a Node.js style request handler compatible with Express, Next.js API routes,
251
308
  * Vercel serverless functions, and any runtime that expects a `(req, res)` listener.
252
309
  */
253
- createNodeHandler(): MiniInteractionNodeHandler;
310
+ createNodeHandler(): InteractionNodeHandler;
254
311
  /**
255
312
  * Generates a lightweight verification handler that serves an HTML page with an embedded OAuth link.
256
313
  *
@@ -264,7 +321,7 @@ export declare class MiniInteraction {
264
321
  * - `{{OAUTH_STATE}}` - HTML-escaped OAuth state value.
265
322
  * - Custom placeholder name (from {@link DiscordOAuthVerificationPageOptions.placeholder}) and its `_RAW` variant.
266
323
  */
267
- discordOAuthVerificationPage(options?: DiscordOAuthVerificationPageOptions): MiniInteractionNodeHandler;
324
+ discordOAuthVerificationPage(options?: DiscordOAuthVerificationPageOptions): InteractionNodeHandler;
268
325
  /**
269
326
  * Loads an HTML file and returns a success template that replaces useful placeholders.
270
327
  *
@@ -300,11 +357,11 @@ export declare class MiniInteraction {
300
357
  * This helper keeps the user-side implementation tiny while still exposing hooks for
301
358
  * storing metadata or validating the OAuth state value.
302
359
  */
303
- discordOAuthCallback(options: DiscordOAuthCallbackOptions): MiniInteractionNodeHandler;
360
+ discordOAuthCallback(options: DiscordOAuthCallbackOptions): InteractionNodeHandler;
304
361
  /**
305
362
  * Creates a Fetch API compatible handler for runtimes like Workers or Deno.
306
363
  */
307
- createFetchHandler(): MiniInteractionFetchHandler;
364
+ createFetchHandler(): InteractionFetchHandler;
308
365
  /**
309
366
  * Checks if the provided directory path exists on disk.
310
367
  */
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { ApplicationCommandType, InteractionResponseType, InteractionType, } from "discord-api-types/v10";
6
6
  import { verifyKey } from "discord-interactions";
7
+ import { resolveJSONEncodable } from "../builders/shared.js";
7
8
  import { DISCORD_BASE_URL } from "../utils/constants.js";
8
9
  import { createCommandInteraction } from "../utils/CommandInteractionOptions.js";
9
10
  import { createMessageComponentInteraction, } from "../utils/MessageComponentInteraction.js";
@@ -35,6 +36,7 @@ export class MiniInteraction {
35
36
  componentHandlers = new Map();
36
37
  modalHandlers = new Map();
37
38
  htmlTemplateCache = new Map();
39
+ interactionStates = new Map();
38
40
  commandsLoaded = false;
39
41
  loadCommandsPromise = null;
40
42
  componentsLoaded = false;
@@ -75,15 +77,131 @@ export class MiniInteraction {
75
77
  initialResponseTimeout: 2800, // Leave 200ms buffer before Discord's 3s limit
76
78
  enableTimeoutWarnings: true,
77
79
  autoDeferSlowOperations: true,
80
+ enableResponseDebugLogging: false,
81
+ responseAcknowledgmentTimeout: 500, // 500ms to wait for acknowledgment
82
+ responseAcknowledgmentRetries: 2,
83
+ responseAcknowledgmentRetryDelay: 100,
78
84
  ...timeoutConfig,
79
85
  };
80
86
  }
87
+ /**
88
+ * Tracks the state of an interaction to prevent race conditions and double responses.
89
+ */
90
+ trackInteractionState(interactionId, token, state) {
91
+ const existing = this.interactionStates.get(interactionId);
92
+ const now = Date.now();
93
+ this.interactionStates.set(interactionId, {
94
+ state,
95
+ timestamp: now,
96
+ token,
97
+ responseCount: existing ? existing.responseCount + 1 : 1,
98
+ });
99
+ if (this.timeoutConfig.enableResponseDebugLogging) {
100
+ console.log(`[MiniInteraction:DEBUG] Interaction ${interactionId} state: ${state} (${existing ? existing.responseCount + 1 : 1} responses)`);
101
+ }
102
+ }
103
+ /**
104
+ * Checks if an interaction can still respond (not expired and not already responded).
105
+ */
106
+ canRespond(interactionId) {
107
+ const state = this.getInteractionState(interactionId);
108
+ if (!state)
109
+ return true; // New interaction
110
+ // Check if expired (15 minutes)
111
+ if (Date.now() - state.timestamp > 900000) {
112
+ this.trackInteractionState(interactionId, state.token, 'expired');
113
+ return false;
114
+ }
115
+ // Check if already responded
116
+ if (state.state === 'responded') {
117
+ return false;
118
+ }
119
+ return true;
120
+ }
121
+ /**
122
+ * Logs response timing and acknowledgment for debugging.
123
+ */
124
+ logResponseTiming(interactionId, operation, startTime, success) {
125
+ if (!this.timeoutConfig.enableResponseDebugLogging)
126
+ return;
127
+ const elapsed = Date.now() - startTime;
128
+ const status = success ? 'SUCCESS' : 'FAILED';
129
+ console.log(`[MiniInteraction:DEBUG] ${operation} for interaction ${interactionId}: ${status} (${elapsed}ms)`);
130
+ if (success && elapsed > 2000) {
131
+ console.warn(`[MiniInteraction:WARN] ${operation} took ${elapsed}ms - consider using deferReply() for slow operations`);
132
+ }
133
+ }
134
+ /**
135
+ * Simulates waiting for Discord acknowledgment to help debug timing issues.
136
+ * Note: This is a best-effort simulation since actual acknowledgment happens at the HTTP level.
137
+ */
138
+ async simulateAcknowledgmentWait(interactionId, operation) {
139
+ if (!this.timeoutConfig.enableResponseDebugLogging)
140
+ return;
141
+ const timeout = this.timeoutConfig.responseAcknowledgmentTimeout || 500;
142
+ const retries = this.timeoutConfig.responseAcknowledgmentRetries || 2;
143
+ const retryDelay = this.timeoutConfig.responseAcknowledgmentRetryDelay || 100;
144
+ console.log(`[MiniInteraction:DEBUG] Waiting for acknowledgment of ${operation} for interaction ${interactionId}...`);
145
+ for (let attempt = 0; attempt <= retries; attempt++) {
146
+ await new Promise(resolve => setTimeout(resolve, attempt === 0 ? timeout : retryDelay));
147
+ // In a real implementation, this would verify Discord actually received the response
148
+ // For now, we just simulate the wait time
149
+ const state = this.getInteractionState(interactionId);
150
+ if (state?.state === 'responded' || state?.state === 'deferred') {
151
+ console.log(`[MiniInteraction:DEBUG] Acknowledgment confirmed for ${operation} after ${attempt + 1} attempts`);
152
+ return;
153
+ }
154
+ }
155
+ console.warn(`[MiniInteraction:WARN] Acknowledgment timeout for ${operation} on interaction ${interactionId}`);
156
+ }
157
+ /**
158
+ * Enables or disables debug logging for interaction responses and timing.
159
+ * Useful for troubleshooting "didn't respond in time" errors.
160
+ *
161
+ * @param enabled - Whether to enable debug logging
162
+ */
163
+ setResponseDebugLogging(enabled) {
164
+ this.timeoutConfig.enableResponseDebugLogging = enabled;
165
+ console.log(`[MiniInteraction] Response debug logging ${enabled ? 'enabled' : 'disabled'}`);
166
+ }
167
+ /**
168
+ * Gets the current state of an interaction.
169
+ */
170
+ getInteractionState(interactionId) {
171
+ return this.interactionStates.get(interactionId);
172
+ }
173
+ /**
174
+ * Gets the current state of an interaction for debugging purposes.
175
+ *
176
+ * @param interactionId - The interaction ID to check
177
+ * @returns The current interaction state or null if not found
178
+ */
179
+ getInteractionStateInfo(interactionId) {
180
+ const state = this.interactionStates.get(interactionId);
181
+ return state ? { ...state } : null; // Return a copy to prevent external modification
182
+ }
183
+ /**
184
+ * Clears expired interaction states to prevent memory leaks.
185
+ * Call this periodically to clean up old interaction data.
186
+ */
187
+ cleanupExpiredInteractions() {
188
+ const now = Date.now();
189
+ let cleaned = 0;
190
+ for (const [id, state] of this.interactionStates.entries()) {
191
+ // Remove interactions older than 15 minutes
192
+ if (now - state.timestamp > 900000) {
193
+ this.interactionStates.delete(id);
194
+ cleaned++;
195
+ }
196
+ }
197
+ if (cleaned > 0) {
198
+ console.log(`[MiniInteraction] Cleaned up ${cleaned} expired interactions`);
199
+ }
200
+ return cleaned;
201
+ }
81
202
  normalizeCommandData(data) {
82
203
  if (typeof data === "object" && data !== null) {
83
- const toJSON = data.toJSON;
84
- if (typeof toJSON === "function") {
85
- return toJSON.call(data);
86
- }
204
+ return resolveJSONEncodable(data);
87
205
  }
88
206
  return data;
89
207
  }
@@ -400,23 +518,34 @@ export class MiniInteraction {
400
518
  };
401
519
  }
402
520
  if (interaction.type === InteractionType.ApplicationCommand) {
521
+ // Track interaction start
522
+ this.trackInteractionState(interaction.id, interaction.token, 'pending');
403
523
  return this.handleApplicationCommand(interaction);
404
524
  }
405
525
  if (interaction.type === InteractionType.MessageComponent) {
526
+ // Track interaction start
527
+ this.trackInteractionState(interaction.id, interaction.token, 'pending');
406
528
  return this.handleMessageComponent(interaction);
407
529
  }
408
530
  if (interaction.type === InteractionType.ModalSubmit) {
531
+ // Track interaction start
532
+ this.trackInteractionState(interaction.id, interaction.token, 'pending');
409
533
  return this.handleModalSubmit(interaction);
410
534
  }
411
- // Check total processing time
535
+ // Check total processing time and log potential timeout issues
412
536
  const totalProcessingTime = Date.now() - requestStartTime;
413
537
  if (this.timeoutConfig.enableTimeoutWarnings &&
414
538
  totalProcessingTime >
415
539
  this.timeoutConfig.initialResponseTimeout * 0.9) {
416
- console.warn(`[MiniInteraction] WARNING: Interaction processing took ${totalProcessingTime}ms ` +
540
+ console.warn(`[MiniInteraction] CRITICAL: Interaction processing took ${totalProcessingTime}ms ` +
417
541
  `(${Math.round((totalProcessingTime / 3000) * 100)}% of Discord's 3-second limit). ` +
542
+ `This may cause "didn't respond in time" errors. ` +
418
543
  `Consider optimizing or using deferReply() for slow operations.`);
419
544
  }
545
+ // Log successful response timing for debugging
546
+ if (this.timeoutConfig.enableResponseDebugLogging) {
547
+ console.log(`[MiniInteraction:DEBUG] Request completed in ${totalProcessingTime}ms`);
548
+ }
420
549
  return {
421
550
  status: 400,
422
551
  body: {
@@ -1195,7 +1324,11 @@ export class MiniInteraction {
1195
1324
  // Check if it's a chat input (slash) command
1196
1325
  if (commandInteraction.data.type ===
1197
1326
  ApplicationCommandType.ChatInput) {
1198
- const interactionWithHelpers = createCommandInteraction(commandInteraction);
1327
+ const interactionWithHelpers = createCommandInteraction(commandInteraction, {
1328
+ canRespond: (id) => this.canRespond(id),
1329
+ trackResponse: (id, token, state) => this.trackInteractionState(id, token, state),
1330
+ logTiming: (id, op, start, success) => this.logResponseTiming(id, op, start, success),
1331
+ });
1199
1332
  response = await command.handler(interactionWithHelpers);
1200
1333
  resolvedResponse =
1201
1334
  response ?? interactionWithHelpers.getResponse();
package/dist/index.d.ts CHANGED
@@ -5,21 +5,21 @@ export { CommandBuilder, CommandContext, IntegrationType, } from "./commands/Com
5
5
  export { UserCommandBuilder, MessageCommandBuilder, AppCommandBuilder, } from "./commands/ContextMenuCommandBuilder.js";
6
6
  export type { AttachmentOptionBuilder, ChannelOptionBuilder, MentionableOptionBuilder, NumberOptionBuilder, RoleOptionBuilder, StringOptionBuilder, SubcommandBuilder, SubcommandGroupBuilder, UserOptionBuilder, } from "./commands/CommandBuilder.js";
7
7
  export { CommandInteractionOptionResolver, createCommandInteraction, } from "./utils/CommandInteractionOptions.js";
8
- export type { CommandInteraction, MentionableOption, ResolvedUserOption, } from "./utils/CommandInteractionOptions.js";
9
- export type { UserContextMenuInteraction, MessageContextMenuInteraction, AppCommandInteraction, } from "./utils/ContextMenuInteraction.js";
10
- export type { MiniInteractionFetchHandler, MiniInteractionNodeHandler, MiniInteractionHandlerResult, MiniInteractionRequest, MiniInteractionOptions, DiscordOAuthAuthorizeContext, DiscordOAuthCallbackOptions, DiscordOAuthCallbackTemplates, DiscordOAuthErrorTemplateContext, DiscordOAuthServerErrorTemplateContext, DiscordOAuthStateTemplateContext, DiscordOAuthSuccessTemplateContext, DiscordOAuthVerificationPageOptions, } from "./clients/MiniInteraction.js";
11
- export type { MiniInteractionCommand, SlashCommandHandler, UserCommandHandler, MessageCommandHandler, AppCommandHandler, CommandHandler, } from "./types/Commands.js";
12
- export type { MiniInteractionComponent, MiniInteractionButtonHandler, MiniInteractionStringSelectHandler, MiniInteractionRoleSelectHandler, MiniInteractionUserSelectHandler, MiniInteractionChannelSelectHandler, MiniInteractionMentionableSelectHandler, MiniInteractionComponentHandler, MiniInteractionModal, MiniInteractionModalHandler, MiniInteractionHandler, } from "./clients/MiniInteraction.js";
13
- export type { MessageComponentInteraction, ButtonInteraction, StringSelectInteraction, RoleSelectInteraction, UserSelectInteraction, ChannelSelectInteraction, MentionableSelectInteraction, ResolvedUserOption as ComponentResolvedUserOption, ResolvedMentionableOption as ComponentResolvedMentionableOption, } from "./utils/MessageComponentInteraction.js";
14
- export type { ModalSubmitInteraction } from "./utils/ModalSubmitInteraction.js";
8
+ export { CommandInteraction, MentionableOption, ResolvedUserOption, } from "./utils/CommandInteractionOptions.js";
9
+ export { UserContextMenuInteraction, MessageContextMenuInteraction, AppCommandInteraction, } from "./utils/ContextMenuInteraction.js";
10
+ export type { InteractionFetchHandler, InteractionNodeHandler, InteractionHandlerResult, InteractionRequest, InteractionClientOptions, DiscordOAuthAuthorizeContext, DiscordOAuthCallbackOptions, DiscordOAuthCallbackTemplates, DiscordOAuthErrorTemplateContext, DiscordOAuthServerErrorTemplateContext, DiscordOAuthStateTemplateContext, DiscordOAuthSuccessTemplateContext, DiscordOAuthVerificationPageOptions, } from "./clients/MiniInteraction.js";
11
+ export type { InteractionCommand, SlashCommandHandler, UserCommandHandler, MessageCommandHandler, AppCommandHandler, CommandHandler, } from "./types/Commands.js";
12
+ export type { ComponentCommand, ButtonComponentHandler, StringSelectComponentHandler, RoleSelectComponentHandler, UserSelectComponentHandler, ChannelSelectComponentHandler, MentionableSelectComponentHandler, ComponentHandler, ModalCommand, ModalHandler, InteractionHandler, } from "./clients/MiniInteraction.js";
13
+ export { MessageComponentInteraction, ButtonInteraction, StringSelectInteraction, RoleSelectInteraction, UserSelectInteraction, ChannelSelectInteraction, MentionableSelectInteraction, ResolvedUserOption as ComponentResolvedUserOption, ResolvedMentionableOption as ComponentResolvedMentionableOption, } from "./utils/MessageComponentInteraction.js";
14
+ export { ModalSubmitInteraction } from "./utils/ModalSubmitInteraction.js";
15
15
  export { RoleConnectionMetadataTypes } from "./types/RoleConnectionMetadataTypes.js";
16
16
  export { ChannelType } from "./types/ChannelType.js";
17
- export { InteractionFollowUpFlags, InteractionReplyFlags, } from "./types/InteractionFlags.js";
17
+ export { InteractionFollowUpFlags, InteractionReplyFlags, InteractionFlags, } from "./types/InteractionFlags.js";
18
18
  export { ButtonStyle } from "./types/ButtonStyle.js";
19
19
  export { SeparatorSpacingSize } from "./types/SeparatorSpacingSize.js";
20
20
  export { TextInputStyle } from "discord-api-types/v10";
21
21
  export { MiniPermFlags } from "./types/PermissionFlags.js";
22
- export type { MiniComponentActionRow, MiniComponentMessageActionRow, } from "./types/ComponentTypes.js";
22
+ export type { ActionRowComponent, MessageActionRowComponent, } from "./types/ComponentTypes.js";
23
23
  export * from "./builders/index.js";
24
24
  export { MiniDataBuilder } from "./database/MiniDataBuilder.js";
25
25
  export type { DataField } from "./database/MiniDataBuilder.js";
package/dist/index.js CHANGED
@@ -3,9 +3,13 @@ export { MiniInteraction } from "./clients/MiniInteraction.js";
3
3
  export { CommandBuilder, CommandContext, IntegrationType, } from "./commands/CommandBuilder.js";
4
4
  export { UserCommandBuilder, MessageCommandBuilder, AppCommandBuilder, } from "./commands/ContextMenuCommandBuilder.js";
5
5
  export { CommandInteractionOptionResolver, createCommandInteraction, } from "./utils/CommandInteractionOptions.js";
6
+ export { CommandInteraction, MentionableOption, ResolvedUserOption, } from "./utils/CommandInteractionOptions.js";
7
+ export { UserContextMenuInteraction, MessageContextMenuInteraction, AppCommandInteraction, } from "./utils/ContextMenuInteraction.js";
8
+ export { MessageComponentInteraction, ButtonInteraction, StringSelectInteraction, RoleSelectInteraction, UserSelectInteraction, ChannelSelectInteraction, MentionableSelectInteraction, ResolvedUserOption as ComponentResolvedUserOption, ResolvedMentionableOption as ComponentResolvedMentionableOption, } from "./utils/MessageComponentInteraction.js";
9
+ export { ModalSubmitInteraction } from "./utils/ModalSubmitInteraction.js";
6
10
  export { RoleConnectionMetadataTypes } from "./types/RoleConnectionMetadataTypes.js";
7
11
  export { ChannelType } from "./types/ChannelType.js";
8
- export { InteractionFollowUpFlags, InteractionReplyFlags, } from "./types/InteractionFlags.js";
12
+ export { InteractionFollowUpFlags, InteractionReplyFlags, InteractionFlags, } from "./types/InteractionFlags.js";
9
13
  export { ButtonStyle } from "./types/ButtonStyle.js";
10
14
  export { SeparatorSpacingSize } from "./types/SeparatorSpacingSize.js";
11
15
  export { TextInputStyle } from "discord-api-types/v10";
@@ -1,7 +1,8 @@
1
1
  import type { APIInteractionResponse, RESTPostAPIChatInputApplicationCommandsJSONBody, RESTPostAPIContextMenuApplicationCommandsJSONBody, RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody } from "discord-api-types/v10";
2
2
  import type { CommandInteraction } from "../utils/CommandInteractionOptions.js";
3
3
  import type { UserContextMenuInteraction, MessageContextMenuInteraction, AppCommandInteraction } from "../utils/ContextMenuInteraction.js";
4
- import type { MiniInteractionComponent, MiniInteractionModal } from "../clients/MiniInteraction.js";
4
+ import type { JSONEncodable } from "../builders/shared.js";
5
+ import type { ComponentCommand, ModalCommand } from "../clients/MiniInteraction.js";
5
6
  import type { CommandBuilder } from "../commands/CommandBuilder.js";
6
7
  import type { MessageCommandBuilder, UserCommandBuilder, AppCommandBuilder } from "../commands/ContextMenuCommandBuilder.js";
7
8
  /** Handler signature for slash command executions within MiniInteraction. */
@@ -15,19 +16,19 @@ export type AppCommandHandler = (interaction: AppCommandInteraction) => Promise<
15
16
  /** Union of all command handler types. */
16
17
  export type CommandHandler = SlashCommandHandler | UserCommandHandler | MessageCommandHandler | AppCommandHandler;
17
18
  /** Structure representing a slash command definition and its runtime handler. */
18
- export type MiniInteractionCommand = {
19
- data: RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIContextMenuApplicationCommandsJSONBody | RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody | CommandBuilder | UserCommandBuilder | MessageCommandBuilder | AppCommandBuilder;
19
+ export type InteractionCommand = {
20
+ data: RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIContextMenuApplicationCommandsJSONBody | RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody | CommandBuilder | UserCommandBuilder | MessageCommandBuilder | AppCommandBuilder | JSONEncodable<RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIContextMenuApplicationCommandsJSONBody | RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody>;
20
21
  handler: CommandHandler;
21
22
  /**
22
23
  * Optional array of component handlers related to this command.
23
24
  * These will be automatically registered when the command is loaded.
24
25
  */
25
- components?: MiniInteractionComponent[];
26
+ components?: ComponentCommand[];
26
27
  /**
27
28
  * Optional array of modal handlers related to this command.
28
29
  * These will be automatically registered when the command is loaded.
29
30
  */
30
- modals?: MiniInteractionModal[];
31
+ modals?: ModalCommand[];
31
32
  };
32
33
  /** Map of command names to their registered MiniInteraction command definitions. */
33
- export type MiniInteractionCommandsMap = Map<string, MiniInteractionCommand>;
34
+ export type InteractionCommandsMap = Map<string, InteractionCommand>;
@@ -1,5 +1,5 @@
1
1
  import type { APIComponentInActionRow, APIComponentInMessageActionRow } from "discord-api-types/v10";
2
- /** * Components that can appear inside a Discord action row container. */
3
- export type MiniComponentMessageActionRow = APIComponentInMessageActionRow;
4
- /** Components that are valid inside message action rows. */
5
- export type MiniComponentActionRow = APIComponentInActionRow;
2
+ /** Defines a component structure for use in ActionRow builders. */
3
+ export type ActionRowComponent = APIComponentInActionRow;
4
+ /** Defines a message component structure for use in message builders. */
5
+ export type MessageActionRowComponent = APIComponentInMessageActionRow;
@@ -1,10 +1,9 @@
1
- /** Flags available when responding directly to an interaction. */
2
- export declare enum InteractionReplyFlags {
3
- Ephemeral = 64,
4
- IsComponentsV2 = 32768
5
- }
6
- /** Flags available when sending a follow-up message for an interaction. */
7
- export declare enum InteractionFollowUpFlags {
1
+ /** Flags available for interaction responses. */
2
+ export declare enum InteractionFlags {
8
3
  Ephemeral = 64,
9
4
  IsComponentsV2 = 32768
10
5
  }
6
+ /** @deprecated Use InteractionFlags instead. */
7
+ export { InteractionFlags as InteractionReplyFlags };
8
+ /** @deprecated Use InteractionFlags instead. */
9
+ export { InteractionFlags as InteractionFollowUpFlags };
@@ -1,12 +1,10 @@
1
- /** Flags available when responding directly to an interaction. */
2
- export var InteractionReplyFlags;
3
- (function (InteractionReplyFlags) {
4
- InteractionReplyFlags[InteractionReplyFlags["Ephemeral"] = 64] = "Ephemeral";
5
- InteractionReplyFlags[InteractionReplyFlags["IsComponentsV2"] = 32768] = "IsComponentsV2";
6
- })(InteractionReplyFlags || (InteractionReplyFlags = {}));
7
- /** Flags available when sending a follow-up message for an interaction. */
8
- export var InteractionFollowUpFlags;
9
- (function (InteractionFollowUpFlags) {
10
- InteractionFollowUpFlags[InteractionFollowUpFlags["Ephemeral"] = 64] = "Ephemeral";
11
- InteractionFollowUpFlags[InteractionFollowUpFlags["IsComponentsV2"] = 32768] = "IsComponentsV2";
12
- })(InteractionFollowUpFlags || (InteractionFollowUpFlags = {}));
1
+ /** Flags available for interaction responses. */
2
+ export var InteractionFlags;
3
+ (function (InteractionFlags) {
4
+ InteractionFlags[InteractionFlags["Ephemeral"] = 64] = "Ephemeral";
5
+ InteractionFlags[InteractionFlags["IsComponentsV2"] = 32768] = "IsComponentsV2";
6
+ })(InteractionFlags || (InteractionFlags = {}));
7
+ /** @deprecated Use InteractionFlags instead. */
8
+ export { InteractionFlags as InteractionReplyFlags };
9
+ /** @deprecated Use InteractionFlags instead. */
10
+ export { InteractionFlags as InteractionFollowUpFlags };
@@ -4,6 +4,7 @@ export type ResolvedUserOption = {
4
4
  user: APIUser;
5
5
  member?: APIInteractionDataResolvedGuildMember;
6
6
  };
7
+ export declare const ResolvedUserOption: {};
7
8
  export type MentionableOption = {
8
9
  type: "user";
9
10
  value: ResolvedUserOption;
@@ -11,6 +12,7 @@ export type MentionableOption = {
11
12
  type: "role";
12
13
  value: APIRole;
13
14
  };
15
+ export declare const MentionableOption: {};
14
16
  /**
15
17
  * Provides ergonomic helpers for extracting typed command options from Discord interactions.
16
18
  */
@@ -150,11 +152,20 @@ export interface CommandInteraction extends Omit<APIChatInputApplicationCommandI
150
152
  toJSON(): APIModalInteractionResponseCallbackData;
151
153
  }): APIModalInteractionResponse;
152
154
  withTimeoutProtection<T>(operation: () => Promise<T>, deferOptions?: DeferReplyOptions): Promise<T>;
155
+ canRespond?(interactionId: string): boolean;
156
+ trackResponse?(interactionId: string, token: string, state: 'responded' | 'deferred'): void;
157
+ logTiming?(interactionId: string, operation: string, startTime: number, success: boolean): void;
153
158
  }
159
+ export declare const CommandInteraction: {};
154
160
  /**
155
161
  * Wraps a raw application command interaction with helper methods and option resolvers.
156
162
  *
157
163
  * @param interaction - The raw interaction payload from Discord.
164
+ * @param helpers - Optional helper methods for state management and logging.
158
165
  * @returns A helper-augmented interaction object.
159
166
  */
160
- export declare function createCommandInteraction(interaction: APIChatInputApplicationCommandInteraction): CommandInteraction;
167
+ export declare function createCommandInteraction(interaction: APIChatInputApplicationCommandInteraction, helpers?: {
168
+ canRespond?: (interactionId: string) => boolean;
169
+ trackResponse?: (interactionId: string, token: string, state: 'responded' | 'deferred') => void;
170
+ logTiming?: (interactionId: string, operation: string, startTime: number, success: boolean) => void;
171
+ }): CommandInteraction;
@@ -1,5 +1,7 @@
1
1
  import { ApplicationCommandOptionType, InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const ResolvedUserOption = {};
4
+ export const MentionableOption = {};
3
5
  /** Maps application command option types to human-readable labels for error messages. */
4
6
  const OPTION_TYPE_LABEL = {
5
7
  [ApplicationCommandOptionType.Subcommand]: "subcommand",
@@ -314,13 +316,15 @@ export class CommandInteractionOptionResolver {
314
316
  return records?.[id];
315
317
  }
316
318
  }
319
+ export const CommandInteraction = {};
317
320
  /**
318
321
  * Wraps a raw application command interaction with helper methods and option resolvers.
319
322
  *
320
323
  * @param interaction - The raw interaction payload from Discord.
324
+ * @param helpers - Optional helper methods for state management and logging.
321
325
  * @returns A helper-augmented interaction object.
322
326
  */
323
- export function createCommandInteraction(interaction) {
327
+ export function createCommandInteraction(interaction, helpers) {
324
328
  const options = new CommandInteractionOptionResolver(interaction.data.options, interaction.data.resolved);
325
329
  let capturedResponse = null;
326
330
  /**
@@ -371,7 +375,17 @@ export function createCommandInteraction(interaction) {
371
375
  return capturedResponse;
372
376
  },
373
377
  reply(data) {
374
- return createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
378
+ // Validate interaction can respond
379
+ if (!this.canRespond?.(this.id)) {
380
+ throw new Error('Interaction cannot respond: already responded or expired');
381
+ }
382
+ const startTime = Date.now();
383
+ const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
384
+ // Track response
385
+ this.trackResponse?.(this.id, this.token, 'responded');
386
+ // Log timing if debug enabled
387
+ this.logTiming?.(this.id, 'reply', startTime, true);
388
+ return response;
375
389
  },
376
390
  followUp(data) {
377
391
  return createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
@@ -380,12 +394,32 @@ export function createCommandInteraction(interaction) {
380
394
  return createMessageResponse(InteractionResponseType.UpdateMessage, data);
381
395
  },
382
396
  editReply(data) {
383
- return createMessageResponse(InteractionResponseType.UpdateMessage, data);
397
+ // Validate interaction can respond
398
+ if (!this.canRespond?.(this.id)) {
399
+ throw new Error('Interaction cannot edit reply: already responded, expired, or not deferred');
400
+ }
401
+ const startTime = Date.now();
402
+ const response = createMessageResponse(InteractionResponseType.UpdateMessage, data);
403
+ // Track response
404
+ this.trackResponse?.(this.id, this.token, 'responded');
405
+ // Log timing if debug enabled
406
+ this.logTiming?.(this.id, 'editReply', startTime, true);
407
+ return response;
384
408
  },
385
409
  deferReply(options) {
386
- return createDeferredResponse(options?.flags !== undefined
410
+ // Validate interaction can respond
411
+ if (!this.canRespond?.(this.id)) {
412
+ throw new Error('Interaction cannot defer: already responded or expired');
413
+ }
414
+ const startTime = Date.now();
415
+ const response = createDeferredResponse(options?.flags !== undefined
387
416
  ? { flags: options.flags }
388
417
  : undefined);
418
+ // Track deferred state
419
+ this.trackResponse?.(this.id, this.token, 'deferred');
420
+ // Log timing if debug enabled
421
+ this.logTiming?.(this.id, 'deferReply', startTime, true);
422
+ return response;
389
423
  },
390
424
  showModal(data) {
391
425
  const resolvedData = typeof data === "object" &&
@@ -432,6 +466,10 @@ export function createCommandInteraction(interaction) {
432
466
  throw error;
433
467
  }
434
468
  },
469
+ // Helper methods for state management
470
+ canRespond: helpers?.canRespond,
471
+ trackResponse: helpers?.trackResponse,
472
+ logTiming: helpers?.logTiming,
435
473
  };
436
474
  return commandInteraction;
437
475
  }
@@ -20,6 +20,7 @@ export type UserContextMenuInteraction = APIUserApplicationCommandInteraction &
20
20
  /** Resolved user targeted by this user context menu command. */
21
21
  targetUser?: APIUser;
22
22
  };
23
+ export declare const UserContextMenuInteraction: {};
23
24
  /**
24
25
  * Message context menu interaction with helper methods.
25
26
  */
@@ -27,10 +28,12 @@ export type MessageContextMenuInteraction = APIMessageApplicationCommandInteract
27
28
  /** Resolved message targeted by this message context menu command. */
28
29
  targetMessage?: APIMessage;
29
30
  };
31
+ export declare const MessageContextMenuInteraction: {};
30
32
  /**
31
33
  * Primary entry point interaction with helper methods.
32
34
  */
33
35
  export type AppCommandInteraction = APIPrimaryEntryPointCommandInteraction & ContextMenuInteractionHelpers;
36
+ export declare const AppCommandInteraction: {};
34
37
  /**
35
38
  * Wraps a raw user context menu interaction with helper methods.
36
39
  *
@@ -1,5 +1,8 @@
1
1
  import { InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const UserContextMenuInteraction = {};
4
+ export const MessageContextMenuInteraction = {};
5
+ export const AppCommandInteraction = {};
3
6
  function createContextMenuInteractionHelpers() {
4
7
  let capturedResponse = null;
5
8
  const captureResponse = (response) => {
@@ -5,6 +5,7 @@ export type ResolvedUserOption = {
5
5
  user: APIUser;
6
6
  member?: APIInteractionDataResolvedGuildMember;
7
7
  };
8
+ export declare const ResolvedUserOption: {};
8
9
  /** Resolved mentionable option (either a user or role). */
9
10
  export type ResolvedMentionableOption = {
10
11
  type: "user";
@@ -13,6 +14,7 @@ export type ResolvedMentionableOption = {
13
14
  type: "role";
14
15
  value: APIRole;
15
16
  };
17
+ export declare const ResolvedMentionableOption: {};
16
18
  /**
17
19
  * Base helper methods available on all component interactions.
18
20
  */
@@ -33,6 +35,7 @@ type BaseComponentInteractionHelpers = {
33
35
  export interface ButtonInteraction extends Omit<APIMessageComponentInteraction, "data">, BaseComponentInteractionHelpers {
34
36
  data: APIMessageButtonInteractionData;
35
37
  }
38
+ export declare const ButtonInteraction: {};
36
39
  /**
37
40
  * String select menu interaction with helper methods.
38
41
  */
@@ -41,6 +44,7 @@ export interface StringSelectInteraction extends Omit<APIMessageComponentInterac
41
44
  values: string[];
42
45
  getStringValues: () => string[];
43
46
  }
47
+ export declare const StringSelectInteraction: {};
44
48
  /**
45
49
  * Role select menu interaction with helper methods.
46
50
  */
@@ -49,6 +53,7 @@ export interface RoleSelectInteraction extends Omit<APIMessageComponentInteracti
49
53
  values: string[];
50
54
  getRoles: () => APIRole[];
51
55
  }
56
+ export declare const RoleSelectInteraction: {};
52
57
  /**
53
58
  * User select menu interaction with helper methods.
54
59
  */
@@ -57,6 +62,7 @@ export interface UserSelectInteraction extends Omit<APIMessageComponentInteracti
57
62
  values: string[];
58
63
  getUsers: () => ResolvedUserOption[];
59
64
  }
65
+ export declare const UserSelectInteraction: {};
60
66
  /**
61
67
  * Channel select menu interaction with helper methods.
62
68
  */
@@ -65,6 +71,7 @@ export interface ChannelSelectInteraction extends Omit<APIMessageComponentIntera
65
71
  values: string[];
66
72
  getChannels: () => APIInteractionDataResolvedChannel[];
67
73
  }
74
+ export declare const ChannelSelectInteraction: {};
68
75
  /**
69
76
  * Mentionable select menu interaction with helper methods.
70
77
  */
@@ -73,6 +80,7 @@ export interface MentionableSelectInteraction extends Omit<APIMessageComponentIn
73
80
  values: string[];
74
81
  getMentionables: () => ResolvedMentionableOption[];
75
82
  }
83
+ export declare const MentionableSelectInteraction: {};
76
84
  /**
77
85
  * Represents a component interaction augmented with helper response methods.
78
86
  *
@@ -120,6 +128,7 @@ export type MessageComponentInteraction = APIMessageComponentInteraction & {
120
128
  */
121
129
  getMentionables: () => ResolvedMentionableOption[];
122
130
  };
131
+ export declare const MessageComponentInteraction: {};
123
132
  /**
124
133
  * Wraps a raw component interaction with helper methods mirroring Discord's expected responses.
125
134
  *
@@ -1,5 +1,14 @@
1
1
  import { InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const ResolvedUserOption = {};
4
+ export const ResolvedMentionableOption = {};
5
+ export const ButtonInteraction = {};
6
+ export const StringSelectInteraction = {};
7
+ export const RoleSelectInteraction = {};
8
+ export const UserSelectInteraction = {};
9
+ export const ChannelSelectInteraction = {};
10
+ export const MentionableSelectInteraction = {};
11
+ export const MessageComponentInteraction = {};
3
12
  /**
4
13
  * Wraps a raw component interaction with helper methods mirroring Discord's expected responses.
5
14
  *
@@ -19,6 +19,7 @@ export type ModalSubmitInteraction = APIModalSubmitInteraction & {
19
19
  */
20
20
  getTextInputValues: () => Map<string, string>;
21
21
  };
22
+ export declare const ModalSubmitInteraction: {};
22
23
  /**
23
24
  * Wraps a raw modal submit interaction with helper methods.
24
25
  *
@@ -1,5 +1,6 @@
1
1
  import { InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const ModalSubmitInteraction = {};
3
4
  /**
4
5
  * Wraps a raw modal submit interaction with helper methods.
5
6
  *
@@ -1,10 +1,17 @@
1
1
  import type { APIInteractionResponseCallbackData, MessageFlags } from "discord-api-types/v10";
2
- import type { InteractionFollowUpFlags, InteractionReplyFlags } from "../types/InteractionFlags.js";
2
+ import type { APIActionRowComponent, APIComponentInMessageActionRow, APIContainerComponent } from "discord-api-types/v10";
3
+ import type { JSONEncodable } from "../builders/shared.js";
4
+ import type { InteractionFlags } from "../types/InteractionFlags.js";
3
5
  /** Union of helper flag enums and raw Discord message flags. */
4
- export type MessageFlagLike = MessageFlags | InteractionReplyFlags | InteractionFollowUpFlags;
6
+ export type MessageFlagLike = MessageFlags | InteractionFlags;
7
+ /** Top-level components allowed in messages (ActionRows or Containers) */
8
+ export type TopLevelComponent = APIActionRowComponent<APIComponentInMessageActionRow> | APIContainerComponent;
5
9
  /** Message payload accepted by helper reply/edit functions. */
6
- export type InteractionMessageData = Omit<APIInteractionResponseCallbackData, "flags"> & {
7
- flags?: MessageFlagLike | MessageFlagLike[];
10
+ export type InteractionMessageData = {
11
+ content?: string;
12
+ components?: TopLevelComponent[] | JSONEncodable<TopLevelComponent>[] | JSONEncodable<TopLevelComponent[]>;
13
+ embeds?: unknown[];
14
+ flags?: InteractionFlags;
8
15
  };
9
16
  /** Deferred response payload recognised by helper methods. */
10
17
  export type DeferredResponseData = {
@@ -1,4 +1,5 @@
1
- import { ComponentType, MessageFlags as RawMessageFlags, } from "discord-api-types/v10";
1
+ import { resolveJSONEncodable } from "../builders/shared.js";
2
+ import { ComponentType, } from "discord-api-types/v10";
2
3
  const COMPONENTS_V2_TYPES = new Set([
3
4
  ComponentType.Container,
4
5
  ComponentType.Section,
@@ -37,36 +38,18 @@ export function normaliseInteractionMessageData(data) {
37
38
  if (!data) {
38
39
  return undefined;
39
40
  }
40
- // Auto-convert builders to JSON
41
- const normalisedComponents = data.components
42
- ? data.components.map((component) => resolveComponentLike(component))
43
- : undefined;
44
- const normalisedEmbeds = data.embeds
45
- ? data.embeds.map((embed) => resolveComponentLike(embed))
46
- : undefined;
47
- const usesComponentsV2 = Array.isArray(normalisedComponents)
48
- ? containsComponentsV2(normalisedComponents)
49
- : false;
50
- const normalisedFlags = normaliseMessageFlags(data.flags);
51
- const finalFlags = usesComponentsV2
52
- ? ((normalisedFlags ?? 0) |
53
- RawMessageFlags.IsComponentsV2)
54
- : normalisedFlags;
55
- const needsNormalisation = finalFlags !== data.flags ||
56
- normalisedComponents !== data.components ||
57
- normalisedEmbeds !== data.embeds;
58
- if (!needsNormalisation) {
59
- return data;
41
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
42
+ const responseData = { ...data };
43
+ if (data.components) {
44
+ const components = resolveJSONEncodable(data.components);
45
+ if (Array.isArray(components)) {
46
+ responseData.components = components.map((c) => resolveJSONEncodable(c));
47
+ }
48
+ }
49
+ if (data.embeds) {
50
+ responseData.embeds = data.embeds.map((e) => resolveJSONEncodable(e));
60
51
  }
61
- const { flags: _flags, components: _components, embeds: _embeds, ...rest } = data;
62
- return {
63
- ...rest,
64
- ...(normalisedComponents !== undefined
65
- ? { components: normalisedComponents }
66
- : {}),
67
- ...(normalisedEmbeds !== undefined ? { embeds: normalisedEmbeds } : {}),
68
- ...(finalFlags !== undefined ? { flags: finalFlags } : {}),
69
- };
52
+ return responseData;
70
53
  }
71
54
  function containsComponentsV2(components) {
72
55
  return components.some((component) => componentUsesComponentsV2(component));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.1.13",
3
+ "version": "0.2.0",
4
4
  "description": "Mini interaction, connecting your app with Discord via HTTP-interaction (Vercel support).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",