@minesa-org/mini-interaction 0.0.4 → 0.0.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.
@@ -7,6 +7,7 @@ import { verifyKey } from "discord-interactions";
7
7
  import { DISCORD_BASE_URL } from "../utils/constants.js";
8
8
  import { createCommandInteraction } from "../utils/CommandInteractionOptions.js";
9
9
  import { createMessageComponentInteraction, } from "../utils/MessageComponentInteraction.js";
10
+ import { createModalSubmitInteraction, } from "../utils/ModalSubmitInteraction.js";
10
11
  /** File extensions that are treated as loadable modules when auto-loading. */
11
12
  const SUPPORTED_MODULE_EXTENSIONS = new Set([
12
13
  ".js",
@@ -29,6 +30,7 @@ export class MiniInteraction {
29
30
  componentsDirectory;
30
31
  commands = new Map();
31
32
  componentHandlers = new Map();
33
+ modalHandlers = new Map();
32
34
  commandsLoaded = false;
33
35
  loadCommandsPromise = null;
34
36
  componentsLoaded = false;
@@ -75,6 +77,18 @@ export class MiniInteraction {
75
77
  console.warn(`[MiniInteraction] Command "${commandName}" already exists and will be overwritten.`);
76
78
  }
77
79
  this.commands.set(commandName, command);
80
+ // Register components exported with the command
81
+ if (command.components && Array.isArray(command.components)) {
82
+ for (const component of command.components) {
83
+ this.useComponent(component);
84
+ }
85
+ }
86
+ // Register modals exported with the command
87
+ if (command.modals && Array.isArray(command.modals)) {
88
+ for (const modal of command.modals) {
89
+ this.useModal(modal);
90
+ }
91
+ }
78
92
  return this;
79
93
  }
80
94
  /**
@@ -118,6 +132,36 @@ export class MiniInteraction {
118
132
  }
119
133
  return this;
120
134
  }
135
+ /**
136
+ * Registers a single modal handler mapped to a custom identifier.
137
+ *
138
+ * @param modal - The modal definition to register.
139
+ */
140
+ useModal(modal) {
141
+ const customId = modal?.customId;
142
+ if (!customId) {
143
+ throw new Error("[MiniInteraction] modal.customId is required");
144
+ }
145
+ if (typeof modal.handler !== "function") {
146
+ throw new Error("[MiniInteraction] modal.handler must be a function");
147
+ }
148
+ if (this.modalHandlers.has(customId)) {
149
+ console.warn(`[MiniInteraction] Modal "${customId}" already exists and will be overwritten.`);
150
+ }
151
+ this.modalHandlers.set(customId, modal.handler);
152
+ return this;
153
+ }
154
+ /**
155
+ * Registers multiple modal handlers in a single call.
156
+ *
157
+ * @param modals - The modal definitions to register.
158
+ */
159
+ useModals(modals) {
160
+ for (const modal of modals) {
161
+ this.useModal(modal);
162
+ }
163
+ return this;
164
+ }
121
165
  /**
122
166
  * Recursively loads components from the configured components directory.
123
167
  *
@@ -184,6 +228,18 @@ export class MiniInteraction {
184
228
  continue;
185
229
  }
186
230
  this.commands.set(command.data.name, command);
231
+ // Register components exported from the command file
232
+ if (command.components && Array.isArray(command.components)) {
233
+ for (const component of command.components) {
234
+ this.useComponent(component);
235
+ }
236
+ }
237
+ // Register modals exported from the command file
238
+ if (command.modals && Array.isArray(command.modals)) {
239
+ for (const modal of command.modals) {
240
+ this.useModal(modal);
241
+ }
242
+ }
187
243
  }
188
244
  this.commandsLoaded = true;
189
245
  return this;
@@ -302,6 +358,9 @@ export class MiniInteraction {
302
358
  if (interaction.type === InteractionType.MessageComponent) {
303
359
  return this.handleMessageComponent(interaction);
304
360
  }
361
+ if (interaction.type === InteractionType.ModalSubmit) {
362
+ return this.handleModalSubmit(interaction);
363
+ }
305
364
  return {
306
365
  status: 400,
307
366
  body: {
@@ -542,9 +601,10 @@ export class MiniInteraction {
542
601
  return;
543
602
  }
544
603
  if (!this.loadComponentsPromise) {
545
- this.loadComponentsPromise = this.loadComponentsFromDirectory().then(() => {
546
- this.loadComponentsPromise = null;
547
- });
604
+ this.loadComponentsPromise =
605
+ this.loadComponentsFromDirectory().then(() => {
606
+ this.loadComponentsPromise = null;
607
+ });
548
608
  }
549
609
  await this.loadComponentsPromise;
550
610
  }
@@ -583,7 +643,8 @@ export class MiniInteraction {
583
643
  const candidates = [];
584
644
  const isWithin = (parent, child) => {
585
645
  const relative = path.relative(parent, child);
586
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
646
+ return (relative === "" ||
647
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
587
648
  };
588
649
  const pushCandidate = (candidate) => {
589
650
  if (!candidates.includes(candidate)) {
@@ -681,6 +742,55 @@ export class MiniInteraction {
681
742
  };
682
743
  }
683
744
  }
745
+ /**
746
+ * Handles execution of a modal submit interaction.
747
+ */
748
+ async handleModalSubmit(interaction) {
749
+ const customId = interaction?.data?.custom_id;
750
+ if (!customId) {
751
+ return {
752
+ status: 400,
753
+ body: {
754
+ error: "[MiniInteraction] Modal submit interaction is missing a custom_id",
755
+ },
756
+ };
757
+ }
758
+ const handler = this.modalHandlers.get(customId);
759
+ if (!handler) {
760
+ return {
761
+ status: 404,
762
+ body: {
763
+ error: `[MiniInteraction] No handler registered for modal "${customId}"`,
764
+ },
765
+ };
766
+ }
767
+ try {
768
+ const interactionWithHelpers = createModalSubmitInteraction(interaction);
769
+ const response = await handler(interactionWithHelpers);
770
+ const resolvedResponse = response ?? interactionWithHelpers.getResponse();
771
+ if (!resolvedResponse) {
772
+ return {
773
+ status: 500,
774
+ body: {
775
+ error: `[MiniInteraction] Modal "${customId}" did not return a response. ` +
776
+ "Return an APIInteractionResponse to acknowledge the interaction.",
777
+ },
778
+ };
779
+ }
780
+ return {
781
+ status: 200,
782
+ body: resolvedResponse,
783
+ };
784
+ }
785
+ catch (error) {
786
+ return {
787
+ status: 500,
788
+ body: {
789
+ error: `[MiniInteraction] Modal "${customId}" failed: ${String(error)}`,
790
+ },
791
+ };
792
+ }
793
+ }
684
794
  /**
685
795
  * Handles execution of an application command interaction.
686
796
  */
package/dist/index.d.ts CHANGED
@@ -7,10 +7,15 @@ export { CommandInteractionOptionResolver, createCommandInteraction, } from "./u
7
7
  export type { CommandInteraction, MentionableOption, ResolvedUserOption, } from "./utils/CommandInteractionOptions.js";
8
8
  export type { MiniInteractionFetchHandler, MiniInteractionNodeHandler, MiniInteractionHandlerResult, MiniInteractionRequest, MiniInteractionOptions, } from "./clients/MiniInteraction.js";
9
9
  export type { MiniInteractionCommand, SlashCommandHandler, } from "./types/Commands.js";
10
- export type { MiniInteractionComponent, MiniInteractionComponentHandler, } from "./clients/MiniInteraction.js";
10
+ export type { MiniInteractionComponent, MiniInteractionComponentHandler, MiniInteractionModal, MiniInteractionModalHandler, } from "./clients/MiniInteraction.js";
11
+ export type { MessageComponentInteraction } from "./utils/MessageComponentInteraction.js";
12
+ export type { ModalSubmitInteraction } from "./utils/ModalSubmitInteraction.js";
11
13
  export { RoleConnectionMetadataTypes } from "./types/RoleConnectionMetadataTypes.js";
12
14
  export { ChannelType } from "./types/ChannelType.js";
13
15
  export { InteractionFollowUpFlags, InteractionReplyFlags, } from "./types/InteractionFlags.js";
14
16
  export { ButtonStyle } from "./types/ButtonStyle.js";
17
+ export { SeparatorSpacingSize } from "./types/SeparatorSpacingSize.js";
18
+ export { TextInputStyle } from "discord-api-types/v10";
19
+ export { MiniPermFlags } from "./types/PermissionFlags.js";
15
20
  export type { MiniComponentActionRow, MiniComponentMessageActionRow, } from "./types/ComponentTypes.js";
16
21
  export * from "./builders/index.js";
package/dist/index.js CHANGED
@@ -6,4 +6,7 @@ export { RoleConnectionMetadataTypes } from "./types/RoleConnectionMetadataTypes
6
6
  export { ChannelType } from "./types/ChannelType.js";
7
7
  export { InteractionFollowUpFlags, InteractionReplyFlags, } from "./types/InteractionFlags.js";
8
8
  export { ButtonStyle } from "./types/ButtonStyle.js";
9
+ export { SeparatorSpacingSize } from "./types/SeparatorSpacingSize.js";
10
+ export { TextInputStyle } from "discord-api-types/v10";
11
+ export { MiniPermFlags } from "./types/PermissionFlags.js";
9
12
  export * from "./builders/index.js";
@@ -1,11 +1,22 @@
1
1
  import type { APIInteractionResponse, RESTPostAPIChatInputApplicationCommandsJSONBody } from "discord-api-types/v10";
2
2
  import type { CommandInteraction } from "../utils/CommandInteractionOptions.js";
3
+ import type { MiniInteractionComponent, MiniInteractionModal } from "../clients/MiniInteraction.js";
3
4
  /** Handler signature for slash command executions within MiniInteraction. */
4
5
  export type SlashCommandHandler = (interaction: CommandInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
5
6
  /** Structure representing a slash command definition and its runtime handler. */
6
7
  export type MiniInteractionCommand = {
7
8
  data: RESTPostAPIChatInputApplicationCommandsJSONBody;
8
9
  handler: SlashCommandHandler;
10
+ /**
11
+ * Optional array of component handlers related to this command.
12
+ * These will be automatically registered when the command is loaded.
13
+ */
14
+ components?: MiniInteractionComponent[];
15
+ /**
16
+ * Optional array of modal handlers related to this command.
17
+ * These will be automatically registered when the command is loaded.
18
+ */
19
+ modals?: MiniInteractionModal[];
9
20
  };
10
21
  /** Map of command names to their registered MiniInteraction command definitions. */
11
22
  export type MiniInteractionCommandsMap = Map<string, MiniInteractionCommand>;
@@ -0,0 +1,55 @@
1
+ /** Convenience mapping of Discord permission bit flags for MiniInteraction usage. */
2
+ export declare const MiniPermFlags: {
3
+ readonly CreateInstantInvite: bigint;
4
+ readonly KickMembers: bigint;
5
+ readonly BanMembers: bigint;
6
+ readonly Administrator: bigint;
7
+ readonly ManageChannels: bigint;
8
+ readonly ManageGuild: bigint;
9
+ readonly AddReactions: bigint;
10
+ readonly ViewAuditLog: bigint;
11
+ readonly PrioritySpeaker: bigint;
12
+ readonly Stream: bigint;
13
+ readonly ViewChannel: bigint;
14
+ readonly SendMessages: bigint;
15
+ readonly SendTTSMessages: bigint;
16
+ readonly ManageMessages: bigint;
17
+ readonly EmbedLinks: bigint;
18
+ readonly AttachFiles: bigint;
19
+ readonly ReadMessageHistory: bigint;
20
+ readonly MentionEveryone: bigint;
21
+ readonly UseExternalEmojis: bigint;
22
+ readonly ViewGuildInsights: bigint;
23
+ readonly Connect: bigint;
24
+ readonly Speak: bigint;
25
+ readonly MuteMembers: bigint;
26
+ readonly DeafenMembers: bigint;
27
+ readonly MoveMembers: bigint;
28
+ readonly UseVAD: bigint;
29
+ readonly ChangeNickname: bigint;
30
+ readonly ManageNicknames: bigint;
31
+ readonly ManageRoles: bigint;
32
+ readonly ManageWebhooks: bigint;
33
+ readonly ManageEmojisAndStickers: bigint;
34
+ readonly ManageGuildExpressions: bigint;
35
+ readonly UseApplicationCommands: bigint;
36
+ readonly RequestToSpeak: bigint;
37
+ readonly ManageEvents: bigint;
38
+ readonly ManageThreads: bigint;
39
+ readonly CreatePublicThreads: bigint;
40
+ readonly CreatePrivateThreads: bigint;
41
+ readonly UseExternalStickers: bigint;
42
+ readonly SendMessagesInThreads: bigint;
43
+ readonly UseEmbeddedActivities: bigint;
44
+ readonly ModerateMembers: bigint;
45
+ readonly ViewCreatorMonetizationAnalytics: bigint;
46
+ readonly UseSoundboard: bigint;
47
+ readonly CreateGuildExpressions: bigint;
48
+ readonly CreateEvents: bigint;
49
+ readonly UseExternalSounds: bigint;
50
+ readonly SendVoiceMessages: bigint;
51
+ readonly SendPolls: bigint;
52
+ readonly UseExternalApps: bigint;
53
+ readonly PinMessages: bigint;
54
+ };
55
+ export type MiniPermFlag = (typeof MiniPermFlags)[keyof typeof MiniPermFlags];
@@ -0,0 +1,5 @@
1
+ import { PermissionFlagsBits } from "discord-api-types/v10";
2
+ /** Convenience mapping of Discord permission bit flags for MiniInteraction usage. */
3
+ export const MiniPermFlags = {
4
+ ...PermissionFlagsBits,
5
+ };
@@ -0,0 +1,2 @@
1
+ /** Re-export of Discord separator spacing size identifiers for convenience in builders. */
2
+ export { SeparatorSpacingSize } from "discord-api-types/v10";
@@ -0,0 +1,2 @@
1
+ /** Re-export of Discord separator spacing size identifiers for convenience in builders. */
2
+ export { SeparatorSpacingSize } from "discord-api-types/v10";
@@ -132,7 +132,9 @@ export interface CommandInteraction extends Omit<APIChatInputApplicationCommandI
132
132
  followUp(data: InteractionMessageData): APIInteractionResponseChannelMessageWithSource;
133
133
  edit(data?: InteractionMessageData): APIInteractionResponseUpdateMessage;
134
134
  deferReply(options?: DeferReplyOptions): APIInteractionResponseDeferredChannelMessageWithSource;
135
- showModal(data: APIModalInteractionResponseCallbackData): APIModalInteractionResponse;
135
+ showModal(data: APIModalInteractionResponseCallbackData | {
136
+ toJSON(): APIModalInteractionResponseCallbackData;
137
+ }): APIModalInteractionResponse;
136
138
  }
137
139
  /**
138
140
  * Wraps a raw application command interaction with helper methods and option resolvers.
@@ -366,9 +366,14 @@ export function createCommandInteraction(interaction) {
366
366
  : undefined);
367
367
  },
368
368
  showModal(data) {
369
+ const resolvedData = typeof data === "object" &&
370
+ "toJSON" in data &&
371
+ typeof data.toJSON === "function"
372
+ ? data.toJSON()
373
+ : data;
369
374
  return captureResponse({
370
375
  type: InteractionResponseType.Modal,
371
- data,
376
+ data: resolvedData,
372
377
  });
373
378
  },
374
379
  };
@@ -1,7 +1,10 @@
1
- import { type APIInteractionResponse, type APIInteractionResponseChannelMessageWithSource, type APIInteractionResponseDeferredChannelMessageWithSource, type APIInteractionResponseDeferredMessageUpdate, type APIInteractionResponseUpdateMessage, type APIMessageComponentInteraction } from "discord-api-types/v10";
1
+ import { type APIInteractionResponse, type APIInteractionResponseChannelMessageWithSource, type APIInteractionResponseDeferredChannelMessageWithSource, type APIInteractionResponseDeferredMessageUpdate, type APIInteractionResponseUpdateMessage, type APIMessageComponentInteraction, type APIModalInteractionResponse, type APIModalInteractionResponseCallbackData } from "discord-api-types/v10";
2
2
  import { DeferReplyOptions, InteractionMessageData } from "./interactionMessageHelpers.js";
3
3
  /**
4
4
  * Represents a component interaction augmented with helper response methods.
5
+ *
6
+ * Note: The `values` property is available on select menu interactions (data.values).
7
+ * For button interactions, this property will be undefined.
5
8
  */
6
9
  export type MessageComponentInteraction = APIMessageComponentInteraction & {
7
10
  getResponse: () => APIInteractionResponse | null;
@@ -9,6 +12,15 @@ export type MessageComponentInteraction = APIMessageComponentInteraction & {
9
12
  deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
10
13
  update: (data?: InteractionMessageData) => APIInteractionResponseUpdateMessage;
11
14
  deferUpdate: () => APIInteractionResponseDeferredMessageUpdate;
15
+ showModal: (data: APIModalInteractionResponseCallbackData | {
16
+ toJSON(): APIModalInteractionResponseCallbackData;
17
+ }) => APIModalInteractionResponse;
18
+ /**
19
+ * The selected values from a select menu interaction.
20
+ * This property is only present for select menu interactions.
21
+ * For button interactions, this will be undefined.
22
+ */
23
+ values?: string[];
12
24
  };
13
25
  /**
14
26
  * Wraps a raw component interaction with helper methods mirroring Discord's expected responses.
@@ -49,12 +49,27 @@ export function createMessageComponentInteraction(interaction) {
49
49
  const deferUpdate = () => captureResponse({
50
50
  type: InteractionResponseType.DeferredMessageUpdate,
51
51
  });
52
+ const showModal = (data) => {
53
+ const resolvedData = typeof data === "object" &&
54
+ "toJSON" in data &&
55
+ typeof data.toJSON === "function"
56
+ ? data.toJSON()
57
+ : data;
58
+ return captureResponse({
59
+ type: InteractionResponseType.Modal,
60
+ data: resolvedData,
61
+ });
62
+ };
52
63
  const getResponse = () => capturedResponse;
64
+ // Extract values from select menu interactions
65
+ const values = "values" in interaction.data ? interaction.data.values : undefined;
53
66
  return Object.assign(interaction, {
54
67
  reply,
55
68
  deferReply,
56
69
  update,
57
70
  deferUpdate,
71
+ showModal,
58
72
  getResponse,
73
+ values,
59
74
  });
60
75
  }
@@ -0,0 +1,28 @@
1
+ import { type APIInteractionResponse, type APIInteractionResponseChannelMessageWithSource, type APIInteractionResponseDeferredChannelMessageWithSource, type APIModalSubmitInteraction } from "discord-api-types/v10";
2
+ import { DeferReplyOptions, InteractionMessageData } from "./interactionMessageHelpers.js";
3
+ /**
4
+ * Represents a modal submit interaction augmented with helper response methods.
5
+ */
6
+ export type ModalSubmitInteraction = APIModalSubmitInteraction & {
7
+ getResponse: () => APIInteractionResponse | null;
8
+ reply: (data: InteractionMessageData) => APIInteractionResponseChannelMessageWithSource;
9
+ deferReply: (options?: DeferReplyOptions) => APIInteractionResponseDeferredChannelMessageWithSource;
10
+ /**
11
+ * Helper method to get the value of a text input component by custom_id.
12
+ * @param customId - The custom_id of the text input component
13
+ * @returns The value of the text input, or undefined if not found
14
+ */
15
+ getTextInputValue: (customId: string) => string | undefined;
16
+ /**
17
+ * Helper method to get all text input values as a map.
18
+ * @returns A map of custom_id to value for all text inputs
19
+ */
20
+ getTextInputValues: () => Map<string, string>;
21
+ };
22
+ /**
23
+ * Wraps a raw modal submit interaction with helper methods.
24
+ *
25
+ * @param interaction - The raw interaction payload from Discord.
26
+ * @returns A helper-augmented interaction object.
27
+ */
28
+ export declare function createModalSubmitInteraction(interaction: APIModalSubmitInteraction): ModalSubmitInteraction;
@@ -0,0 +1,74 @@
1
+ import { InteractionResponseType, } from "discord-api-types/v10";
2
+ import { normaliseInteractionMessageData, normaliseMessageFlags, } from "./interactionMessageHelpers.js";
3
+ /**
4
+ * Wraps a raw modal submit interaction with helper methods.
5
+ *
6
+ * @param interaction - The raw interaction payload from Discord.
7
+ * @returns A helper-augmented interaction object.
8
+ */
9
+ export function createModalSubmitInteraction(interaction) {
10
+ let capturedResponse = null;
11
+ const captureResponse = (response) => {
12
+ capturedResponse = response;
13
+ return response;
14
+ };
15
+ const reply = (data) => {
16
+ const normalisedData = normaliseInteractionMessageData(data);
17
+ if (!normalisedData) {
18
+ throw new Error("[MiniInteraction] Modal submit replies require response data to be provided.");
19
+ }
20
+ return captureResponse({
21
+ type: InteractionResponseType.ChannelMessageWithSource,
22
+ data: normalisedData,
23
+ });
24
+ };
25
+ const deferReply = (options) => {
26
+ const flags = normaliseMessageFlags(options?.flags);
27
+ const response = flags !== undefined
28
+ ? {
29
+ type: InteractionResponseType.DeferredChannelMessageWithSource,
30
+ data: { flags },
31
+ }
32
+ : {
33
+ type: InteractionResponseType.DeferredChannelMessageWithSource,
34
+ };
35
+ return captureResponse(response);
36
+ };
37
+ const getResponse = () => capturedResponse;
38
+ // Helper to extract text input values from modal components
39
+ const extractTextInputs = () => {
40
+ const textInputs = new Map();
41
+ for (const component of interaction.data.components) {
42
+ // Handle action rows
43
+ if ("components" in component && Array.isArray(component.components)) {
44
+ for (const child of component.components) {
45
+ if ("value" in child && "custom_id" in child) {
46
+ textInputs.set(child.custom_id, child.value);
47
+ }
48
+ }
49
+ }
50
+ // Handle labeled components
51
+ else if ("component" in component) {
52
+ const labeledComponent = component.component;
53
+ if ("value" in labeledComponent && "custom_id" in labeledComponent) {
54
+ textInputs.set(labeledComponent.custom_id, labeledComponent.value);
55
+ }
56
+ }
57
+ }
58
+ return textInputs;
59
+ };
60
+ const textInputValues = extractTextInputs();
61
+ const getTextInputValue = (customId) => {
62
+ return textInputValues.get(customId);
63
+ };
64
+ const getTextInputValues = () => {
65
+ return new Map(textInputValues);
66
+ };
67
+ return Object.assign(interaction, {
68
+ reply,
69
+ deferReply,
70
+ getResponse,
71
+ getTextInputValue,
72
+ getTextInputValues,
73
+ });
74
+ }
@@ -4,23 +4,23 @@ import type { InteractionFollowUpFlags, InteractionReplyFlags } from "../types/I
4
4
  export type MessageFlagLike = MessageFlags | InteractionReplyFlags | InteractionFollowUpFlags;
5
5
  /** Message payload accepted by helper reply/edit functions. */
6
6
  export type InteractionMessageData = Omit<APIInteractionResponseCallbackData, "flags"> & {
7
- flags?: MessageFlagLike;
7
+ flags?: MessageFlagLike | MessageFlagLike[];
8
8
  };
9
9
  /** Deferred response payload recognised by helper methods. */
10
10
  export type DeferredResponseData = {
11
- flags: MessageFlagLike;
11
+ flags: MessageFlagLike | MessageFlagLike[];
12
12
  };
13
13
  /** Options accepted when deferring a reply. */
14
14
  export type DeferReplyOptions = {
15
- flags?: MessageFlagLike;
15
+ flags?: MessageFlagLike | MessageFlagLike[];
16
16
  };
17
17
  /**
18
18
  * Normalises helper flag enums into the raw Discord `MessageFlags` bitfield.
19
19
  *
20
- * @param flags - A flag from helper enums or raw Discord flags.
20
+ * @param flags - A flag or array of flags from helper enums or raw Discord flags.
21
21
  * @returns The value coerced to a `MessageFlags` compatible bitfield.
22
22
  */
23
- export declare function normaliseMessageFlags(flags: MessageFlagLike | undefined): MessageFlags | undefined;
23
+ export declare function normaliseMessageFlags(flags: MessageFlagLike | MessageFlagLike[] | undefined): MessageFlags | undefined;
24
24
  /**
25
25
  * Ensures helper message payloads include correctly normalised message flags.
26
26
  *
@@ -1,11 +1,31 @@
1
+ import { ComponentType, MessageFlags as RawMessageFlags, } from "discord-api-types/v10";
2
+ const COMPONENTS_V2_TYPES = new Set([
3
+ ComponentType.Container,
4
+ ComponentType.Section,
5
+ ComponentType.TextDisplay,
6
+ ComponentType.MediaGallery,
7
+ ComponentType.File,
8
+ ComponentType.Separator,
9
+ ComponentType.Thumbnail,
10
+ ]);
1
11
  /**
2
12
  * Normalises helper flag enums into the raw Discord `MessageFlags` bitfield.
3
13
  *
4
- * @param flags - A flag from helper enums or raw Discord flags.
14
+ * @param flags - A flag or array of flags from helper enums or raw Discord flags.
5
15
  * @returns The value coerced to a `MessageFlags` compatible bitfield.
6
16
  */
7
17
  export function normaliseMessageFlags(flags) {
8
- return flags === undefined ? undefined : flags;
18
+ if (flags === undefined) {
19
+ return undefined;
20
+ }
21
+ if (Array.isArray(flags)) {
22
+ if (flags.length === 0) {
23
+ return undefined;
24
+ }
25
+ // Combine multiple flags using bitwise OR
26
+ return flags.reduce((acc, flag) => (acc | flag), 0);
27
+ }
28
+ return flags;
9
29
  }
10
30
  /**
11
31
  * Ensures helper message payloads include correctly normalised message flags.
@@ -17,16 +37,66 @@ export function normaliseInteractionMessageData(data) {
17
37
  if (!data) {
18
38
  return undefined;
19
39
  }
20
- if (data.flags === undefined) {
21
- return data;
22
- }
23
- const { flags, ...rest } = data;
24
- const normalisedFlags = normaliseMessageFlags(flags);
25
- if (normalisedFlags === flags) {
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) {
26
59
  return data;
27
60
  }
61
+ const { flags: _flags, components: _components, embeds: _embeds, ...rest } = data;
28
62
  return {
29
63
  ...rest,
30
- flags: normalisedFlags,
64
+ ...(normalisedComponents !== undefined
65
+ ? { components: normalisedComponents }
66
+ : {}),
67
+ ...(normalisedEmbeds !== undefined ? { embeds: normalisedEmbeds } : {}),
68
+ ...(finalFlags !== undefined ? { flags: finalFlags } : {}),
31
69
  };
32
70
  }
71
+ function containsComponentsV2(components) {
72
+ return components.some((component) => componentUsesComponentsV2(component));
73
+ }
74
+ function componentUsesComponentsV2(component) {
75
+ const resolved = resolveComponentLike(component);
76
+ if (!resolved || typeof resolved !== "object") {
77
+ return false;
78
+ }
79
+ const type = resolved.type;
80
+ if (typeof type !== "number") {
81
+ return false;
82
+ }
83
+ if (COMPONENTS_V2_TYPES.has(type)) {
84
+ return true;
85
+ }
86
+ if (type === ComponentType.ActionRow) {
87
+ const rowComponents = resolved.components;
88
+ if (Array.isArray(rowComponents)) {
89
+ return containsComponentsV2(rowComponents);
90
+ }
91
+ }
92
+ return false;
93
+ }
94
+ function resolveComponentLike(component) {
95
+ if (component && typeof component === "object" && "toJSON" in component) {
96
+ const encoder = component;
97
+ if (typeof encoder.toJSON === "function") {
98
+ return encoder.toJSON();
99
+ }
100
+ }
101
+ return component;
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.0.4",
3
+ "version": "0.0.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",
@@ -14,6 +14,7 @@
14
14
  "build": "tsc --project tsconfig.json",
15
15
  "typecheck": "tsc --noEmit",
16
16
  "prepare": "npm run build",
17
+ "patch": "npm version patch",
17
18
  "publish:npm": "npm publish",
18
19
  "publish:gh": "npm publish --registry=https://npm.pkg.github.com"
19
20
  },