From 798f64895548cbd07281910d72008f52e0cecabe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Susa=C3=B1a?= Date: Thu, 19 Jun 2025 23:12:42 -0400 Subject: [PATCH] feat: waitFor modals (#346) * feat: modal#waitFor * refactor: update waitFor method to improve promise handling and timeout logic * feat: enhance modal handling with options for wait time and improved promise resolution --------- Co-authored-by: MARCROCK22 --- src/commands/applications/entrycontext.ts | 10 +++++--- src/commands/applications/menucontext.ts | 29 +++++++++++++-------- src/common/types/write.ts | 4 +++ src/components/componentcontext.ts | 11 +++++--- src/components/modalcontext.ts | 13 ++++++---- src/structures/Interaction.ts | 31 +++++++++++++++++++++-- 6 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/commands/applications/entrycontext.ts b/src/commands/applications/entrycontext.ts index d1c59b2..0a1910d 100644 --- a/src/commands/applications/entrycontext.ts +++ b/src/commands/applications/entrycontext.ts @@ -12,10 +12,11 @@ import type { MakeRequired, MessageWebhookCreateBodyRequest, ModalCreateBodyRequest, + ModalCreateOptions, UnionToTuple, When, } from '../../common'; -import type { AllChannels, EntryPointInteraction } from '../../structures'; +import type { AllChannels, EntryPointInteraction, ModalSubmitInteraction } from '../../structures'; import { MessageFlags, type RESTGetAPIGuildQuery } from '../../types'; import { BaseContext } from '../basecontext'; import type { RegisteredMiddlewares } from '../decorators'; @@ -52,8 +53,11 @@ export class EntryPointContext ex return this.interaction.write(body, withResponse); } - modal(body: ModalCreateBodyRequest) { - return this.interaction.modal(body); + modal(body: ModalCreateBodyRequest, options?: undefined): Promise; + modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise; + modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) { + if (options === undefined) return this.interaction.modal(body); + return this.interaction.modal(body, options); } deferReply( diff --git a/src/commands/applications/menucontext.ts b/src/commands/applications/menucontext.ts index 1d66f7c..17a86f4 100644 --- a/src/commands/applications/menucontext.ts +++ b/src/commands/applications/menucontext.ts @@ -8,16 +8,22 @@ import { type WebhookMessageStructure, } from '../../client/transformers'; import { - type InteractionCreateBodyRequest, - type InteractionMessageUpdateBodyRequest, - type MakeRequired, - type MessageWebhookCreateBodyRequest, - type ModalCreateBodyRequest, + InteractionCreateBodyRequest, + InteractionMessageUpdateBodyRequest, + MakeRequired, + MessageWebhookCreateBodyRequest, + ModalCreateBodyRequest, + ModalCreateOptions, toSnakeCase, - type UnionToTuple, - type When, + UnionToTuple, + When, } from '../../common'; -import type { AllChannels, MessageCommandInteraction, UserCommandInteraction } from '../../structures'; +import { + AllChannels, + MessageCommandInteraction, + ModalSubmitInteraction, + UserCommandInteraction, +} from '../../structures'; import { type APIMessage, ApplicationCommandType, MessageFlags, type RESTGetAPIGuildQuery } from '../../types'; import { BaseContext } from '../basecontext'; import type { RegisteredMiddlewares } from '../decorators'; @@ -75,8 +81,11 @@ export class MenuCommandContext< return this.interaction.write(body, withResponse); } - modal(body: ModalCreateBodyRequest) { - return this.interaction.modal(body); + modal(body: ModalCreateBodyRequest, options?: undefined): Promise; + modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise; + modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) { + if (options === undefined) return this.interaction.modal(body); + return this.interaction.modal(body, options); } deferReply( diff --git a/src/common/types/write.ts b/src/common/types/write.ts index 6635330..eab0c01 100644 --- a/src/common/types/write.ts +++ b/src/common/types/write.ts @@ -70,3 +70,7 @@ export type InteractionCreateBodyRequest = OmitInsert< >; export type ModalCreateBodyRequest = APIModalInteractionResponse['data'] | Modal; + +export interface ModalCreateOptions { + waitFor?: number; +} diff --git a/src/components/componentcontext.ts b/src/components/componentcontext.ts index 8303f9d..38e5287 100644 --- a/src/components/componentcontext.ts +++ b/src/components/componentcontext.ts @@ -18,16 +18,18 @@ import type { } from '../client/transformers'; import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; import { BaseContext } from '../commands/basecontext'; -import type { +import { ComponentInteractionMessageUpdate, InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest, MakeRequired, MessageWebhookCreateBodyRequest, ModalCreateBodyRequest, + ModalCreateOptions, UnionToTuple, When, } from '../common'; +import { ModalSubmitInteraction } from '../structures'; import { ComponentType, MessageFlags, type RESTGetAPIGuildQuery } from '../types'; export interface ComponentContext< @@ -150,8 +152,11 @@ export class ComponentContext< return this.interaction.deleteResponse(); } - modal(body: ModalCreateBodyRequest) { - return this.interaction.modal(body); + modal(body: ModalCreateBodyRequest, options?: undefined): Promise; + modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise; + modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) { + if (options === undefined) return this.interaction.modal(body); + return this.interaction.modal(body, options); } /** diff --git a/src/components/modalcontext.ts b/src/components/modalcontext.ts index 7e5aade..d691481 100644 --- a/src/components/modalcontext.ts +++ b/src/components/modalcontext.ts @@ -1,4 +1,4 @@ -import type { AllChannels, Interaction, ModalCommand, ModalSubmitInteraction, ReturnCache } from '..'; +import type { AllChannels, ModalCommand, ModalSubmitInteraction, ReturnCache } from '..'; import type { GuildMemberStructure, GuildStructure, @@ -8,12 +8,13 @@ import type { } from '../client/transformers'; import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands'; import { BaseContext } from '../commands/basecontext'; -import type { +import { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest, MakeRequired, MessageWebhookCreateBodyRequest, ModalCreateBodyRequest, + ModalCreateOptions, UnionToTuple, When, } from '../common'; @@ -119,9 +120,11 @@ export class ModalContext extends return this.interaction.deleteResponse(); } - modal(body: ModalCreateBodyRequest): ReturnType { - //@ts-expect-error - return this.interaction.modal(body); + modal(body: ModalCreateBodyRequest, options?: undefined): Promise; + modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise; + modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) { + // @ts-expect-error + return this.interaction.modal(body, options); } /** diff --git a/src/structures/Interaction.ts b/src/structures/Interaction.ts index 5a942a6..a153690 100644 --- a/src/structures/Interaction.ts +++ b/src/structures/Interaction.ts @@ -21,6 +21,7 @@ import { type MessageUpdateBodyRequest, type MessageWebhookCreateBodyRequest, type ModalCreateBodyRequest, + ModalCreateOptions, type ObjectToLower, type OmitInsert, type ToClass, @@ -472,11 +473,37 @@ export class Interaction< ) as never; } - modal(body: ModalCreateBodyRequest) { - return this.reply({ + modal(body: ModalCreateBodyRequest, options?: undefined): Promise; + modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise; + async modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) { + if (options !== undefined && !(body instanceof Modal)) { + body = new Modal(body); + } + + if (options === undefined) + return this.reply({ + type: InteractionResponseType.Modal, + data: body, + }); + + const promise = new Promise(res => { + let nodeTimeout: NodeJS.Timeout | undefined; + // body is always a modal here, so we can safely cast it + (body as Modal).__exec = (interaction: ModalSubmitInteraction) => { + res(interaction); + clearTimeout(nodeTimeout); + }; + if (options?.waitFor && options?.waitFor > 0) { + nodeTimeout = setTimeout(() => { + res(null); + }, options.waitFor); + } + }); + await this.reply({ type: InteractionResponseType.Modal, data: body, }); + return promise; } async editOrReply(