@minesa-org/mini-interaction 0.2.1 → 0.2.4

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.
@@ -49,15 +49,6 @@ export type InteractionTimeoutConfig = {
49
49
  /** Whether to enable debug logging for interaction responses (default: false) */
50
50
  enableResponseDebugLogging?: boolean;
51
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;
60
- };
61
52
  /** Handler signature invoked for Discord button interactions. */
62
53
  export type ButtonComponentHandler = (interaction: ButtonInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
63
54
  /** Handler signature invoked for Discord string select menu interactions. */
@@ -184,46 +175,15 @@ export declare class MiniInteraction {
184
175
  * Creates a new MiniInteraction client with optional command auto-loading and custom runtime hooks.
185
176
  */
186
177
  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
178
  private trackInteractionState;
191
179
  /**
192
180
  * Checks if an interaction can still respond (not expired and not already responded).
193
181
  */
194
182
  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
183
  /**
212
184
  * Gets the current state of an interaction.
213
185
  */
214
186
  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
187
  /**
228
188
  * Clears expired interaction states to prevent memory leaks.
229
189
  * Call this periodically to clean up old interaction data.
@@ -78,27 +78,16 @@ export class MiniInteraction {
78
78
  enableTimeoutWarnings: true,
79
79
  autoDeferSlowOperations: true,
80
80
  enableResponseDebugLogging: false,
81
- responseAcknowledgmentTimeout: 500, // 500ms to wait for acknowledgment
82
- responseAcknowledgmentRetries: 2,
83
- responseAcknowledgmentRetryDelay: 100,
84
81
  ...timeoutConfig,
85
82
  };
86
83
  }
87
- /**
88
- * Tracks the state of an interaction to prevent race conditions and double responses.
89
- */
90
84
  trackInteractionState(interactionId, token, state) {
91
- const existing = this.interactionStates.get(interactionId);
92
85
  const now = Date.now();
93
86
  this.interactionStates.set(interactionId, {
94
87
  state,
95
88
  timestamp: now,
96
89
  token,
97
- responseCount: existing ? existing.responseCount + 1 : 1,
98
90
  });
99
- if (this.timeoutConfig.enableResponseDebugLogging) {
100
- console.log(`[MiniInteraction:DEBUG] Interaction ${interactionId} state: ${state} (${existing ? existing.responseCount + 1 : 1} responses)`);
101
- }
102
91
  }
103
92
  /**
104
93
  * Checks if an interaction can still respond (not expired and not already responded).
@@ -118,68 +107,12 @@ export class MiniInteraction {
118
107
  }
119
108
  return true;
120
109
  }
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
110
  /**
168
111
  * Gets the current state of an interaction.
169
112
  */
170
113
  getInteractionState(interactionId) {
171
114
  return this.interactionStates.get(interactionId);
172
115
  }
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
116
  /**
184
117
  * Clears expired interaction states to prevent memory leaks.
185
118
  * Call this periodically to clean up old interaction data.
@@ -194,9 +127,6 @@ export class MiniInteraction {
194
127
  cleaned++;
195
128
  }
196
129
  }
197
- if (cleaned > 0) {
198
- console.log(`[MiniInteraction] Cleaned up ${cleaned} expired interactions`);
199
- }
200
130
  return cleaned;
201
131
  }
202
132
  normalizeCommandData(data) {
@@ -219,18 +149,6 @@ export class MiniInteraction {
219
149
  data: normalizedData,
220
150
  };
221
151
  this.commands.set(commandName, normalizedCommand);
222
- if (normalizedCommand.components &&
223
- Array.isArray(normalizedCommand.components)) {
224
- for (const component of normalizedCommand.components) {
225
- this.useComponent(component);
226
- }
227
- }
228
- if (normalizedCommand.modals &&
229
- Array.isArray(normalizedCommand.modals)) {
230
- for (const modal of normalizedCommand.modals) {
231
- this.useModal(modal);
232
- }
233
- }
234
152
  }
235
153
  /**
236
154
  * Registers a single command handler with the client.
@@ -542,10 +460,6 @@ export class MiniInteraction {
542
460
  `This may cause "didn't respond in time" errors. ` +
543
461
  `Consider optimizing or using deferReply() for slow operations.`);
544
462
  }
545
- // Log successful response timing for debugging
546
- if (this.timeoutConfig.enableResponseDebugLogging) {
547
- console.log(`[MiniInteraction:DEBUG] Request completed in ${totalProcessingTime}ms`);
548
- }
549
463
  return {
550
464
  status: 400,
551
465
  body: {
@@ -961,7 +875,7 @@ export class MiniInteraction {
961
875
  console.warn(`[MiniInteraction] Command module "${absolutePath}" does not export a command object. Skipping.`);
962
876
  return null;
963
877
  }
964
- const { data, handler, components, modals } = candidate;
878
+ const { data, handler } = candidate;
965
879
  const normalizedData = this.normalizeCommandData(data);
966
880
  if (!normalizedData || typeof normalizedData.name !== "string") {
967
881
  console.warn(`[MiniInteraction] Command module "${absolutePath}" is missing "data.name". Skipping.`);
@@ -971,7 +885,7 @@ export class MiniInteraction {
971
885
  console.warn(`[MiniInteraction] Command module "${absolutePath}" is missing a "handler" function. Skipping.`);
972
886
  return null;
973
887
  }
974
- return { data: normalizedData, handler, components, modals };
888
+ return { data: normalizedData, handler };
975
889
  }
976
890
  catch (error) {
977
891
  console.error(`[MiniInteraction] Failed to load command module "${absolutePath}":`, error);
@@ -1206,8 +1120,15 @@ export class MiniInteraction {
1206
1120
  };
1207
1121
  }
1208
1122
  try {
1209
- const interactionWithHelpers = createMessageComponentInteraction(interaction);
1210
- // Wrap component handler with timeout
1123
+ // Create an acknowledgment promise that resolves when the handler calls reply() or deferReply()
1124
+ let ackResolver = null;
1125
+ const ackPromise = new Promise((resolve) => {
1126
+ ackResolver = resolve;
1127
+ });
1128
+ const interactionWithHelpers = createMessageComponentInteraction(interaction, {
1129
+ onAck: (response) => ackResolver?.(response),
1130
+ });
1131
+ // Wrap component handler with timeout and acknowledgment
1211
1132
  const timeoutWrapper = createTimeoutWrapper(async () => {
1212
1133
  const response = await handler(interactionWithHelpers);
1213
1134
  const resolvedResponse = response ?? interactionWithHelpers.getResponse();
@@ -1216,7 +1137,7 @@ export class MiniInteraction {
1216
1137
  "Return an APIInteractionResponse to acknowledge the interaction.");
1217
1138
  }
1218
1139
  return resolvedResponse;
1219
- }, this.timeoutConfig.initialResponseTimeout, `Component "${customId}"`, this.timeoutConfig.enableTimeoutWarnings);
1140
+ }, this.timeoutConfig.initialResponseTimeout, `Component "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
1220
1141
  const resolvedResponse = await timeoutWrapper();
1221
1142
  return {
1222
1143
  status: 200,
@@ -1261,8 +1182,15 @@ export class MiniInteraction {
1261
1182
  };
1262
1183
  }
1263
1184
  try {
1264
- const interactionWithHelpers = createModalSubmitInteraction(interaction);
1265
- // Wrap modal handler with timeout
1185
+ // Create an acknowledgment promise for modals
1186
+ let ackResolver = null;
1187
+ const ackPromise = new Promise((resolve) => {
1188
+ ackResolver = resolve;
1189
+ });
1190
+ const interactionWithHelpers = createModalSubmitInteraction(interaction, {
1191
+ onAck: (response) => ackResolver?.(response),
1192
+ });
1193
+ // Wrap modal handler with timeout and acknowledgment
1266
1194
  const timeoutWrapper = createTimeoutWrapper(async () => {
1267
1195
  const response = await handler(interactionWithHelpers);
1268
1196
  const resolvedResponse = response ?? interactionWithHelpers.getResponse();
@@ -1271,7 +1199,7 @@ export class MiniInteraction {
1271
1199
  "Return an APIInteractionResponse to acknowledge the interaction.");
1272
1200
  }
1273
1201
  return resolvedResponse;
1274
- }, this.timeoutConfig.initialResponseTimeout, `Modal "${customId}"`, this.timeoutConfig.enableTimeoutWarnings);
1202
+ }, this.timeoutConfig.initialResponseTimeout, `Modal "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
1275
1203
  const resolvedResponse = await timeoutWrapper();
1276
1204
  return {
1277
1205
  status: 200,
@@ -1319,6 +1247,11 @@ export class MiniInteraction {
1319
1247
  try {
1320
1248
  let response;
1321
1249
  let resolvedResponse = null;
1250
+ // Create an acknowledgment promise for application commands
1251
+ let ackResolver = null;
1252
+ const ackPromise = new Promise((resolve) => {
1253
+ ackResolver = resolve;
1254
+ });
1322
1255
  // Create a timeout wrapper for the command handler
1323
1256
  const timeoutWrapper = createTimeoutWrapper(async () => {
1324
1257
  // Check if it's a chat input (slash) command
@@ -1327,7 +1260,7 @@ export class MiniInteraction {
1327
1260
  const interactionWithHelpers = createCommandInteraction(commandInteraction, {
1328
1261
  canRespond: (id) => this.canRespond(id),
1329
1262
  trackResponse: (id, token, state) => this.trackInteractionState(id, token, state),
1330
- logTiming: (id, op, start, success) => this.logResponseTiming(id, op, start, success),
1263
+ onAck: (response) => ackResolver?.(response),
1331
1264
  });
1332
1265
  response = await command.handler(interactionWithHelpers);
1333
1266
  resolvedResponse =
@@ -1336,14 +1269,18 @@ export class MiniInteraction {
1336
1269
  else if (commandInteraction.data.type ===
1337
1270
  ApplicationCommandType.User) {
1338
1271
  // User context menu command
1339
- const interactionWithHelpers = createUserContextMenuInteraction(commandInteraction);
1272
+ const interactionWithHelpers = createUserContextMenuInteraction(commandInteraction, {
1273
+ onAck: (response) => ackResolver?.(response),
1274
+ });
1340
1275
  response = await command.handler(interactionWithHelpers);
1341
1276
  resolvedResponse =
1342
1277
  response ?? interactionWithHelpers.getResponse();
1343
1278
  }
1344
1279
  else if (commandInteraction.data.type ===
1345
1280
  ApplicationCommandType.PrimaryEntryPoint) {
1346
- const interactionWithHelpers = createAppCommandInteraction(commandInteraction);
1281
+ const interactionWithHelpers = createAppCommandInteraction(commandInteraction, {
1282
+ onAck: (response) => ackResolver?.(response),
1283
+ });
1347
1284
  response = await command.handler(interactionWithHelpers);
1348
1285
  resolvedResponse =
1349
1286
  response ?? interactionWithHelpers.getResponse();
@@ -1351,7 +1288,9 @@ export class MiniInteraction {
1351
1288
  else if (commandInteraction.data.type ===
1352
1289
  ApplicationCommandType.Message) {
1353
1290
  // Message context menu command
1354
- const interactionWithHelpers = createMessageContextMenuInteraction(commandInteraction);
1291
+ const interactionWithHelpers = createMessageContextMenuInteraction(commandInteraction, {
1292
+ onAck: (response) => ackResolver?.(response),
1293
+ });
1355
1294
  response = await command.handler(interactionWithHelpers);
1356
1295
  resolvedResponse =
1357
1296
  response ?? interactionWithHelpers.getResponse();
@@ -1361,9 +1300,10 @@ export class MiniInteraction {
1361
1300
  response = await command.handler(commandInteraction);
1362
1301
  resolvedResponse = response ?? null;
1363
1302
  }
1364
- }, this.timeoutConfig.initialResponseTimeout, `Command "${commandName}"`, this.timeoutConfig.enableTimeoutWarnings);
1365
- await timeoutWrapper();
1366
- if (!resolvedResponse) {
1303
+ return resolvedResponse;
1304
+ }, this.timeoutConfig.initialResponseTimeout, `Command "${commandName}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
1305
+ const finalResponse = await timeoutWrapper();
1306
+ if (!finalResponse) {
1367
1307
  console.error(`[MiniInteraction] Command "${commandName}" did not return a response. ` +
1368
1308
  "This indicates the handler completed but no response was generated. " +
1369
1309
  "Check that deferReply(), reply(), showModal(), or a direct response is returned.");
@@ -1378,7 +1318,7 @@ export class MiniInteraction {
1378
1318
  }
1379
1319
  return {
1380
1320
  status: 200,
1381
- body: resolvedResponse,
1321
+ body: finalResponse,
1382
1322
  };
1383
1323
  }
1384
1324
  catch (error) {
@@ -1566,7 +1506,7 @@ function resolveOAuthConfig(provided) {
1566
1506
  /**
1567
1507
  * Wraps a handler function with timeout detection and error handling.
1568
1508
  */
1569
- function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings = true) {
1509
+ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings = true, ackPromise) {
1570
1510
  return async (...args) => {
1571
1511
  const startTime = Date.now();
1572
1512
  let timeoutId;
@@ -1578,10 +1518,14 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
1578
1518
  }, timeoutMs);
1579
1519
  });
