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 <marcos22dev@gmail.com>
This commit is contained in:
Marcos Susaña 2025-06-19 23:12:42 -04:00 committed by GitHub
parent 6e037ae964
commit 798f648955
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 75 additions and 23 deletions

View File

@ -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<M extends keyof RegisteredMiddlewares = never> ex
return this.interaction.write<WR>(body, withResponse);
}
modal(body: ModalCreateBodyRequest) {
return this.interaction.modal(body);
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
if (options === undefined) return this.interaction.modal(body);
return this.interaction.modal(body, options);
}
deferReply<WR extends boolean = false>(

View File

@ -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<WR>(body, withResponse);
}
modal(body: ModalCreateBodyRequest) {
return this.interaction.modal(body);
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
if (options === undefined) return this.interaction.modal(body);
return this.interaction.modal(body, options);
}
deferReply<WR extends boolean = false>(

View File

@ -70,3 +70,7 @@ export type InteractionCreateBodyRequest = OmitInsert<
>;
export type ModalCreateBodyRequest = APIModalInteractionResponse['data'] | Modal;
export interface ModalCreateOptions {
waitFor?: number;
}

View File

@ -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<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
if (options === undefined) return this.interaction.modal(body);
return this.interaction.modal(body, options);
}
/**

View File

@ -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<M extends keyof RegisteredMiddlewares = never> extends
return this.interaction.deleteResponse();
}
modal(body: ModalCreateBodyRequest): ReturnType<Interaction['modal']> {
//@ts-expect-error
return this.interaction.modal(body);
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
// @ts-expect-error
return this.interaction.modal(body, options);
}
/**

View File

@ -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<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
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<ModalSubmitInteraction | null>(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<FR extends boolean = false>(