@minesa-org/mini-interaction 0.1.12 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ export type ResolvedUserOption = {
4
4
  user: APIUser;
5
5
  member?: APIInteractionDataResolvedGuildMember;
6
6
  };
7
+ export declare const ResolvedUserOption: {};
7
8
  export type MentionableOption = {
8
9
  type: "user";
9
10
  value: ResolvedUserOption;
@@ -11,6 +12,7 @@ export type MentionableOption = {
11
12
  type: "role";
12
13
  value: APIRole;
13
14
  };
15
+ export declare const MentionableOption: {};
14
16
  /**
15
17
  * Provides ergonomic helpers for extracting typed command options from Discord interactions.
16
18
  */
@@ -149,11 +151,21 @@ export interface CommandInteraction extends Omit<APIChatInputApplicationCommandI
149
151
  showModal(data: APIModalInteractionResponseCallbackData | {
150
152
  toJSON(): APIModalInteractionResponseCallbackData;
151
153
  }): APIModalInteractionResponse;
154
+ withTimeoutProtection<T>(operation: () => Promise<T>, deferOptions?: DeferReplyOptions): Promise<T>;
155
+ canRespond?(interactionId: string): boolean;
156
+ trackResponse?(interactionId: string, token: string, state: 'responded' | 'deferred'): void;
157
+ logTiming?(interactionId: string, operation: string, startTime: number, success: boolean): void;
152
158
  }
159
+ export declare const CommandInteraction: {};
153
160
  /**
154
161
  * Wraps a raw application command interaction with helper methods and option resolvers.
155
162
  *
156
163
  * @param interaction - The raw interaction payload from Discord.
164
+ * @param helpers - Optional helper methods for state management and logging.
157
165
  * @returns A helper-augmented interaction object.
158
166
  */