1580
1520
  try {
1581
- const result = await Promise.race([
1521
+ const promises = [
1582
1522
  Promise.resolve(handler(...args)),
1583
1523
  timeoutPromise,
1584
- ]);
1524
+ ];
1525
+ if (ackPromise) {
1526
+ promises.push(ackPromise);
1527
+ }
1528
+ const result = await Promise.race(promises);
1585
1529
  if (timeoutId) {
1586
1530
  clearTimeout(timeoutId);
1587
1531
  }
@@ -77,7 +77,6 @@ export class MiniDatabase {
77
77
  await this.mongoClient.connect();
78
78
  this.mongoDb = this.mongoClient.db(this.config.dbName || "minidb");
79
79
  this.mongoCollection = this.mongoDb.collection(this.config.collectionName || "data");
80
- console.log("✅ [MiniDatabase] Connected to MongoDB");
81
80
  }
82
81
  catch (err) {
83
82
  console.error("❌ [MiniDatabase] Failed to connect to MongoDB:", err);
@@ -136,7 +135,6 @@ export class MiniDatabase {
136
135
  createdAt: new Date(),
137
136
  },
138
137
  }, { upsert: true });
139
- console.log(`✅ [MiniDatabase] Saved data for key "${key}"`);
140
138
  return true;
141
139
  }
