@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.
@@ -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
- return createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
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: already responded, expired, or not deferred');
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
- // Log timing if debug enabled
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
- getResponse: () => APIInteractionResponse | null;
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">, BaseComponentInteractionHelpers {
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">, BaseComponentInteractionHelpers {
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">, BaseComponentInteractionHelpers {
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">, BaseComponentInteractionHelpers {
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">, BaseComponentInteractionHelpers {
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">, BaseComponentInteractionHelpers {
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?.onAck?.(response);
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
- return captureResponse(response);
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 all text input values as a map.
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
- getTextInputValues: () => Map<string, string>;
13
+ getTextFieldValue: (customId: string) => string | undefined;
22
14
  /**
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
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
- getSelectMenuValues: (customId: string) => string[] | undefined;
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 submit replies require response data to be provided.");
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?.onAck?.(response);
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
- // Helper to extract text input values from modal components
44
- const extractTextInputs = () => {
45
- const textInputs = new Map();
46
- for (const component of interaction.data.components) {
47
- // Handle action rows
48
- if ("components" in component && Array.isArray(component.components)) {
49
- for (const child of component.components) {
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 selectMenuValues;
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
- getTextInputValue,
100
- getTextInputValues,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
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",