159
- export declare function createCommandInteraction(interaction: APIChatInputApplicationCommandInteraction): CommandInteraction;
167
+ export declare function createCommandInteraction(interaction: APIChatInputApplicationCommandInteraction, helpers?: {
168
+ canRespond?: (interactionId: string) => boolean;
169
+ trackResponse?: (interactionId: string, token: string, state: 'responded' | 'deferred') => void;
170
+ logTiming?: (interactionId: string, operation: string, startTime: number, success: boolean) => void;
171
+ }): CommandInteraction;
@@ -1,5 +1,7 @@
1
1
  import { ApplicationCommandOptionType, InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const ResolvedUserOption = {};
4
+ export const MentionableOption = {};
3
5
  /** Maps application command option types to human-readable labels for error messages. */
4
6
  const OPTION_TYPE_LABEL = {
5
7
  [ApplicationCommandOptionType.Subcommand]: "subcommand",
@@ -314,13 +316,15 @@ export class CommandInteractionOptionResolver {
314
316
  return records?.[id];
315
317
  }
316
318
  }
319
+ export const CommandInteraction = {};
317
320
  /**
318
321
  * Wraps a raw application command interaction with helper methods and option resolvers.
319
322
  *
320
323
  * @param interaction - The raw interaction payload from Discord.
324
+ * @param helpers - Optional helper methods for state management and logging.
321
325
  * @returns A helper-augmented interaction object.
322
326
  */
323
- export function createCommandInteraction(interaction) {
327
+ export function createCommandInteraction(interaction, helpers) {
324
328
  const options = new CommandInteractionOptionResolver(interaction.data.options, interaction.data.resolved);
325
329
  let capturedResponse = null;
326
330
  /**
@@ -371,7 +375,17 @@ export function createCommandInteraction(interaction) {
371
375
  return capturedResponse;
372
376
  },
373
377
  reply(data) {
374
- return createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
378
+ // Validate interaction can respond
379
+ if (!this.canRespond?.(this.id)) {
380
+ throw new Error('Interaction cannot respond: already responded or expired');
381
+ }
382
+ const startTime = Date.now();
383
+ const response = createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
384
+ // Track response
385
+ this.trackResponse?.(this.id, this.token, 'responded');
386
+ // Log timing if debug enabled
387
+ this.logTiming?.(this.id, 'reply', startTime, true);
388
+ return response;
375
389
  },
376
390
  followUp(data) {
377
391
  return createMessageResponse(InteractionResponseType.ChannelMessageWithSource, data);
@@ -380,12 +394,32 @@ export function createCommandInteraction(interaction) {
380
394
  return createMessageResponse(InteractionResponseType.UpdateMessage, data);
381
395
  },
382
396
  editReply(data) {
383
- return createMessageResponse(InteractionResponseType.UpdateMessage, data);
397
+ // Validate interaction can respond
398
+ if (!this.canRespond?.(this.id)) {
399
+ throw new Error('Interaction cannot edit reply: already responded, expired, or not deferred');
400
+ }
401
+ const startTime = Date.now();
402
+ const response = createMessageResponse(InteractionResponseType.UpdateMessage, data);
403
+ // Track response
404
+ this.trackResponse?.(this.id, this.token, 'responded');
405
+ // Log timing if debug enabled
406
+ this.logTiming?.(this.id, 'editReply', startTime, true);
407
+ return response;
384
408
  },
385
409
  deferReply(options) {
386
- return createDeferredResponse(options?.flags !== undefined
410
+ // Validate interaction can respond
411
+ if (!this.canRespond?.(this.id)) {
412
+ throw new Error('Interaction cannot defer: already responded or expired');
413
+ }
414
+ const startTime = Date.now();
415
+ const response = createDeferredResponse(options?.flags !== undefined
387
416
  ? { flags: options.flags }
388
417
  : undefined);
418
+ // Track deferred state
419
+ this.trackResponse?.(this.id, this.token, 'deferred');
420
+ // Log timing if debug enabled
421
+ this.logTiming?.(this.id, 'deferReply', startTime, true);
422
+ return response;
389
423
  },
390
424
  showModal(data) {
391
425
  const resolvedData = typeof data === "object" &&
@@ -398,6 +432,44 @@ export function createCommandInteraction(interaction) {
398
432
  data: resolvedData,
399
433
  });
400
434
  },
435
+ /**
436
+ * Creates a delayed response wrapper that automatically defers if the operation takes too long.
437
+ * Use this for operations that might exceed Discord's 3-second limit.
438
+ *
439
+ * @param operation - The async operation to perform
440
+ * @param deferOptions - Options for automatic deferral
441
+ */
442
+ async withTimeoutProtection(operation, deferOptions) {
443
+ const startTime = Date.now();
444
+ let deferred = false;
445
+ // Set up a timer to auto-defer after 2.5 seconds
446
+ const deferTimer = setTimeout(async () => {
447
+ if (!deferred) {
448
+ console.warn("[MiniInteraction] Auto-deferring interaction due to slow operation. " +
449
+ "Consider using deferReply() explicitly for better user experience.");
450
+ this.deferReply(deferOptions);
451
+ deferred = true;
452
+ }
453
+ }, 2500);
454
+ try {
455
+ const result = await operation();
456
+ clearTimeout(deferTimer);
457
+ const elapsed = Date.now() - startTime;
458
+ if (elapsed > 2000 && !deferred) {
459
+ console.warn(`[MiniInteraction] Operation completed in ${elapsed}ms. ` +
460
+ "Consider using deferReply() for operations > 2 seconds.");
461
+ }
462
+ return result;
463
+ }
464
+ catch (error) {
465
+ clearTimeout(deferTimer);
466
+ throw error;
467
+ }
468
+ },
469
+ // Helper methods for state management
470
+ canRespond: helpers?.canRespond,
471
+ trackResponse: helpers?.trackResponse,
472
+ logTiming: helpers?.logTiming,
401
473
  };
402
474
  return commandInteraction;
403
475
  }
@@ -20,6 +20,7 @@ export type UserContextMenuInteraction = APIUserApplicationCommandInteraction &
20
20
  /** Resolved user targeted by this user context menu command. */
21
21
  targetUser?: APIUser;
22
22
  };
23
+ export declare const UserContextMenuInteraction: {};
23
24
  /**
24
25
  * Message context menu interaction with helper methods.
25
26
  */
@@ -27,10 +28,12 @@ export type MessageContextMenuInteraction = APIMessageApplicationCommandInteract
27
28
  /** Resolved message targeted by this message context menu command. */
28
29
  targetMessage?: APIMessage;
29
30
  };
31
+ export declare const MessageContextMenuInteraction: {};
30
32
  /**
31
33
  * Primary entry point interaction with helper methods.
32
34
  */
33
35
  export type AppCommandInteraction = APIPrimaryEntryPointCommandInteraction & ContextMenuInteractionHelpers;
36
+ export declare const AppCommandInteraction: {};
34
37
  /**
35
38
  * Wraps a raw user context menu interaction with helper methods.
36
39
  *
@@ -1,5 +1,8 @@
1
1
  import { InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const UserContextMenuInteraction = {};
4
+ export const MessageContextMenuInteraction = {};
5
+ export const AppCommandInteraction = {};
3
6
  function createContextMenuInteractionHelpers() {
4
7
  let capturedResponse = null;
5
8
  const captureResponse = (response) => {
@@ -5,6 +5,7 @@ export type ResolvedUserOption = {
5
5
  user: APIUser;
6
6
  member?: APIInteractionDataResolvedGuildMember;
7
7
  };
8
+ export declare const ResolvedUserOption: {};
8
9
  /** Resolved mentionable option (either a user or role). */