142
140
  catch (err) {
@@ -175,7 +173,6 @@ export class MiniDatabase {
175
173
  createdAt: new Date(),
176
174
  },
177
175
  }, { upsert: true });
178
- console.log(`✅ [MiniDatabase] Updated data for key "${key}"`);
179
176
  return true;
180
177
  }
181
178
  catch (err) {
@@ -194,7 +191,6 @@ export class MiniDatabase {
194
191
  throw new Error("MongoDB collection is not initialized");
195
192
  }
196
193
  await collection.deleteOne({ _id: key });
197
- console.log(`✅ [MiniDatabase] Deleted data for key "${key}"`);
198
194
  return true;
199
195
  }
200
196
  catch (err) {
@@ -208,7 +204,6 @@ export class MiniDatabase {
208
204
  async close() {
209
205
  if (this.mongoClient) {
210
206
  await this.mongoClient.close();
211
- console.log("✅ [MiniDatabase] MongoDB connection closed");
212
207
  }
213
208
  }
214
209
  }
@@ -2,7 +2,6 @@ import type { APIInteractionResponse, RESTPostAPIChatInputApplicationCommandsJSO
2
2
  import type { CommandInteraction } from "../utils/CommandInteractionOptions.js";
3
3
  import type { UserContextMenuInteraction, MessageContextMenuInteraction, AppCommandInteraction } from "../utils/ContextMenuInteraction.js";
4
4
  import type { JSONEncodable } from "../builders/shared.js";
5
- import type { ComponentCommand, ModalCommand } from "../clients/MiniInteraction.js";
6
5
  import type { CommandBuilder } from "../commands/CommandBuilder.js";
7
6
  import type { MessageCommandBuilder, UserCommandBuilder, AppCommandBuilder } from "../commands/ContextMenuCommandBuilder.js";
8
7
  /** Handler signature for slash command executions within MiniInteraction. */
@@ -19,16 +18,6 @@ export type CommandHandler = SlashCommandHandler | UserCommandHandler | MessageC
19
18
  export type InteractionCommand = {
20
19
  data: RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIContextMenuApplicationCommandsJSONBody | RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody | CommandBuilder | UserCommandBuilder | MessageCommandBuilder | AppCommandBuilder | JSONEncodable<RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIContextMenuApplicationCommandsJSONBody | RESTPostAPIPrimaryEntryPointApplicationCommandJSONBody>;
21
20
  handler: CommandHandler;
22
- /**
23
- * Optional array of component handlers related to this command.
24
- * These will be automatically registered when the command is loaded.
25
- */
26
- components?: ComponentCommand[];
27
- /**
28
- * Optional array of modal handlers related to this command.
29
- * These will be automatically registered when the command is loaded.
30
- */
31
- modals?: ModalCommand[];
32
21
  };
33
22
  /** Map of command names to their registered MiniInteraction command definitions. */
34
23
  export type InteractionCommandsMap = Map<string, InteractionCommand>;
@@ -154,7 +154,7 @@ export interface CommandInteraction extends Omit<APIChatInputApplicationCommandI
154
154
  withTimeoutProtection<T>(operation: () => Promise<T>, deferOptions?: DeferReplyOptions): Promise<T>;
155
155
  canRespond?(interactionId: string): boolean;
156
156
  trackResponse?(interactionId: string, token: string, state: 'responded' | 'deferred'): void;
157
- logTiming?(interactionId: string, operation: string, startTime: number, success: boolean): void;
157
+ onAck?(response: APIInteractionResponse): void;
158
158
  }
159
159
  export declare const CommandInteraction: {};
160
160
  /**
@@ -168,4 +168,5 @@ export declare function createCommandInteraction(interaction: APIChatInputApplic
168
168
  canRespond?: (interactionId: string) => boolean;
169
169
  trackResponse?: (interactionId: string, token: string, state: 'responded' | 'deferred') => void;
170
170
  logTiming?: (interactionId: string, operation: string, startTime: number, success: boolean) => void;
171
+ onAck?: (response: APIInteractionResponse) => void;
171
172
  }): CommandInteraction;
@@ -383,8 +383,9 @@ export function createCommandInteraction(interaction, helpers) {
383
383
  const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
384
384
  // Track response
385
385
  this.trackResponse?.(this.id, this.token, 'responded');
386
+ // Notify acknowledgment
387
+ this.onAck?.(response);
386
388
  // Log timing if debug enabled
387
- this.logTiming?.(this.id, 'reply', startTime, true);
388
389
  return response;
389
390
  },
390
391
  followUp(data) {
@@ -403,7 +404,6 @@ export function createCommandInteraction(interaction, helpers) {
403
404
  // Track response
404
405
  this.trackResponse?.(this.id, this.token, 'responded');
405
406
  // Log timing if debug enabled
406
- this.logTiming?.(this.id, 'editReply', startTime, true);
407
407
  return response;
408
408
  },
409
409
  deferReply(options) {
@@ -417,8 +417,9 @@ export function createCommandInteraction(interaction, helpers) {
417
417
  : undefined);
418
418
  // Track deferred state
419
419
  this.trackResponse?.(this.id, this.token, 'deferred');
420
+ // Notify acknowledgment
421
+ this.onAck?.(response);
420
422
  // Log timing if debug enabled
421
- this.logTiming?.(this.id, 'deferReply', startTime, true);
422
423
  return response;
423
424
  },
424
425
  showModal(data) {
@@ -469,7 +470,7 @@ export function createCommandInteraction(interaction, helpers) {
469
470
  // Helper methods for state management
470
471
  canRespond: helpers?.canRespond,
471
472
  trackResponse: helpers?.trackResponse,
472
- logTiming: helpers?.logTiming,
473
+ onAck: helpers?.onAck,
473
474
  };
474
475
  return commandInteraction;
475
476
  }
@@ -12,6 +12,7 @@ type ContextMenuInteractionHelpers = {
12
12
  showModal: (data: APIModalInteractionResponseCallbackData | {
13
13
  toJSON(): APIModalInteractionResponseCallbackData;
14
14
  }) => APIModalInteractionResponse;
15
+ onAck?: (response: APIInteractionResponse) => void;
15
16
  };
16
17
  /**
17
18
  * User context menu interaction with helper methods.
@@ -40,19 +41,25 @@ export declare const AppCommandInteraction: {};
40
41
  * @param interaction - The raw user context menu interaction payload from Discord.
41
42
  * @returns A helper-augmented interaction object.
42
43
  */
43
- export declare function createUserContextMenuInteraction(interaction: APIUserApplicationCommandInteraction): UserContextMenuInteraction;
44
+ export declare function createUserContextMenuInteraction(interaction: APIUserApplicationCommandInteraction, helpers?: {
45
+ onAck?: (response: APIInteractionResponse) => void;
46
+ }): UserContextMenuInteraction;
44
47
  /**
45
48
  * Wraps a raw message context menu interaction with helper methods.
46
49
  *
47
50
  * @param interaction - The raw message context menu interaction payload from Discord.
48
51
  * @returns A helper-augmented interaction object.
49
52
  */
50
- export declare function createMessageContextMenuInteraction(interaction: APIMessageApplicationCommandInteraction): MessageContextMenuInteraction;
53
+ export declare function createMessageContextMenuInteraction(interaction: APIMessageApplicationCommandInteraction, helpers?: {
54
+ onAck?: (response: APIInteractionResponse) => void;
55
+ }): MessageContextMenuInteraction;
51
56
  /**
52
57
  * Wraps a raw primary entry point interaction with helper methods.
53
58
  *
54
59
  * @param interaction - The raw primary entry point interaction payload from Discord.
55
60
  * @returns A helper-augmented interaction object.
56
61
  */
57
- export declare function createAppCommandInteraction(interaction: APIPrimaryEntryPointCommandInteraction): AppCommandInteraction;
62
+ export declare function createAppCommandInteraction(interaction: APIPrimaryEntryPointCommandInteraction, helpers?: {
63
+ onAck?: (response: APIInteractionResponse) => void;
64
+ }): AppCommandInteraction;
58
65
  export {};
@@ -3,7 +3,7 @@ import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./inter
3
3
  export const UserContextMenuInteraction = {};
4
4
  export const MessageContextMenuInteraction = {};
5
5
  export const AppCommandInteraction = {};
6
- function createContextMenuInteractionHelpers() {
6
+ function createContextMenuInteractionHelpers(helpers) {
7
7
  let capturedResponse = null;
8
8
  const captureResponse = (response) => {
9
9
  capturedResponse = response;
@@ -25,15 +25,21 @@ function createContextMenuInteractionHelpers() {
25
25
  }
26
26
  return captureResponse({ type });
27
27
  }
28
- const reply = (data) => createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
28
+ const reply = (data) => {
29
+ const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
30
+ helpers?.onAck?.(response);
31
+ return response;
32
+ };
29
33
  const followUp = (data) => createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
30
34
  const editReply = (data) => createMessageResponse(InteractionResponseType.UpdateMessage, data);
31
35
  const deferReply = (options = {}) => {
32
36
  const flags = normaliseMessageFlags(options.flags);
33
- return captureResponse({
37
+ const response = captureResponse({
34
38
  type: InteractionResponseType.DeferredChannelMessageWithSource,
35
39
  data: flags ? { flags } : undefined,
36
40
  });
41
+ helpers?.onAck?.(response);
42
+ return response;
37
43
  };
38
44
  const showModal = (data) => {
39
45
  const modalData = typeof data === "object" && "toJSON" in data ? data.toJSON() : data;
@@ -50,6 +56,7 @@ function createContextMenuInteractionHelpers() {
50
56
  editReply,
51
57
  deferReply,
52
58
  showModal,
59
+ onAck: helpers?.onAck,
53
60
  };
54
61
  }
55
62
  /**
@@ -58,8 +65,8 @@ function createContextMenuInteractionHelpers() {
58
65
  * @param interaction - The raw user context menu interaction payload from Discord.
59
66
  * @returns A helper-augmented interaction object.
60
67
  */
61
- export function createUserContextMenuInteraction(interaction) {
62
- return Object.assign(interaction, createContextMenuInteractionHelpers(), {
68
+ export function createUserContextMenuInteraction(interaction, helpers) {
69
+ return Object.assign(interaction, createContextMenuInteractionHelpers(helpers), {
63
70
  targetUser: resolveTargetUser(interaction),
64
71
  });
65
72
  }
@@ -69,8 +76,8 @@ export function createUserContextMenuInteraction(interaction) {
69
76
  * @param interaction - The raw message context menu interaction payload from Discord.
70
77
  * @returns A helper-augmented interaction object.
71
78
  */
72
- export function createMessageContextMenuInteraction(interaction) {
73
- return Object.assign(interaction, createContextMenuInteractionHelpers(), {
79
+ export function createMessageContextMenuInteraction(interaction, helpers) {
80
+ return Object.assign(interaction, createContextMenuInteractionHelpers(helpers), {
74
81
  targetMessage: resolveTargetMessage(interaction),
75
82
  });
76
83
  }
@@ -80,8 +87,8 @@ export function createMessageContextMenuInteraction(interaction) {
80
87
  * @param interaction - The raw primary entry point interaction payload from Discord.
81
88
  * @returns A helper-augmented interaction object.
82
89
  */
83
- export function createAppCommandInteraction(interaction) {
84
- return Object.assign(interaction, createContextMenuInteractionHelpers());
90
+ export function createAppCommandInteraction(interaction, helpers) {
91
+ return Object.assign(interaction, createContextMenuInteractionHelpers(helpers));
85
92
  }
86
93
  function resolveTargetMessage(interaction) {
87
94
  const targetId = interaction.data?.target_id;
@@ -27,6 +27,7 @@ type BaseComponentInteractionHelpers = {
27
27
  showModal: (data: APIModalInteractionResponseCallbackData | {
28
28
  toJSON(): APIModalInteractionResponseCallbackData;
29
29
  }) => APIModalInteractionResponse;
30
+ onAck?: (response: APIInteractionResponse) => void;
30
31
  };
31
32
  /**
32
33
  * Button interaction with helper methods.
@@ -135,5 +136,7 @@ export declare const MessageComponentInteraction: {};
135
136
  * @param interaction - The raw interaction payload from Discord.
136
137
  * @returns A helper-augmented interaction object.
137
138
  */
138
- export declare function createMessageComponentInteraction(interaction: APIMessageComponentInteraction): MessageComponentInteraction;
139
+ export declare function createMessageComponentInteraction(interaction: APIMessageComponentInteraction, helpers?: {
140
+ onAck?: (response: APIInteractionResponse) => void;
141
+ }): MessageComponentInteraction;
139
142
  export {};
@@ -15,7 +15,7 @@ export const MessageComponentInteraction = {};
15
15
  * @param interaction - The raw interaction payload from Discord.
16
16
  * @returns A helper-augmented interaction object.
17
17
  */
18
- export function createMessageComponentInteraction(interaction) {
18
+ export function createMessageComponentInteraction(interaction, helpers) {
19
19
  let capturedResponse = null;
20
20
  const captureResponse = (response) => {
21
21
  capturedResponse = response;
@@ -26,10 +26,12 @@ export function createMessageComponentInteraction(interaction) {
26
26
  if (!normalisedData) {
27
27
  throw new Error("[MiniInteraction] Component replies require response data to be provided.");
28
28
  }
29
- return captureResponse({
29
+ const response = captureResponse({
30
30
  type: InteractionResponseType.ChannelMessageWithSource,
31
31
  data: normalisedData,
32
32
  });
33
+ helpers?.onAck?.(response);
34
+ return response;
33
35
  };
34
36
  const deferReply = (options) => {
35
37
  const flags = normaliseMessageFlags(options?.flags);
@@ -41,7 +43,9 @@ export function createMessageComponentInteraction(interaction) {
41
43
  : {
42
44
  type: InteractionResponseType.DeferredChannelMessageWithSource,
43
45
  };
44
- return captureResponse(response);
46
+ captureResponse(response);
47
+ helpers?.onAck?.(response);
48
+ return response;
45
49
  };
46
50
  const update = (data) => {
47
51
  const normalisedData = normaliseInteractionMessageData(data);
@@ -178,5 +182,6 @@ export function createMessageComponentInteraction(interaction) {
178
182
  getChannels,
179
183
  getUsers,
180
184
  getMentionables,
185
+ onAck: helpers?.onAck,
181
186
  });
182
187
  }
@@ -7,6 +7,7 @@ export type ModalSubmitInteraction = APIModalSubmitInteraction & {
7
7
  getResponse: () => APIInteractionResponse | null;
8
8
  reply: (data: InteractionMessageData) => APIInteractionResponseChannelMessageWithSource;
9
9
  deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
10
+ onAck?: (response: APIInteractionResponse) => void;
10
11
  /**
11
12
  * Helper method to get the value of a text input component by custom_id.
12
13
  * @param customId - The custom_id of the text input component
@@ -18,6 +19,12 @@ export type ModalSubmitInteraction = APIModalSubmitInteraction & {
18
19
  * @returns A map of custom_id to value for all text inputs
19
20
  */
20
21
  getTextInputValues: () => Map<string, string>;
22
+ /**
23
+ * Helper method to get the selected values of a select menu component by custom_id.
24
+ * @param customId - The custom_id of the select menu component
25
+ * @returns The selected values of the select menu, or undefined if not found
26
+ */
27
+ getSelectMenuValues: (customId: string) => string[] | undefined;
21
28
  };
22
29
  export declare const ModalSubmitInteraction: {};
23
30
  /**
@@ -26,4 +33,6 @@ export declare const ModalSubmitInteraction: {};
26
33
  * @param interaction - The raw interaction payload from Discord.
27
34
  * @returns A helper-augmented interaction object.
28
35
  */
29
- export declare function createModalSubmitInteraction(interaction: APIModalSubmitInteraction): ModalSubmitInteraction;
36
+ export declare function createModalSubmitInteraction(interaction: APIModalSubmitInteraction, helpers?: {
37
+ onAck?: (response: APIInteractionResponse) => void;
38
+ }): ModalSubmitInteraction;
@@ -7,7 +7,7 @@ export const ModalSubmitInteraction = {};
7
7
  * @param interaction - The raw interaction payload from Discord.
8
8
  * @returns A helper-augmented interaction object.
9
9
  */
10
- export function createModalSubmitInteraction(interaction) {
10
+ export function createModalSubmitInteraction(interaction, helpers) {
11
11
  let capturedResponse = null;
12
12
  const captureResponse = (response) => {
13
13
  capturedResponse = response;
@@ -18,10 +18,12 @@ export function createModalSubmitInteraction(interaction) {
18
18
  if (!normalisedData) {
19
19
  throw new Error("[MiniInteraction] Modal submit replies require response data to be provided.");
20
20
  }
21
- return captureResponse({
21
+ const response = captureResponse({
22
22
  type: InteractionResponseType.ChannelMessageWithSource,
23
23
  data: normalisedData,
24
24
  });
25
+ helpers?.onAck?.(response);
26
+ return response;
25
27
  };
26
28
  const deferReply = (options) => {
27
29
  const flags = normaliseMessageFlags(options?.flags);
@@ -33,7 +35,9 @@ export function createModalSubmitInteraction(interaction) {
33
35
  : {
34
36
  type: InteractionResponseType.DeferredChannelMessageWithSource,
35
37
  };
36
- return captureResponse(response);
38
+ captureResponse(response);
39
+ helpers?.onAck?.(response);
40
+ return response;
37
41
  };
38
42
  const getResponse = () => capturedResponse;
39
43
  // Helper to extract text input values from modal components
@@ -58,7 +62,30 @@ export function createModalSubmitInteraction(interaction) {
58
62
  }
59
63
  return textInputs;
60
64
  };
65
+ // Helper to extract select menu values from modal components
66
+ const extractSelectMenuValues = () => {
67
+ const selectMenuValues = new Map();
68
+ for (const component of interaction.data.components) {
69
+ // Handle action rows
70
+ if ("components" in component && Array.isArray(component.components)) {
71
+ for (const child of component.components) {
72
+ if ("values" in child && "custom_id" in child && Array.isArray(child.values)) {
73
+ selectMenuValues.set(child.custom_id, child.values);
74
+ }
75
+ }
76
+ }
77
+ // Handle labeled components (unlikely for select menus but good for completeness if spec allows)
78
+ else if ("component" in component) {
79
+ const labeledComponent = component.component; // Using any as ModalSubmitComponent might not cover select menus fully in types yet or strictness varies
80
+ if ("values" in labeledComponent && "custom_id" in labeledComponent && Array.isArray(labeledComponent.values)) {
81
+ selectMenuValues.set(labeledComponent.custom_id, labeledComponent.values);
82
+ }
83
+ }
84
+ }
85
+ return selectMenuValues;
86
+ };
61
87
  const textInputValues = extractTextInputs();
88
+ const selectMenuValues = extractSelectMenuValues();
62
89
  const getTextInputValue = (customId) => {
63
90
  return textInputValues.get(customId);
64
91
  };
@@ -71,5 +98,7 @@ export function createModalSubmitInteraction(interaction) {
71
98
  getResponse,
72
99
  getTextInputValue,
73
100
  getTextInputValues,
101
+ getSelectMenuValues: (customId) => selectMenuValues.get(customId),
102
+ onAck: helpers?.onAck,
74
103
  });
75
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.2.1",
3
+ "version": "0.2.4",
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",