@minesa-org/mini-interaction 0.2.4 → 0.2.6
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 -0
- package/dist/clients/MiniInteraction.js +39 -4
- package/dist/utils/CommandInteractionOptions.d.ts +4 -2
- package/dist/utils/CommandInteractionOptions.js +18 -10
- package/dist/utils/MessageComponentInteraction.d.ts +66 -21
- package/dist/utils/MessageComponentInteraction.js +29 -9
- package/dist/utils/ModalSubmitInteraction.d.ts +9 -16
- package/dist/utils/ModalSubmitInteraction.js +23 -56
- package/dist/utils/interactionMessageHelpers.js +7 -0
- package/package.json +1 -1
|
@@ -394,5 +394,10 @@ export declare class MiniInteraction {
|
|
|
394
394
|
* Handles execution of an application command interaction.
|
|
395
395
|
*/
|
|
396
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;
|
|
397
402
|
}
|
|
398
403
|
export {};
|
|
@@ -101,10 +101,6 @@ export class MiniInteraction {
|
|
|
101
101
|
this.trackInteractionState(interactionId, state.token, 'expired');
|
|
102
102
|
return false;
|
|
103
103
|
}
|
|
104
|
-
// Check if already responded
|
|
105
|
-
if (state.state === 'responded') {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
104
|
return true;
|
|
109
105
|
}
|
|
110
106
|
/**
|
|
@@ -1125,8 +1121,11 @@ export class MiniInteraction {
|
|
|
1125
1121
|
const ackPromise = new Promise((resolve) => {
|
|
1126
1122
|
ackResolver = resolve;
|
|
1127
1123
|
});
|
|
1124
|
+
// Helper to send follow-up responses via webhooks
|
|
1125
|
+
const sendFollowUp = (token, data, messageId = '@original') => this.sendFollowUp(token, data, messageId);
|
|
1128
1126
|
const interactionWithHelpers = createMessageComponentInteraction(interaction, {
|
|
1129
1127
|
onAck: (response) => ackResolver?.(response),
|
|
1128
|
+
sendFollowUp,
|
|
1130
1129
|
});
|
|
1131
1130
|
// Wrap component handler with timeout and acknowledgment
|
|
1132
1131
|
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
@@ -1187,8 +1186,11 @@ export class MiniInteraction {
|
|
|
1187
1186
|
const ackPromise = new Promise((resolve) => {
|
|
1188
1187
|
ackResolver = resolve;
|
|
1189
1188
|
});
|
|
1189
|
+
// Helper to send follow-up responses via webhooks
|
|
1190
|
+
const sendFollowUp = (token, data, messageId = '@original') => this.sendFollowUp(token, data, messageId);
|
|
1190
1191
|
const interactionWithHelpers = createModalSubmitInteraction(interaction, {
|
|
1191
1192
|
onAck: (response) => ackResolver?.(response),
|
|
1193
|
+
sendFollowUp,
|
|
1192
1194
|
});
|
|
1193
1195
|
// Wrap modal handler with timeout and acknowledgment
|
|
1194
1196
|
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
@@ -1252,6 +1254,8 @@ export class MiniInteraction {
|
|
|
1252
1254
|
const ackPromise = new Promise((resolve) => {
|
|
1253
1255
|
ackResolver = resolve;
|
|
1254
1256
|
});
|
|
1257
|
+
// Helper to send follow-up responses via webhooks
|
|
1258
|
+
const sendFollowUp = (token, data, messageId = '@original') => this.sendFollowUp(token, data, messageId);
|
|
1255
1259
|
// Create a timeout wrapper for the command handler
|
|
1256
1260
|
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
1257
1261
|
// Check if it's a chat input (slash) command
|
|
@@ -1261,6 +1265,7 @@ export class MiniInteraction {
|
|
|
1261
1265
|
canRespond: (id) => this.canRespond(id),
|
|
1262
1266
|
trackResponse: (id, token, state) => this.trackInteractionState(id, token, state),
|
|
1263
1267
|
onAck: (response) => ackResolver?.(response),
|
|
1268
|
+
sendFollowUp,
|
|
1264
1269
|
});
|
|
1265
1270
|
response = await command.handler(interactionWithHelpers);
|
|
1266
1271
|
resolvedResponse =
|
|
@@ -1338,6 +1343,36 @@ export class MiniInteraction {
|
|
|
1338
1343
|
};
|
|
1339
1344
|
}
|
|
1340
1345
|
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Sends a follow-up response or edits an existing response via Discord's interaction webhooks.
|
|
1348
|
+
* This is used for interactions that have already been acknowledged (e.g., via deferReply).
|
|
1349
|
+
*/
|
|
1350
|
+
async sendFollowUp(token, response, messageId = "@original") {
|
|
1351
|
+
const isEdit = messageId !== "";
|
|
1352
|
+
const url = isEdit
|
|
1353
|
+
? `${DISCORD_BASE_URL}/webhooks/${this.applicationId}/${token}/messages/${messageId}`
|
|
1354
|
+
: `${DISCORD_BASE_URL}/webhooks/${this.applicationId}/${token}`;
|
|
1355
|
+
// Only send follow-up if there is data to send
|
|
1356
|
+
if (!('data' in response) || !response.data) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
try {
|
|
1360
|
+
const fetchResponse = await this.fetchImpl(url, {
|
|
1361
|
+
method: isEdit ? "PATCH" : "POST",
|
|
1362
|
+
headers: {
|
|
1363
|
+
"Content-Type": "application/json",
|
|
1364
|
+
},
|
|
1365
|
+
body: JSON.stringify(response.data),
|
|
1366
|
+
});
|
|
1367
|
+
if (!fetchResponse.ok) {
|
|
1368
|
+
const errorBody = await fetchResponse.text();
|
|
1369
|
+
console.error(`[MiniInteraction] Failed to send follow-up response: [${fetchResponse.status}] ${errorBody}`);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
catch (error) {
|
|
1373
|
+
console.error(`[MiniInteraction] Error sending follow-up response: ${error instanceof Error ? error.message : String(error)}`);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1341
1376
|
}
|
|
1342
1377
|
const DEFAULT_DISCORD_OAUTH_TEMPLATES = {
|
|
1343
1378
|
success: ({ user }) => {
|
|
@@ -144,9 +144,9 @@ export interface CommandInteraction extends Omit<APIChatInputApplicationCommandI
|
|
|
144
144
|
options: CommandInteractionOptionResolver;
|
|
145
145
|
getResponse(): APIInteractionResponse | null;
|
|
146
146
|
reply(data: InteractionMessageData): APIInteractionResponseChannelMessageWithSource;
|
|
147
|
-
followUp(data: InteractionMessageData): APIInteractionResponseChannelMessageWithSource
|
|
147
|
+
followUp(data: InteractionMessageData): Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
148
148
|
edit(data?: InteractionMessageData): APIInteractionResponseUpdateMessage;
|
|
149
|
-
editReply(data?: InteractionMessageData): APIInteractionResponseUpdateMessage
|
|
149
|
+
editReply(data?: InteractionMessageData): Promise<APIInteractionResponseUpdateMessage>;
|
|
150
150
|
deferReply(options?: DeferReplyOptions): APIInteractionResponseDeferredChannelMessageWithSource;
|
|
151
151
|
showModal(data: APIModalInteractionResponseCallbackData | {
|
|
152
152
|
toJSON(): APIModalInteractionResponseCallbackData;
|
|
@@ -155,6 +155,7 @@ export interface CommandInteraction extends Omit<APIChatInputApplicationCommandI
|
|
|
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, messageId?: string): Promise<void>;
|
|
158
159
|
}
|
|
159
160
|
export declare const CommandInteraction: {};
|
|
160
161
|
/**
|
|
@@ -169,4 +170,5 @@ export declare function createCommandInteraction(interaction: APIChatInputApplic
|
|
|
169
170
|
trackResponse?: (interactionId: string, token: string, state: 'responded' | 'deferred') => void;
|
|
170
171
|
logTiming?: (interactionId: string, operation: string, startTime: number, success: boolean) => void;
|
|
171
172
|
onAck?: (response: APIInteractionResponse) => void;
|
|
173
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse, messageId?: string) => Promise<void>;
|
|
172
174
|
}): CommandInteraction;
|
|
@@ -327,6 +327,8 @@ export const CommandInteraction = {};
|
|
|
327
327
|
export function createCommandInteraction(interaction, helpers) {
|
|
328
328
|
const options = new CommandInteractionOptionResolver(interaction.data.options, interaction.data.resolved);
|
|
329
329
|
let capturedResponse = null;
|
|
330
|
+
let isDeferred = false;
|
|
331
|
+
let hasResponded = false;
|
|
330
332
|
/**
|
|
331
333
|
* Stores the most recent response helper payload for later retrieval.
|
|
332
334
|
*/
|
|
@@ -379,31 +381,38 @@ export function createCommandInteraction(interaction, helpers) {
|
|
|
379
381
|
if (!this.canRespond?.(this.id)) {
|
|
380
382
|
throw new Error('Interaction cannot respond: already responded or expired');
|
|
381
383
|
}
|
|
382
|
-
const startTime = Date.now();
|
|
383
384
|
const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
|
|
384
385
|
// Track response
|
|
385
386
|
this.trackResponse?.(this.id, this.token, 'responded');
|
|
387
|
+
hasResponded = true;
|
|
386
388
|
// Notify acknowledgment
|
|
387
389
|
this.onAck?.(response);
|
|
388
|
-
// Log timing if debug enabled
|
|
389
390
|
return response;
|
|
390
391
|
},
|
|
391
|
-
followUp(data) {
|
|
392
|
-
|
|
392
|
+
async followUp(data) {
|
|
393
|
+
const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
|
|
394
|
+
if (this.sendFollowUp) {
|
|
395
|
+
// Empty string for messageId means a new follow-up (POST)
|
|
396
|
+
await this.sendFollowUp(this.token, response, '');
|
|
397
|
+
}
|
|
398
|
+
return response;
|
|
393
399
|
},
|
|
394
400
|
edit(data) {
|
|
395
401
|
return createMessageResponse(InteractionResponseType.UpdateMessage, data);
|
|
396
402
|
},
|
|
397
|
-
editReply(data) {
|
|
403
|
+
async editReply(data) {
|
|
398
404
|
// Validate interaction can respond
|
|
399
405
|
if (!this.canRespond?.(this.id)) {
|
|
400
|
-
throw new Error('Interaction cannot edit reply:
|
|
406
|
+
throw new Error('Interaction cannot edit reply: expired');
|
|
401
407
|
}
|
|
402
|
-
const startTime = Date.now();
|
|
403
408
|
const response = createMessageResponse(InteractionResponseType.UpdateMessage, data);
|
|
409
|
+
// If it's already deferred or responded, we MUST use a webhook
|
|
410
|
+
if (this.sendFollowUp && (isDeferred || hasResponded)) {
|
|
411
|
+
await this.sendFollowUp(this.token, response, '@original');
|
|
412
|
+
}
|
|
404
413
|
// Track response
|
|
405
414
|
this.trackResponse?.(this.id, this.token, 'responded');
|
|
406
|
-
|
|
415
|
+
hasResponded = true;
|
|
407
416
|
return response;
|
|
408
417
|
},
|
|
409
418
|
deferReply(options) {
|
|
@@ -411,15 +420,14 @@ export function createCommandInteraction(interaction, helpers) {
|
|
|
411
420
|
if (!this.canRespond?.(this.id)) {
|
|
412
421
|
throw new Error('Interaction cannot defer: already responded or expired');
|
|
413
422
|
}
|
|
414
|
-
const startTime = Date.now();
|
|
415
423
|
const response = createDeferredResponse(options?.flags !== undefined
|
|
416
424
|
? { flags: options.flags }
|
|
417
425
|
: undefined);
|
|
418
426
|
// Track deferred state
|
|
419
427
|
this.trackResponse?.(this.id, this.token, 'deferred');
|
|
428
|
+
isDeferred = true;
|
|
420
429
|
// Notify acknowledgment
|
|
421
430
|
this.onAck?.(response);
|
|
422
|
-
// Log timing if debug enabled
|
|
423
431
|
return response;
|
|
424
432
|
},
|
|
425
433
|
showModal(data) {
|
|
@@ -18,68 +18,110 @@ export declare const ResolvedMentionableOption: {};
|
|
|
18
18
|
/**
|
|
19
19
|
* Base helper methods available on all component interactions.
|
|
20
20
|
*/
|
|
21
|
-
type BaseComponentInteractionHelpers = {
|
|
22
|
-
|
|
23
|
-
reply: (data: InteractionMessageData) => APIInteractionResponseChannelMessageWithSource;
|
|
24
|
-
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
25
|
-
update: (data?: InteractionMessageData) => APIInteractionResponseUpdateMessage;
|
|
26
|
-
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
27
|
-
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
28
|
-
toJSON(): APIModalInteractionResponseCallbackData;
|
|
29
|
-
}) => APIModalInteractionResponse;
|
|
21
|
+
export type BaseComponentInteractionHelpers = {
|
|
22
|
+
trackTiming?: (interactionId: string, operation: string, startTime: number, success: boolean) => void;
|
|
30
23
|
onAck?: (response: APIInteractionResponse) => void;
|
|
24
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse, messageId?: string) => Promise<void>;
|
|
31
25
|
};
|
|
32
26
|
/**
|
|
33
27
|
* Button interaction with helper methods.
|
|
34
28
|
* Buttons don't have values or resolved data.
|
|
35
29
|
*/
|
|
36
|
-
export interface ButtonInteraction extends Omit<APIMessageComponentInteraction, "data"
|
|
30
|
+
export interface ButtonInteraction extends Omit<APIMessageComponentInteraction, "data"> {
|
|
37
31
|
data: APIMessageButtonInteractionData;
|
|
32
|
+
getResponse: () => APIInteractionResponse | null;
|
|
33
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
34
|
+
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
35
|
+
update: (data?: InteractionMessageData) => Promise<APIInteractionResponseUpdateMessage>;
|
|
36
|
+
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
37
|
+
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
38
|
+
toJSON(): APIModalInteractionResponseCallbackData;
|
|
39
|
+
}) => APIModalInteractionResponse;
|
|
38
40
|
}
|
|
39
41
|
export declare const ButtonInteraction: {};
|
|
40
42
|
/**
|
|
41
43
|
* String select menu interaction with helper methods.
|
|
42
44
|
*/
|
|
43
|
-
export interface StringSelectInteraction extends Omit<APIMessageComponentInteraction, "data"
|
|
45
|
+
export interface StringSelectInteraction extends Omit<APIMessageComponentInteraction, "data"> {
|
|
44
46
|
data: APIMessageStringSelectInteractionData;
|
|
45
47
|
values: string[];
|
|
46
48
|
getStringValues: () => string[];
|
|
49
|
+
getResponse: () => APIInteractionResponse | null;
|
|
50
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
51
|
+
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
52
|
+
update: (data?: InteractionMessageData) => Promise<APIInteractionResponseUpdateMessage>;
|
|
53
|
+
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
54
|
+
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
55
|
+
toJSON(): APIModalInteractionResponseCallbackData;
|
|
56
|
+
}) => APIModalInteractionResponse;
|
|
47
57
|
}
|
|
48
58
|
export declare const StringSelectInteraction: {};
|
|
49
59
|
/**
|
|
50
60
|
* Role select menu interaction with helper methods.
|
|
51
61
|
*/
|
|
52
|
-
export interface RoleSelectInteraction extends Omit<APIMessageComponentInteraction, "data"
|
|
62
|
+
export interface RoleSelectInteraction extends Omit<APIMessageComponentInteraction, "data"> {
|
|
53
63
|
data: APIMessageRoleSelectInteractionData;
|
|
54
64
|
values: string[];
|
|
55
65
|
getRoles: () => APIRole[];
|
|
66
|
+
getResponse: () => APIInteractionResponse | null;
|
|
67
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
68
|
+
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
69
|
+
update: (data?: InteractionMessageData) => Promise<APIInteractionResponseUpdateMessage>;
|
|
70
|
+
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
71
|
+
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
72
|
+
toJSON(): APIModalInteractionResponseCallbackData;
|
|
73
|
+
}) => APIModalInteractionResponse;
|
|
56
74
|
}
|
|
57
75
|
export declare const RoleSelectInteraction: {};
|
|
58
76
|
/**
|
|
59
77
|
* User select menu interaction with helper methods.
|
|
60
78
|
*/
|
|
61
|
-
export interface UserSelectInteraction extends Omit<APIMessageComponentInteraction, "data"
|
|
79
|
+
export interface UserSelectInteraction extends Omit<APIMessageComponentInteraction, "data"> {
|
|
62
80
|
data: APIMessageUserSelectInteractionData;
|
|
63
81
|
values: string[];
|
|
64
82
|
getUsers: () => ResolvedUserOption[];
|
|
83
|
+
getResponse: () => APIInteractionResponse | null;
|
|
84
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
85
|
+
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
86
|
+
update: (data?: InteractionMessageData) => Promise<APIInteractionResponseUpdateMessage>;
|
|
87
|
+
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
88
|
+
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
89
|
+
toJSON(): APIModalInteractionResponseCallbackData;
|
|
90
|
+
}) => APIModalInteractionResponse;
|
|
65
91
|
}
|
|
66
92
|
export declare const UserSelectInteraction: {};
|
|
67
93
|
/**
|
|
68
94
|
* Channel select menu interaction with helper methods.
|
|
69
95
|
*/
|
|
70
|
-
export interface ChannelSelectInteraction extends Omit<APIMessageComponentInteraction, "data"
|
|
96
|
+
export interface ChannelSelectInteraction extends Omit<APIMessageComponentInteraction, "data"> {
|
|
71
97
|
data: APIMessageChannelSelectInteractionData;
|
|
72
98
|
values: string[];
|
|
73
99
|
getChannels: () => APIInteractionDataResolvedChannel[];
|
|
100
|
+
getResponse: () => APIInteractionResponse | null;
|
|
101
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
102
|
+
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
103
|
+
update: (data?: InteractionMessageData) => Promise<APIInteractionResponseUpdateMessage>;
|
|
104
|
+
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
105
|
+
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
106
|
+
toJSON(): APIModalInteractionResponseCallbackData;
|
|
107
|
+
}) => APIModalInteractionResponse;
|
|
74
108
|
}
|
|
75
109
|
export declare const ChannelSelectInteraction: {};
|
|
76
110
|
/**
|
|
77
111
|
* Mentionable select menu interaction with helper methods.
|
|
78
112
|
*/
|
|
79
|
-
export interface MentionableSelectInteraction extends Omit<APIMessageComponentInteraction, "data"
|
|
113
|
+
export interface MentionableSelectInteraction extends Omit<APIMessageComponentInteraction, "data"> {
|
|
80
114
|
data: APIMessageMentionableSelectInteractionData;
|
|
81
115
|
values: string[];
|
|
82
116
|
getMentionables: () => ResolvedMentionableOption[];
|
|
117
|
+
getResponse: () => APIInteractionResponse | null;
|
|
118
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
119
|
+
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
120
|
+
update: (data?: InteractionMessageData) => Promise<APIInteractionResponseUpdateMessage>;
|
|
121
|
+
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
122
|
+
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
123
|
+
toJSON(): APIModalInteractionResponseCallbackData;
|
|
124
|
+
}) => APIModalInteractionResponse;
|
|
83
125
|
}
|
|
84
126
|
export declare const MentionableSelectInteraction: {};
|
|
85
127
|
/**
|
|
@@ -90,13 +132,18 @@ export declare const MentionableSelectInteraction: {};
|
|
|
90
132
|
*/
|
|
91
133
|
export type MessageComponentInteraction = APIMessageComponentInteraction & {
|
|
92
134
|
getResponse: () => APIInteractionResponse | null;
|
|
93
|
-
reply: (data: InteractionMessageData) => APIInteractionResponseChannelMessageWithSource
|
|
135
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
94
136
|
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
95
|
-
update: (data?: InteractionMessageData) => APIInteractionResponseUpdateMessage
|
|
137
|
+
update: (data?: InteractionMessageData) => Promise<APIInteractionResponseUpdateMessage>;
|
|
96
138
|
deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
|
|
97
139
|
showModal: (data: APIModalInteractionResponseCallbackData | {
|
|
98
140
|
toJSON(): APIModalInteractionResponseCallbackData;
|
|
99
141
|
}) => APIModalInteractionResponse;
|
|
142
|
+
/**
|
|
143
|
+
* Finalise the interaction response via a webhook follow-up.
|
|
144
|
+
* This is automatically called by reply() and update() if the interaction is deferred.
|
|
145
|
+
*/
|
|
146
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse, messageId?: string) => Promise<void>;
|
|
100
147
|
/**
|
|
101
148
|
* The selected values from a select menu interaction.
|
|
102
149
|
* This property is only present for select menu interactions.
|
|
@@ -134,9 +181,7 @@ export declare const MessageComponentInteraction: {};
|
|
|
134
181
|
* Wraps a raw component interaction with helper methods mirroring Discord's expected responses.
|
|
135
182
|
*
|
|
136
183
|
* @param interaction - The raw interaction payload from Discord.
|
|
184
|
+
* @param helpers - Optional callback to capture the final interaction response.
|
|
137
185
|
* @returns A helper-augmented interaction object.
|
|
138
186
|
*/
|
|
139
|
-
export declare function createMessageComponentInteraction(interaction: APIMessageComponentInteraction, helpers?:
|
|
140
|
-
onAck?: (response: APIInteractionResponse) => void;
|
|
141
|
-
}): MessageComponentInteraction;
|
|
142
|
-
export {};
|
|
187
|
+
export declare function createMessageComponentInteraction(interaction: APIMessageComponentInteraction, helpers?: BaseComponentInteractionHelpers): MessageComponentInteraction;
|
|
@@ -13,15 +13,17 @@ export const MessageComponentInteraction = {};
|
|
|
13
13
|
* Wraps a raw component interaction with helper methods mirroring Discord's expected responses.
|
|
14
14
|
*
|
|
15
15
|
* @param interaction - The raw interaction payload from Discord.
|
|
16
|
+
* @param helpers - Optional callback to capture the final interaction response.
|
|
16
17
|
* @returns A helper-augmented interaction object.
|
|
17
18
|
*/
|
|
18
19
|
export function createMessageComponentInteraction(interaction, helpers) {
|
|
19
20
|
let capturedResponse = null;
|
|
21
|
+
let isDeferred = false;
|
|
20
22
|
const captureResponse = (response) => {
|
|
21
23
|
capturedResponse = response;
|
|
22
24
|
return response;
|
|
23
25
|
};
|
|
24
|
-
const reply = (data) => {
|
|
26
|
+
const reply = async (data) => {
|
|
25
27
|
const normalisedData = normaliseInteractionMessageData(data);
|
|
26
28
|
if (!normalisedData) {
|
|
27
29
|
throw new Error("[MiniInteraction] Component replies require response data to be provided.");
|
|
@@ -30,7 +32,12 @@ export function createMessageComponentInteraction(interaction, helpers) {
|
|
|
30
32
|
type: InteractionResponseType.ChannelMessageWithSource,
|
|
31
33
|
data: normalisedData,
|
|
32
34
|
});
|
|
33
|
-
helpers?.
|
|
35
|
+
if (isDeferred && helpers?.sendFollowUp) {
|
|
36
|
+
await helpers.sendFollowUp(interaction.token, response, '');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
helpers?.onAck?.(response);
|
|
40
|
+
}
|
|
34
41
|
return response;
|
|
35
42
|
};
|
|
36
43
|
const deferReply = (options) => {
|
|
@@ -44,24 +51,36 @@ export function createMessageComponentInteraction(interaction, helpers) {
|
|
|
44
51
|
type: InteractionResponseType.DeferredChannelMessageWithSource,
|
|
45
52
|
};
|
|
46
53
|
captureResponse(response);
|
|
54
|
+
isDeferred = true;
|
|
47
55
|
helpers?.onAck?.(response);
|
|
48
56
|
return response;
|
|
49
57
|
};
|
|
50
|
-
const update = (data) => {
|
|
58
|
+
const update = async (data) => {
|
|
51
59
|
const normalisedData = normaliseInteractionMessageData(data);
|
|
52
|
-
const response = normalisedData
|
|
60
|
+
const response = captureResponse(normalisedData
|
|
53
61
|
? {
|
|
54
62
|
type: InteractionResponseType.UpdateMessage,
|
|
55
63
|
data: normalisedData,
|
|
56
64
|
}
|
|
57
65
|
: {
|
|
58
66
|
type: InteractionResponseType.UpdateMessage,
|
|
59
|
-
};
|
|
60
|
-
|
|
67
|
+
});
|
|
68
|
+
if (isDeferred && helpers?.sendFollowUp) {
|
|
69
|
+
await helpers.sendFollowUp(interaction.token, response, '@original');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
helpers?.onAck?.(response);
|
|
73
|
+
}
|
|
74
|
+
return response;
|
|
75
|
+
};
|
|
76
|
+
const deferUpdate = () => {
|
|
77
|
+
const response = captureResponse({
|
|
78
|
+
type: InteractionResponseType.DeferredMessageUpdate,
|
|
79
|
+
});
|
|
80
|
+
isDeferred = true;
|
|
81
|
+
helpers?.onAck?.(response);
|
|
82
|
+
return response;
|
|
61
83
|
};
|
|
62
|
-
const deferUpdate = () => captureResponse({
|
|
63
|
-
type: InteractionResponseType.DeferredMessageUpdate,
|
|
64
|
-
});
|
|
65
84
|
const showModal = (data) => {
|
|
66
85
|
const resolvedData = typeof data === "object" &&
|
|
67
86
|
"toJSON" in data &&
|
|
@@ -183,5 +202,6 @@ export function createMessageComponentInteraction(interaction, helpers) {
|
|
|
183
202
|
getUsers,
|
|
184
203
|
getMentionables,
|
|
185
204
|
onAck: helpers?.onAck,
|
|
205
|
+
sendFollowUp: helpers?.sendFollowUp,
|
|
186
206
|
});
|
|
187
207
|
}
|
|
@@ -5,34 +5,27 @@ import { DeferReplyOptions, InteractionMessageData } from "./interactionMessageH
|
|
|
5
5
|
*/
|
|
6
6
|
export type ModalSubmitInteraction = APIModalSubmitInteraction & {
|
|
7
7
|
getResponse: () => APIInteractionResponse | null;
|
|
8
|
-
reply: (data: InteractionMessageData) => APIInteractionResponseChannelMessageWithSource
|
|
8
|
+
reply: (data: InteractionMessageData) => Promise<APIInteractionResponseChannelMessageWithSource>;
|
|
9
9
|
deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
|
|
10
|
-
onAck?: (response: APIInteractionResponse) => void;
|
|
11
|
-
/**
|
|
12
|
-
* Helper method to get the value of a text input component by custom_id.
|
|
13
|
-
* @param customId - The custom_id of the text input component
|
|
14
|
-
* @returns The value of the text input, or undefined if not found
|
|
15
|
-
*/
|
|
16
|
-
getTextInputValue: (customId: string) => string | undefined;
|
|
17
10
|
/**
|
|
18
|
-
* Helper method to get
|
|
19
|
-
* @returns A map of custom_id to value for all text inputs
|
|
11
|
+
* Helper method to get the value of a text input component by its custom ID.
|
|
20
12
|
*/
|
|
21
|
-
|
|
13
|
+
getTextFieldValue: (customId: string) => string | undefined;
|
|
22
14
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @returns The selected values of the select menu, or undefined if not found
|
|
15
|
+
* Finalise the interaction response via a webhook follow-up.
|
|
16
|
+
* This is automatically called by reply() if the interaction is deferred.
|
|
26
17
|
*/
|
|
27
|
-
|
|
18
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse, messageId?: string) => Promise<void>;
|
|
28
19
|
};
|
|
29
20
|
export declare const ModalSubmitInteraction: {};
|
|
30
21
|
/**
|
|
31
|
-
* Wraps a raw modal submit interaction with helper methods.
|
|
22
|
+
* Wraps a raw modal submit interaction with helper methods mirroring Discord's expected responses.
|
|
32
23
|
*
|
|
33
24
|
* @param interaction - The raw interaction payload from Discord.
|
|
25
|
+
* @param helpers - Optional callback to capture the final interaction response.
|
|
34
26
|
* @returns A helper-augmented interaction object.
|
|
35
27
|
*/
|
|
36
28
|
export declare function createModalSubmitInteraction(interaction: APIModalSubmitInteraction, helpers?: {
|
|
37
29
|
onAck?: (response: APIInteractionResponse) => void;
|
|
30
|
+
sendFollowUp?: (token: string, response: APIInteractionResponse, messageId?: string) => Promise<void>;
|
|
38
31
|
}): ModalSubmitInteraction;
|
|
@@ -1,28 +1,35 @@
|
|
|
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
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;
|
|
15
17
|
};
|
|
16
|
-
const reply = (data) => {
|
|
18
|
+
const reply = async (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
|
});
|
|
25
|
-
helpers?.
|
|
27
|
+
if (isDeferred && helpers?.sendFollowUp) {
|
|
28
|
+
await helpers.sendFollowUp(interaction.token, response, '');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
helpers?.onAck?.(response);
|
|
32
|
+
}
|
|
26
33
|
return response;
|
|
27
34
|
};
|
|
28
35
|
const deferReply = (options) => {
|
|
@@ -36,69 +43,29 @@ export function createModalSubmitInteraction(interaction, helpers) {
|
|
|
36
43
|
type: InteractionResponseType.DeferredChannelMessageWithSource,
|
|
37
44
|
};
|
|
38
45
|
captureResponse(response);
|
|
46
|
+
isDeferred = true;
|
|
39
47
|
helpers?.onAck?.(response);
|
|
40
48
|
return response;
|
|
41
49
|
};
|
|
42
50
|
const getResponse = () => capturedResponse;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if ("value" in child && "custom_id" in child) {
|
|
51
|
-
textInputs.set(child.custom_id, child.value);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// Handle labeled components
|
|
56
|
-
else if ("component" in component) {
|
|
57
|
-
const labeledComponent = component.component;
|
|
58
|
-
if ("value" in labeledComponent && "custom_id" in labeledComponent) {
|
|
59
|
-
textInputs.set(labeledComponent.custom_id, labeledComponent.value);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return textInputs;
|
|
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);
|
|
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;
|
|
74
58
|
}
|
|
75
59
|
}
|
|
76
60
|
}
|
|
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
61
|
}
|
|
85
|
-
return
|
|
86
|
-
};
|
|
87
|
-
const textInputValues = extractTextInputs();
|
|
88
|
-
const selectMenuValues = extractSelectMenuValues();
|
|
89
|
-
const getTextInputValue = (customId) => {
|
|
90
|
-
return textInputValues.get(customId);
|
|
91
|
-
};
|
|
92
|
-
const getTextInputValues = () => {
|
|
93
|
-
return new Map(textInputValues);
|
|
62
|
+
return undefined;
|
|
94
63
|
};
|
|
95
64
|
return Object.assign(interaction, {
|
|
96
65
|
reply,
|
|
97
66
|
deferReply,
|
|
98
67
|
getResponse,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
getSelectMenuValues: (customId) => selectMenuValues.get(customId),
|
|
102
|
-
onAck: helpers?.onAck,
|
|
68
|
+
getTextFieldValue,
|
|
69
|
+
sendFollowUp: helpers?.sendFollowUp,
|
|
103
70
|
});
|
|
104
71
|
}
|
|
@@ -52,6 +52,13 @@ export function normaliseInteractionMessageData(data) {
|
|
|
52
52
|
if (data.flags !== undefined) {
|
|
53
53
|
responseData.flags = normaliseMessageFlags(data.flags);
|
|
54
54
|
}
|
|
55
|
+
// Automatically handle IsComponentsV2 flag if Components V2 are detected
|
|
56
|
+
if (responseData.components && Array.isArray(responseData.components)) {
|
|
57
|
+
if (containsComponentsV2(responseData.components)) {
|
|
58
|
+
const currentFlags = responseData.flags ?? 0;
|
|
59
|
+
responseData.flags = (currentFlags | (0x1 << 12)); // 0x1 << 12 is IsComponentsV2
|
|
60
|
+
}
|
|
61
|
+
}
|
|
55
62
|
return responseData;
|
|
56
63
|
}
|
|
57
64
|
function containsComponentsV2(components) {
|
package/package.json
CHANGED