9
10
  export type ResolvedMentionableOption = {
10
11
  type: "user";
@@ -13,6 +14,7 @@ export type ResolvedMentionableOption = {
13
14
  type: "role";
14
15
  value: APIRole;
15
16
  };
17
+ export declare const ResolvedMentionableOption: {};
16
18
  /**
17
19
  * Base helper methods available on all component interactions.
18
20
  */
@@ -33,6 +35,7 @@ type BaseComponentInteractionHelpers = {
33
35
  export interface ButtonInteraction extends Omit<APIMessageComponentInteraction, "data">, BaseComponentInteractionHelpers {
34
36
  data: APIMessageButtonInteractionData;
35
37
  }
38
+ export declare const ButtonInteraction: {};
36
39
  /**
37
40
  * String select menu interaction with helper methods.
38
41
  */
@@ -41,6 +44,7 @@ export interface StringSelectInteraction extends Omit<APIMessageComponentInterac
41
44
  values: string[];
42
45
  getStringValues: () => string[];
43
46
  }
47
+ export declare const StringSelectInteraction: {};
44
48
  /**
45
49
  * Role select menu interaction with helper methods.
46
50
  */
@@ -49,6 +53,7 @@ export interface RoleSelectInteraction extends Omit<APIMessageComponentInteracti
49
53
  values: string[];
50
54
  getRoles: () => APIRole[];
51
55
  }
56
+ export declare const RoleSelectInteraction: {};
52
57
  /**
53
58
  * User select menu interaction with helper methods.
54
59
  */
@@ -57,6 +62,7 @@ export interface UserSelectInteraction extends Omit<APIMessageComponentInteracti
57
62
  values: string[];
58
63
  getUsers: () => ResolvedUserOption[];
59
64
  }
65
+ export declare const UserSelectInteraction: {};
60
66
  /**
61
67
  * Channel select menu interaction with helper methods.
62
68
  */
@@ -65,6 +71,7 @@ export interface ChannelSelectInteraction extends Omit<APIMessageComponentIntera
65
71
  values: string[];
66
72
  getChannels: () => APIInteractionDataResolvedChannel[];
67
73
  }
74
+ export declare const ChannelSelectInteraction: {};
68
75
  /**
69
76
  * Mentionable select menu interaction with helper methods.
70
77
  */
@@ -73,6 +80,7 @@ export interface MentionableSelectInteraction extends Omit<APIMessageComponentIn
73
80
  values: string[];
74
81
  getMentionables: () => ResolvedMentionableOption[];
75
82
  }
83
+ export declare const MentionableSelectInteraction: {};
76
84
  /**
77
85
  * Represents a component interaction augmented with helper response methods.
78
86
  *
@@ -120,6 +128,7 @@ export type MessageComponentInteraction = APIMessageComponentInteraction & {
120
128
  */
121
129
  getMentionables: () => ResolvedMentionableOption[];
122
130
  };
131
+ export declare const MessageComponentInteraction: {};
123
132
  /**
124
133
  * Wraps a raw component interaction with helper methods mirroring Discord's expected responses.
125
134
  *
@@ -1,5 +1,14 @@
1
1
  import { InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const ResolvedUserOption = {};
4
+ export const ResolvedMentionableOption = {};
5
+ export const ButtonInteraction = {};
6
+ export const StringSelectInteraction = {};
7
+ export const RoleSelectInteraction = {};
8
+ export const UserSelectInteraction = {};
9
+ export const ChannelSelectInteraction = {};
10
+ export const MentionableSelectInteraction = {};
11
+ export const MessageComponentInteraction = {};
3
12
  /**
4
13
  * Wraps a raw component interaction with helper methods mirroring Discord's expected responses.
5
14
  *
@@ -19,6 +19,7 @@ export type ModalSubmitInteraction = APIModalSubmitInteraction & {
19
19
  */
20
20
  getTextInputValues: () => Map<string, string>;
21
21
  };
