@minesa-org/mini-interaction 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clients/MiniInteraction.d.ts +5 -40
- package/dist/clients/MiniInteraction.js +83 -91
- package/dist/database/MiniDatabase.js +0 -5
- package/dist/utils/CommandInteractionOptions.d.ts +4 -1
- package/dist/utils/CommandInteractionOptions.js +14 -11
- package/dist/utils/ContextMenuInteraction.d.ts +10 -3
- package/dist/utils/ContextMenuInteraction.js +16 -9
- package/dist/utils/MessageComponentInteraction.d.ts +11 -1
- package/dist/utils/MessageComponentInteraction.js +27 -6
- package/dist/utils/ModalSubmitInteraction.d.ts +11 -15
- package/dist/utils/ModalSubmitInteraction.js +28 -56
- package/package.json +1 -1
|
@@ -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.
|
|
@@ -434,5 +394,10 @@ export declare class MiniInteraction {
|
|
|
434
394
|
* Handles execution of an application command interaction.
|
|
435
395
|
*/
|
|
436
396
|
private handleApplicationCommand;
|
|
397
|
+
/**
|
|
398
|
+
* Sends a follow-up response or edits an existing response via Discord's interaction webhooks.
|
|
399
|
+
* This is used for interactions that have already been acknowledged (e.g., via deferReply).
|
|
400
|
+
*/
|
|
401
|
+
private sendFollowUp;
|
|
437
402
|
}
|
|
438
403
|
export {};
|
|
@@ -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) {
|
|
@@ -530,10 +460,6 @@ export class MiniInteraction {
|
|
|
530
460
|
`This may cause "didn't respond in time" errors. ` +
|
|
531
461
|
`Consider optimizing or using deferReply() for slow operations.`);
|
|
532
462
|
}
|
|
533
|
-
// Log successful response timing for debugging
|
|
534
|
-
if (this.timeoutConfig.enableResponseDebugLogging) {
|
|
535
|
-
console.log(`[MiniInteraction:DEBUG] Request completed in ${totalProcessingTime}ms`);
|
|
536
|
-
}
|
|
537
463
|
return {
|
|
538
464
|
status: 400,
|
|
539
465
|
body: {
|
|
@@ -1194,8 +1120,18 @@ export class MiniInteraction {
|
|
|
1194
1120
|
};
|
|
1195
1121
|
}
|
|
1196
1122
|
try {
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
+
// Helper to send follow-up responses via webhooks
|
|
1129
|
+
const sendFollowUp = (token, data) => this.sendFollowUp(token, data);
|
|
1130
|
+
const interactionWithHelpers = createMessageComponentInteraction(interaction, {
|
|
1131
|
+
onAck: (response) => ackResolver?.(response),
|
|
1132
|
+
sendFollowUp,
|
|
1133
|
+
});
|
|
1134
|
+
// Wrap component handler with timeout and acknowledgment
|
|
1199
1135
|
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
1200
1136
|
const response = await handler(interactionWithHelpers);
|
|
1201
1137
|
const resolvedResponse = response ?? interactionWithHelpers.getResponse();
|
|
@@ -1204,7 +1140,7 @@ export class MiniInteraction {
|
|
|
1204
1140
|
"Return an APIInteractionResponse to acknowledge the interaction.");
|
|
1205
1141
|
}
|
|
1206
1142
|
return resolvedResponse;
|
|
1207
|
-
}, this.timeoutConfig.initialResponseTimeout, `Component "${customId}"`, this.timeoutConfig.enableTimeoutWarnings);
|
|
1143
|
+
}, this.timeoutConfig.initialResponseTimeout, `Component "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1208
1144
|
const resolvedResponse = await timeoutWrapper();
|
|
1209
1145
|
return {
|
|
1210
1146
|
status: 200,
|
|
@@ -1249,8 +1185,18 @@ export class MiniInteraction {
|
|
|
1249
1185
|
};
|
|
1250
1186
|
}
|
|
1251
1187
|
try {
|
|
1252
|
-
|
|
1253
|
-
|
|
1188
|
+
// Create an acknowledgment promise for modals
|
|
1189
|
+
let ackResolver = null;
|
|
1190
|
+
const ackPromise = new Promise((resolve) => {
|
|
1191
|
+
ackResolver = resolve;
|
|
1192
|
+
});
|
|
1193
|
+
// Helper to send follow-up responses via webhooks
|
|
1194
|
+
const sendFollowUp = (token, data) => this.sendFollowUp(token, data);
|
|
1195
|
+
const interactionWithHelpers = createModalSubmitInteraction(interaction, {
|
|
1196
|
+
onAck: (response) => ackResolver?.(response),
|
|
1197
|
+
sendFollowUp,
|
|
1198
|
+
});
|
|
1199
|
+
// Wrap modal handler with timeout and acknowledgment
|
|
1254
1200
|
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
1255
1201
|
const response = await handler(interactionWithHelpers);
|
|
1256
1202
|
const resolvedResponse = response ?? interactionWithHelpers.getResponse();
|
|
@@ -1259,7 +1205,7 @@ export class MiniInteraction {
|
|
|
1259
1205
|
"Return an APIInteractionResponse to acknowledge the interaction.");
|
|
1260
1206
|
}
|
|
1261
1207
|
return resolvedResponse;
|
|
1262
|
-
}, this.timeoutConfig.initialResponseTimeout, `Modal "${customId}"`, this.timeoutConfig.enableTimeoutWarnings);
|
|
1208
|
+
}, this.timeoutConfig.initialResponseTimeout, `Modal "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1263
1209
|
const resolvedResponse = await timeoutWrapper();
|
|
1264
1210
|
return {
|
|
1265
1211
|
status: 200,
|
|
@@ -1307,6 +1253,13 @@ export class MiniInteraction {
|
|
|
1307
1253
|
try {
|
|
1308
1254
|
let response;
|
|
1309
1255
|
let resolvedResponse = null;
|
|
1256
|
+
// Create an acknowledgment promise for application commands
|
|
1257
|
+
let ackResolver = null;
|
|
1258
|
+
const ackPromise = new Promise((resolve) => {
|
|
1259
|
+
ackResolver = resolve;
|
|
1260
|
+
});
|
|
1261
|
+
// Helper to send follow-up responses via webhooks
|
|
1262
|
+
const sendFollowUp = (token, data) => this.sendFollowUp(token, data);
|
|
1310
1263
|
// Create a timeout wrapper for the command handler
|
|
1311
1264
|
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
1312
1265
|
// Check if it's a chat input (slash) command
|
|
@@ -1315,7 +1268,8 @@ export class MiniInteraction {
|
|
|
1315
1268
|
const interactionWithHelpers = createCommandInteraction(commandInteraction, {
|
|
1316
1269
|
canRespond: (id) => this.canRespond(id),
|
|
1317
1270
|
trackResponse: (id, token, state) => this.trackInteractionState(id, token, state),
|
|
1318
|
-
|
|
1271
|
+
onAck: (response) => ackResolver?.(response),
|
|
1272
|
+
sendFollowUp,
|
|
1319
1273
|
});
|
|
1320
1274
|
response = await command.handler(interactionWithHelpers);
|
|
1321
1275
|
resolvedResponse =
|
|
@@ -1324,14 +1278,18 @@ export class MiniInteraction {
|
|
|
1324
1278
|
else if (commandInteraction.data.type ===
|
|
1325
1279
|
ApplicationCommandType.User) {
|
|
1326
1280
|
// User context menu command
|
|
1327
|
-
const interactionWithHelpers = createUserContextMenuInteraction(commandInteraction
|
|
1281
|
+
const interactionWithHelpers = createUserContextMenuInteraction(commandInteraction, {
|
|
1282
|
+
onAck: (response) => ackResolver?.(response),
|
|
1283
|
+
});
|
|
1328
1284
|
response = await command.handler(interactionWithHelpers);
|
|
1329
1285
|
resolvedResponse =
|
|
1330
1286
|
response ?? interactionWithHelpers.getResponse();
|
|
1331
1287
|
}
|
|
1332
1288
|
else if (commandInteraction.data.type ===
|
|
1333
1289
|
ApplicationCommandType.PrimaryEntryPoint) {
|
|
1334
|
-
const interactionWithHelpers = createAppCommandInteraction(commandInteraction
|
|
1290
|
+
const interactionWithHelpers = createAppCommandInteraction(commandInteraction, {
|
|
1291
|
+
onAck: (response) => ackResolver?.(response),
|
|
1292
|
+
});
|
|
1335
1293
|
response = await command.handler(interactionWithHelpers);
|
|
1336
1294
|
resolvedResponse =
|
|
1337
1295
|
response ?? interactionWithHelpers.getResponse();
|
|
@@ -1339,7 +1297,9 @@ export class MiniInteraction {
|
|
|
1339
1297
|
else if (commandInteraction.data.type ===
|
|
1340
1298
|
ApplicationCommandType.Message) {
|
|
1341
1299
|
// Message context menu command
|
|
1342
|
-
const interactionWithHelpers = createMessageContextMenuInteraction(commandInteraction
|
|
1300
|
+
const interactionWithHelpers = createMessageContextMenuInteraction(commandInteraction, {
|
|
1301
|
+
onAck: (response) => ackResolver?.(response),
|
|
1302
|
+
});
|
|
1343
1303
|
response = await command.handler(interactionWithHelpers);
|
|
1344
1304
|
resolvedResponse =
|
|
1345
1305
|
response ?? interactionWithHelpers.getResponse();
|
|
@@ -1349,9 +1309,10 @@ export class MiniInteraction {
|
|
|
1349
1309
|
response = await command.handler(commandInteraction);
|
|
1350
1310
|
resolvedResponse = response ?? null;
|
|
1351
1311
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1312
|
+
return resolvedResponse;
|
|
1313
|
+
}, this.timeoutConfig.initialResponseTimeout, `Command "${commandName}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1314
|
+
const finalResponse = await timeoutWrapper();
|
|
1315
|
+
if (!finalResponse) {
|
|
1355
1316
|
console.error(`[MiniInteraction] Command "${commandName}" did not return a response. ` +
|
|
1356
1317
|
"This indicates the handler completed but no response was generated. " +
|
|
1357
1318
|
"Check that deferReply(), reply(), showModal(), or a direct response is returned.");
|
|
@@ -1366,7 +1327,7 @@ export class MiniInteraction {
|
|
|
1366
1327
|
}
|
|
1367
1328
|
return {
|
|
1368
1329
|
status: 200,
|
|
1369
|
-
body:
|
|
1330
|
+
body: finalResponse,
|
|
1370
1331
|
};
|
|
1371
1332
|
}
|
|
1372
1333
|
catch (error) {
|
|
@@ -1386,6 +1347,33 @@ export class MiniInteraction {
|
|
|
1386
1347
|
};
|
|
1387
1348
|
}
|
|
1388
1349
|
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Sends a follow-up response or edits an existing response via Discord's interaction webhooks.
|
|
1352
|
+
* This is used for interactions that have already been acknowledged (e.g., via deferReply).
|
|
1353
|
+
*/
|
|
1354
|
+
async sendFollowUp(token, response) {
|
|
1355
|
+
const url = `${DISCORD_BASE_URL}/webhooks/${this.applicationId}/${token}/messages/@original`;
|
|
1356
|
+
// Only send follow-up if there is data to send
|
|
1357
|
+
if (!('data' in response) || !response.data) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
try {
|
|
1361
|
+
const fetchResponse = await this.fetchImpl(url, {
|
|
1362
|
+
method: "PATCH",
|
|
1363
|
+
headers: {
|
|
1364
|
+
"Content-Type": "application/json",
|
|
1365
|
+
},
|
|
1366
|
+
body: JSON.stringify(response.data),
|
|
1367
|
+
});
|
|
1368
|
+
if (!fetchResponse.ok) {
|
|
1369
|
+
const errorBody = await fetchResponse.text();
|
|
1370
|
+
console.error(`[MiniInteraction] Failed to send follow-up response: [${fetchResponse.status}] ${errorBody}`);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
catch (error) {
|
|
1374
|
+
console.error(`[MiniInteraction] Error sending follow-up response: ${error instanceof Error ? error.message : String(error)}`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1389
1377
|
}
|
|
1390
1378
|
const DEFAULT_DISCORD_OAUTH_TEMPLATES = {
|
|
1391
1379
|
success: ({ user }) => {
|
|
@@ -1554,7 +1542,7 @@ function resolveOAuthConfig(provided) {
|
|
|
1554
1542
|
/**
|
|
1555
1543
|
* Wraps a handler function with timeout detection and error handling.
|
|
1556
1544
|
*/
|
|
1557
|
-
function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings = true) {
|
|
1545
|
+
function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings = true, ackPromise) {
|
|
1558
1546
|
return async (...args) => {
|
|
1559
1547
|
const startTime = Date.now();
|
|
1560
1548
|
let timeoutId;
|
|
@@ -1566,10 +1554,14 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
|
|
|
1566
1554
|
}, timeoutMs);
|
|
1567
1555
|
});
|
|
1568
1556
|
try {
|
|
1569
|
-
const
|
|
1557
|
+
const promises = [
|
|
1570
1558
|
Promise.resolve(handler(...args)),
|
|
1571
1559
|
timeoutPromise,
|
|
1572
|
-
]
|
|
1560
|
+
];
|
|
1561
|
+
if (ackPromise) {
|
|
1562
|
+
promises.push(ackPromise);
|
|
1563
|
+
}
|
|
1564
|
+
const result = await Promise.race(promises);
|
|
1573
1565
|
if (timeoutId) {
|
|
1574
1566
|
clearTimeout(timeoutId);
|
|
1575
1567
|
}
|
|
@@ -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
|
}
|
|
@@ -154,7 +154,8 @@ 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
|
-
|
|
157
|
+
onAck?(response: APIInteractionResponse): void;
|
|
158
|
+
sendFollowUp?(token: string, response: APIInteractionResponse): void;
|
|
158
159
|
}
|
|
159
160
|
export declare const CommandInteraction: {};
|
|
160
161
|
/**
|
|
@@ -168,4 +169,6 @@ export declare function createCommandInteraction(interaction: APIChatInputApplic
|
|
|
168
169
|
canRespond?: (interactionId: string) => boolean;
|
|
169
170
|
trackResponse?: (interactionId: string, token: string, state: 'responded' | 'deferred') => void;
|
|
170
171
|
logTiming?: (interactionId: string, operation: string, startTime: number, success: boolean) => void;
|
|
172
|
+
onAck?: (response: APIInteractionResponse) => void;
|
|
173
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse) => void;
|
|
171
174
|
}): CommandInteraction;
|
|
@@ -379,16 +379,19 @@ export function createCommandInteraction(interaction, helpers) {
|
|
|
379
379
|
if (!this.canRespond?.(this.id)) {
|
|
380
380
|
throw new Error('Interaction cannot respond: already responded or expired');
|
|
381
381
|
}
|
|
382
|
-
const startTime = Date.now();
|
|
383
382
|
const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
|
|
384
383
|
// Track response
|
|
385
384
|
this.trackResponse?.(this.id, this.token, 'responded');
|
|
386
|
-
//
|
|
387
|
-
this.
|
|
385
|
+
// Notify acknowledgment
|
|
386
|
+
this.onAck?.(response);
|
|
388
387
|
return response;
|
|
389
388
|
},
|
|
390
389
|
followUp(data) {
|
|
391
|
-
|
|
390
|
+
const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
|
|
391
|
+
if (this.sendFollowUp) {
|
|
392
|
+
this.sendFollowUp(this.token, response);
|
|
393
|
+
}
|
|
394
|
+
return response;
|
|
392
395
|
},
|
|
393
396
|
edit(data) {
|
|
394
397
|
return createMessageResponse(InteractionResponseType.UpdateMessage, data);
|
|
@@ -398,12 +401,13 @@ export function createCommandInteraction(interaction, helpers) {
|
|
|
398
401
|
if (!this.canRespond?.(this.id)) {
|
|
399
402
|
throw new Error('Interaction cannot edit reply: already responded, expired, or not deferred');
|
|
400
403
|
}
|
|
401
|
-
const startTime = Date.now();
|
|
402
404
|
const response = createMessageResponse(InteractionResponseType.UpdateMessage, data);
|
|
405
|
+
// If it's already deferred or responded, we MUST use a webhook
|
|
406
|
+
if (this.sendFollowUp) {
|
|
407
|
+
this.sendFollowUp(this.token, response);
|
|
408
|
+
}
|
|
403
409
|
// Track response
|
|
404
410
|
this.trackResponse?.(this.id, this.token, 'responded');
|
|
405
|
-
// Log timing if debug enabled
|
|
406
|
-
this.logTiming?.(this.id, 'editReply', startTime, true);
|
|
407
411
|
return response;
|
|
408
412
|
},
|
|
409
413
|
deferReply(options) {
|
|
@@ -411,14 +415,13 @@ export function createCommandInteraction(interaction, helpers) {
|
|
|
411
415
|
if (!this.canRespond?.(this.id)) {
|
|
412
416
|
throw new Error('Interaction cannot defer: already responded or expired');
|
|
413
417
|
}
|
|
414
|
-
const startTime = Date.now();
|
|
415
418
|
const response = createDeferredResponse(options?.flags !== undefined
|
|
416
419
|
? { flags: options.flags }
|
|
417
420
|
: undefined);
|
|
418
421
|
// Track deferred state
|
|
419
422
|
this.trackResponse?.(this.id, this.token, 'deferred');
|
|
420
|
-
//
|
|
421
|
-
this.
|
|
423
|
+
// Notify acknowledgment
|
|
424
|
+
this.onAck?.(response);
|
|
422
425
|
return response;
|
|
423
426
|
},
|
|
424
427
|
showModal(data) {
|
|
@@ -469,7 +472,7 @@ export function createCommandInteraction(interaction, helpers) {
|
|
|
469
472
|
// Helper methods for state management
|
|
470
473
|
canRespond: helpers?.canRespond,
|
|
471
474
|
trackResponse: helpers?.trackResponse,
|
|
472
|
-
|
|
475
|
+
onAck: helpers?.onAck,
|
|
473
476
|
};
|
|
474
477
|
return commandInteraction;
|
|
475
478
|
}
|
|
@@ -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
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
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,8 @@ type BaseComponentInteractionHelpers = {
|
|
|
27
27
|
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
28
28
|
toJSON(): APIModalInteractionResponseCallbackData;
|
|
29
29
|
}) => APIModalInteractionResponse;
|
|
30
|
+
onAck?: (response: APIInteractionResponse) => void;
|
|
31
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse) => void;
|
|
30
32
|
};
|
|
31
33
|
/**
|
|
32
34
|
* Button interaction with helper methods.
|
|
@@ -96,6 +98,11 @@ export type MessageComponentInteraction = APIMessageComponentInteraction & {
|
|
|
96
98
|
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
97
99
|
toJSON(): APIModalInteractionResponseCallbackData;
|
|
98
100
|
}) => APIModalInteractionResponse;
|
|
101
|
+
/**
|
|
102
|
+
* Finalise the interaction response via a webhook follow-up.
|
|
103
|
+
* This is automatically called by reply() and update() if the interaction is deferred.
|
|
104
|
+
*/
|
|
105
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse) => void;
|
|
99
106
|
/**
|
|
100
107
|
* The selected values from a select menu interaction.
|
|
101
108
|
* This property is only present for select menu interactions.
|
|
@@ -135,5 +142,8 @@ export declare const MessageComponentInteraction: {};
|
|
|
135
142
|
* @param interaction - The raw interaction payload from Discord.
|
|
136
143
|
* @returns A helper-augmented interaction object.
|
|
137
144
|
*/
|
|
138
|
-
export declare function createMessageComponentInteraction(interaction: APIMessageComponentInteraction
|
|
145
|
+
export declare function createMessageComponentInteraction(interaction: APIMessageComponentInteraction, helpers?: {
|
|
146
|
+
onAck?: (response: APIInteractionResponse) => void;
|
|
147
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse) => void;
|
|
148
|
+
}): MessageComponentInteraction;
|
|
139
149
|
export {};
|
|
@@ -15,8 +15,9 @@ 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
|
+
let isDeferred = false;
|
|
20
21
|
const captureResponse = (response) => {
|
|
21
22
|
capturedResponse = response;
|
|
22
23
|
return response;
|
|
@@ -26,10 +27,17 @@ export function createMessageComponentInteraction(interaction) {
|
|
|
26
27
|
if (!normalisedData) {
|
|
27
28
|
throw new Error("[MiniInteraction] Component replies require response data to be provided.");
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
const response = captureResponse({
|
|
30
31
|
type: InteractionResponseType.ChannelMessageWithSource,
|
|
31
32
|
data: normalisedData,
|
|
32
33
|
});
|
|
34
|
+
if (isDeferred && helpers?.sendFollowUp) {
|
|
35
|
+
helpers.sendFollowUp(interaction.token, response);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
helpers?.onAck?.(response);
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
33
41
|
};
|
|
34
42
|
const deferReply = (options) => {
|
|
35
43
|
const flags = normaliseMessageFlags(options?.flags);
|
|
@@ -41,7 +49,10 @@ export function createMessageComponentInteraction(interaction) {
|
|
|
41
49
|
: {
|
|
42
50
|
type: InteractionResponseType.DeferredChannelMessageWithSource,
|
|
43
51
|
};
|
|
44
|
-
|
|
52
|
+
captureResponse(response);
|
|
53
|
+
isDeferred = true;
|
|
54
|
+
helpers?.onAck?.(response);
|
|
55
|
+
return response;
|
|
45
56
|
};
|
|
46
57
|
const update = (data) => {
|
|
47
58
|
const normalisedData = normaliseInteractionMessageData(data);
|
|
@@ -53,11 +64,19 @@ export function createMessageComponentInteraction(interaction) {
|
|
|
53
64
|
: {
|
|
54
65
|
type: InteractionResponseType.UpdateMessage,
|
|
55
66
|
};
|
|
67
|
+
if (isDeferred && helpers?.sendFollowUp) {
|
|
68
|
+
helpers.sendFollowUp(interaction.token, response);
|
|
69
|
+
}
|
|
56
70
|
return captureResponse(response);
|
|
57
71
|
};
|
|
58
|
-
const deferUpdate = () =>
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
const deferUpdate = () => {
|
|
73
|
+
const response = captureResponse({
|
|
74
|
+
type: InteractionResponseType.DeferredMessageUpdate,
|
|
75
|
+
});
|
|
76
|
+
isDeferred = true;
|
|
77
|
+
helpers?.onAck?.(response);
|
|
78
|
+
return response;
|
|
79
|
+
};
|
|
61
80
|
const showModal = (data) => {
|
|
62
81
|
const resolvedData = typeof data === "object" &&
|
|
63
82
|
"toJSON" in data &&
|
|
@@ -178,5 +197,7 @@ export function createMessageComponentInteraction(interaction) {
|
|
|
178
197
|
getChannels,
|
|
179
198
|
getUsers,
|
|
180
199
|
getMentionables,
|
|
200
|
+
onAck: helpers?.onAck,
|
|
201
|
+
sendFollowUp: helpers?.sendFollowUp,
|
|
181
202
|
});
|
|
182
203
|
}
|
|
@@ -8,28 +8,24 @@ export type ModalSubmitInteraction = APIModalSubmitInteraction & {
|
|
|
8
8
|
reply: (data: InteractionMessageData) => APIInteractionResponseChannelMessageWithSource;
|
|
9
9
|
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
10
10
|
/**
|
|
11
|
-
* Helper method to get the value of a text input component by
|
|
12
|
-
* @param customId - The custom_id of the text input component
|
|
13
|
-
* @returns The value of the text input, or undefined if not found
|
|
11
|
+
* Helper method to get the value of a text input component by its custom ID.
|
|
14
12
|
*/
|
|
15
|
-
|
|
13
|
+
getTextFieldValue: (customId: string) => string | undefined;
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Finalise the interaction response via a webhook follow-up.
|
|
16
|
+
* This is automatically called by reply() if the interaction is deferred.
|
|
19
17
|
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Helper method to get the selected values of a select menu component by custom_id.
|
|
23
|
-
* @param customId - The custom_id of the select menu component
|
|
24
|
-
* @returns The selected values of the select menu, or undefined if not found
|
|
25
|
-
*/
|
|
26
|
-
getSelectMenuValues: (customId: string) => string[] | undefined;
|
|
18
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse) => void;
|
|
27
19
|
};
|
|
28
20
|
export declare const ModalSubmitInteraction: {};
|
|
29
21
|
/**
|
|
30
|
-
* Wraps a raw modal submit interaction with helper methods.
|
|
22
|
+
* Wraps a raw modal submit interaction with helper methods mirroring Discord's expected responses.
|
|
31
23
|
*
|
|
32
24
|
* @param interaction - The raw interaction payload from Discord.
|
|
25
|
+
* @param helpers - Optional callback to capture the final interaction response.
|
|
33
26
|
* @returns A helper-augmented interaction object.
|
|
34
27
|
*/
|
|
35
|
-
export declare function createModalSubmitInteraction(interaction: APIModalSubmitInteraction
|
|
28
|
+
export declare function createModalSubmitInteraction(interaction: APIModalSubmitInteraction, helpers?: {
|
|
29
|
+
onAck?: (response: APIInteractionResponse) => void;
|
|
30
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse) => void;
|
|
31
|
+
}): ModalSubmitInteraction;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { InteractionResponseType, } from "discord-api-types/v10";
|
|
1
|
+
import { ComponentType, InteractionResponseType, } from "discord-api-types/v10";
|
|
2
2
|
import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
|
|
3
3
|
export const ModalSubmitInteraction = {};
|
|
4
4
|
/**
|
|
5
|
-
* Wraps a raw modal submit interaction with helper methods.
|
|
5
|
+
* Wraps a raw modal submit interaction with helper methods mirroring Discord's expected responses.
|
|
6
6
|
*
|
|
7
7
|
* @param interaction - The raw interaction payload from Discord.
|
|
8
|
+
* @param helpers - Optional callback to capture the final interaction response.
|
|
8
9
|
* @returns A helper-augmented interaction object.
|
|
9
10
|
*/
|
|
10
|
-
export function createModalSubmitInteraction(interaction) {
|
|
11
|
+
export function createModalSubmitInteraction(interaction, helpers) {
|
|
11
12
|
let capturedResponse = null;
|
|
13
|
+
let isDeferred = false;
|
|
12
14
|
const captureResponse = (response) => {
|
|
13
15
|
capturedResponse = response;
|
|
14
16
|
return response;
|
|
@@ -16,12 +18,19 @@ export function createModalSubmitInteraction(interaction) {
|
|
|
16
18
|
const reply = (data) => {
|
|
17
19
|
const normalisedData = normaliseInteractionMessageData(data);
|
|
18
20
|
if (!normalisedData) {
|
|
19
|
-
throw new Error("[MiniInteraction] Modal
|
|
21
|
+
throw new Error("[MiniInteraction] Modal replies require response data to be provided.");
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
const response = captureResponse({
|
|
22
24
|
type: InteractionResponseType.ChannelMessageWithSource,
|
|
23
25
|
data: normalisedData,
|
|
24
26
|
});
|
|
27
|
+
if (isDeferred && helpers?.sendFollowUp) {
|
|
28
|
+
helpers.sendFollowUp(interaction.token, response);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
helpers?.onAck?.(response);
|
|
32
|
+
}
|
|
33
|
+
return response;
|
|
25
34
|
};
|
|
26
35
|
const deferReply = (options) => {
|
|
27
36
|
const flags = normaliseMessageFlags(options?.flags);
|
|
@@ -33,67 +42,30 @@ export function createModalSubmitInteraction(interaction) {
|
|
|
33
42
|
: {
|
|
34
43
|
type: InteractionResponseType.DeferredChannelMessageWithSource,
|
|
35
44
|
};
|
|
36
|
-
|
|
45
|
+
captureResponse(response);
|
|
46
|
+
isDeferred = true;
|
|
47
|
+
helpers?.onAck?.(response);
|
|
48
|
+
return response;
|
|
37
49
|
};
|
|
38
50
|
const getResponse = () => capturedResponse;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if ("value" in child && "custom_id" in child) {
|
|
47
|
-
textInputs.set(child.custom_id, child.value);
|
|
51
|
+
const getTextFieldValue = (customId) => {
|
|
52
|
+
for (const actionRow of interaction.data.components) {
|
|
53
|
+
if ("components" in actionRow && Array.isArray(actionRow.components)) {
|
|
54
|
+
for (const component of actionRow.components) {
|
|
55
|
+
if (component.type === ComponentType.TextInput &&
|
|
56
|
+
component.custom_id === customId) {
|
|
57
|
+
return component.value;
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
|
-
// Handle labeled components
|
|
52
|
-
else if ("component" in component) {
|
|
53
|
-
const labeledComponent = component.component;
|
|
54
|
-
if ("value" in labeledComponent && "custom_id" in labeledComponent) {
|
|
55
|
-
textInputs.set(labeledComponent.custom_id, labeledComponent.value);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
61
|
}
|
|
59
|
-
return
|
|
60
|
-
};
|
|
61
|
-
// Helper to extract select menu values from modal components
|
|
62
|
-
const extractSelectMenuValues = () => {
|
|
63
|
-
const selectMenuValues = new Map();
|
|
64
|
-
for (const component of interaction.data.components) {
|
|
65
|
-
// Handle action rows
|
|
66
|
-
if ("components" in component && Array.isArray(component.components)) {
|
|
67
|
-
for (const child of component.components) {
|
|
68
|
-
if ("values" in child && "custom_id" in child && Array.isArray(child.values)) {
|
|
69
|
-
selectMenuValues.set(child.custom_id, child.values);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// Handle labeled components (unlikely for select menus but good for completeness if spec allows)
|
|
74
|
-
else if ("component" in component) {
|
|
75
|
-
const labeledComponent = component.component; // Using any as ModalSubmitComponent might not cover select menus fully in types yet or strictness varies
|
|
76
|
-
if ("values" in labeledComponent && "custom_id" in labeledComponent && Array.isArray(labeledComponent.values)) {
|
|
77
|
-
selectMenuValues.set(labeledComponent.custom_id, labeledComponent.values);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return selectMenuValues;
|
|
82
|
-
};
|
|
83
|
-
const textInputValues = extractTextInputs();
|
|
84
|
-
const selectMenuValues = extractSelectMenuValues();
|
|
85
|
-
const getTextInputValue = (customId) => {
|
|
86
|
-
return textInputValues.get(customId);
|
|
87
|
-
};
|
|
88
|
-
const getTextInputValues = () => {
|
|
89
|
-
return new Map(textInputValues);
|
|
62
|
+
return undefined;
|
|
90
63
|
};
|
|
91
64
|
return Object.assign(interaction, {
|
|
92
65
|
reply,
|
|
93
66
|
deferReply,
|
|
94
67
|
getResponse,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
getSelectMenuValues: (customId) => selectMenuValues.get(customId),
|
|
68
|
+
getTextFieldValue,
|
|
69
|
+
sendFollowUp: helpers?.sendFollowUp,
|
|
98
70
|
});
|
|
99
71
|
}
|
package/package.json
CHANGED