22
+ export declare const ModalSubmitInteraction: {};
22
23
  /**
23
24
  * Wraps a raw modal submit interaction with helper methods.
24
25
  *
@@ -1,5 +1,6 @@
1
1
  import { InteractionResponseType, } from "discord-api-types/v10";
2
2
  import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ export const ModalSubmitInteraction = {};
3
4
  /**
4
5
  * Wraps a raw modal submit interaction with helper methods.
5
6
  *
@@ -1,10 +1,17 @@
1
1
  import type { APIInteractionResponseCallbackData, MessageFlags } from "discord-api-types/v10";
2
- import type { InteractionFollowUpFlags, InteractionReplyFlags } from "../types/InteractionFlags.js";
2
+ import type { APIActionRowComponent, APIComponentInMessageActionRow, APIContainerComponent } from "discord-api-types/v10";
3
+ import type { JSONEncodable } from "../builders/shared.js";
4
+ import type { InteractionFlags } from "../types/InteractionFlags.js";
3
5
  /** Union of helper flag enums and raw Discord message flags. */
4
- export type MessageFlagLike = MessageFlags | InteractionReplyFlags | InteractionFollowUpFlags;
6
+ export type MessageFlagLike = MessageFlags | InteractionFlags;
7
+ /** Top-level components allowed in messages (ActionRows or Containers) */
8
+ export type TopLevelComponent = APIActionRowComponent<APIComponentInMessageActionRow> | APIContainerComponent;
5
9
  /** Message payload accepted by helper reply/edit functions. */
6
- export type InteractionMessageData = Omit<APIInteractionResponseCallbackData, "flags"> & {
7
- flags?: MessageFlagLike | MessageFlagLike[];
10
+ export type InteractionMessageData = {
11
+ content?: string;
12
+ components?: TopLevelComponent[] | JSONEncodable<TopLevelComponent>[] | JSONEncodable<TopLevelComponent[]>;
13
+ embeds?: unknown[];
14
+ flags?: InteractionFlags;
8
15
  };
9
16
  /** Deferred response payload recognised by helper methods. */
10
17
  export type DeferredResponseData = {
@@ -1,4 +1,5 @@
1
- import { ComponentType, MessageFlags as RawMessageFlags, } from "discord-api-types/v10";
1
+ import { resolveJSONEncodable } from "../builders/shared.js";
2
+ import { ComponentType, } from "discord-api-types/v10";
2
3
  const COMPONENTS_V2_TYPES = new Set([
3
4
  ComponentType.Container,
4
5
  ComponentType.Section,
@@ -37,36 +38,18 @@ export function normaliseInteractionMessageData(data) {
37
38
  if (!data) {
38
39
  return undefined;
39
40
  }
40
- // Auto-convert builders to JSON
41
- const normalisedComponents = data.components
42
- ? data.components.map((component) => resolveComponentLike(component))
43
- : undefined;
44
- const normalisedEmbeds = data.embeds
45
- ? data.embeds.map((embed) => resolveComponentLike(embed))
46
- : undefined;
47
- const usesComponentsV2 = Array.isArray(normalisedComponents)
48
- ? containsComponentsV2(normalisedComponents)
49
- : false;
50
- const normalisedFlags = normaliseMessageFlags(data.flags);
51
- const finalFlags = usesComponentsV2
52
- ? ((normalisedFlags ?? 0) |
53
- RawMessageFlags.IsComponentsV2)
54
- : normalisedFlags;
55
- const needsNormalisation = finalFlags !== data.flags ||
56
- normalisedComponents !== data.components ||
57
- normalisedEmbeds !== data.embeds;
58
- if (!needsNormalisation) {
59
- return data;
41
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
42
+ const responseData = { ...data };
43
+ if (data.components) {
44
+ const components = resolveJSONEncodable(data.components);
45
+ if (Array.isArray(components)) {
46
+ responseData.components = components.map((c) => resolveJSONEncodable(c));
47
+ }
48
+ }
49
+ if (data.embeds) {
50
+ responseData.embeds = data.embeds.map((e) => resolveJSONEncodable(e));
60
51
  }
61
- const { flags: _flags, components: _components, embeds: _embeds, ...rest } = data;
62
- return {
63
- ...rest,
64
- ...(normalisedComponents !== undefined
65
- ? { components: normalisedComponents }
66
- : {}),
67
- ...(normalisedEmbeds !== undefined ? { embeds: normalisedEmbeds } : {}),
68
- ...(finalFlags !== undefined ? { flags: finalFlags } : {}),
69
- };
52
+ return responseData;
70
53
  }
71
54
  function containsComponentsV2(components) {
72
55
  return components.some((component) => componentUsesComponentsV2(component));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.1.12",
3
+ "version": "0.2.0",
4
4
  "description": "Mini interaction, connecting your app with Discord via HTTP-interaction (Vercel support).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",