diff --git a/packages/bot/src/transformers/interaction.ts b/packages/bot/src/transformers/interaction.ts index abb5a9b33..c7e2c1069 100644 --- a/packages/bot/src/transformers/interaction.ts +++ b/packages/bot/src/transformers/interaction.ts @@ -12,7 +12,7 @@ import { } from '@discordeno/types' import { Collection } from '@discordeno/utils' import type { Bot, Channel, Component, DiscordChannel } from '../index.js' -import type { DiscordInteractionDataResolved } from '../typings.js' +import { MessageFlags, type DiscordInteractionDataResolved } from '../typings.js' import type { Attachment } from './attachment.js' import type { Member } from './member.js' import type { Message } from './message.js' @@ -78,30 +78,58 @@ export interface BaseInteraction { * Sends a response to an interaction. * * @remarks - * Uses `interaction.type`, `interaction.token` and `interaction.id` + * This will send a {@link InteractionResponseTypes.ChannelMessageWithSource}, {@link InteractionResponseTypes.ApplicationCommandAutocompleteResult} or {@link InteractionResponseTypes.Modal} response based on the type of the interaction you are responding to. + * + * If the interaction has been already acknowledged, indicated by {@link Interaction.acknowledged}, it will send a followup message instead. + * + * Uses `interaction.type`, `interaction.token` and `interaction.id`, missing one of these in the desired proprieties may cause unexpected behavior. */ respond: (response: string | InteractionCallbackData, options?: { isPrivate?: boolean }) => Promise /** - * Edit the original response of an interaction. + * Edit the original response of an interaction or a followup if the message id is provided. * * @remarks - * Uses `interaction.token` + * This will edit the original interaction response or, if the interaction has not yet been acknowledged and the type of the interaction is {@link InteractionTypes.MessageComponent} it will instead send a {@link InteractionResponseTypes.UpdateMessage} response instead. + * + * Uses `interaction.type`, `interaction.token` and `interaction.id`, missing one of these in the desired proprieties may cause unexpected behavior. */ - edit: (response: string | InteractionCallbackData) => Promise + edit: (response: string | InteractionCallbackData, messageId?: BigString) => Promise /** - * Defer the interaction. + * Defer the interaction for updating the referenced message at a later time with {@link edit}. * * @remarks - * Uses `interaction.type`, `interaction.token` and `interaction.id` + * This will send a {@link InteractionResponseTypes.DeferredUpdateMessage} response. + * + * Uses `interaction.type`, `interaction.token` and `interaction.id`, missing one of these in the desired proprieties may cause unexpected behavior. + */ + deferEdit: () => Promise + /** + * Defer the interaction for updating the response at a later time with {@link edit}. + * + * @remarks + * This will send a {@link InteractionResponseTypes.DeferredChannelMessageWithSource} response. + * + * Uses `interaction.type`, `interaction.token` and `interaction.id`, missing one of these in the desired proprieties may cause unexpected behavior. */ defer: (isPrivate?: boolean) => Promise /** - * Delete the original interaction response or a followup message + * Delete the original interaction response or a followup if the message id is provided. * * @remarks - * Uses `interaction.type` and `interaction.token` + * Uses `interaction.type` and `interaction.token`, missing one of these in the desired proprieties may cause unexpected behavior. */ delete: (messageId?: BigString) => Promise + /** + * Sends a response with an upgrade button. + * + * @remarks + * This will send a {@link InteractionResponseTypes.PremiumRequired} response. + * + * This can be used only with applications that have monetization enabled. + * + * Uses `interaction.type`, `interaction.token` and `interaction.id`, missing one of these in the desired proprieties may cause unexpected behavior. + */ + premiumRequired: () => Promise } const baseInteraction: Partial & BaseInteraction = { @@ -110,12 +138,11 @@ const baseInteraction: Partial & BaseInteraction = { // If user provides a string, change it to a response object if (typeof response === 'string') response = { content: response } - // If user provides an object, determine if it should be an autocomplete or a modal response - else if (response.title) type = InteractionResponseTypes.Modal - else if (this.type === InteractionTypes.ApplicationCommandAutocomplete) type = InteractionResponseTypes.ApplicationCommandAutocompleteResult - // If user wants to send a private message - if (type === InteractionResponseTypes.ChannelMessageWithSource && options?.isPrivate) response.flags = 64 + // If user provides an object, determine if it should be an autocomplete or a modal response + if (response.title) type = InteractionResponseTypes.Modal + if (this.type === InteractionTypes.ApplicationCommandAutocomplete) type = InteractionResponseTypes.ApplicationCommandAutocompleteResult + if (type === InteractionResponseTypes.ChannelMessageWithSource && options?.isPrivate) response.flags = MessageFlags.Ephemeral // Since this has already been given a response, any further responses must be followups. if (this.acknowledged) return await this.bot!.helpers.sendFollowupMessage(this.token!, response) @@ -124,43 +151,57 @@ const baseInteraction: Partial & BaseInteraction = { if (this.type === InteractionTypes.ModalSubmit && type === InteractionResponseTypes.Modal) throw new Error('Cannot respond to a modal interaction with another modal.') - // Autocomplete response can only be used for autocomplete interactions - if (this.type === InteractionTypes.ApplicationCommandAutocomplete && type !== InteractionResponseTypes.ApplicationCommandAutocompleteResult) - throw new Error('Cannot respond to an autocomplete interaction with a modal or message.') - - // If user has not already responded to this interaction we need to send an original response this.acknowledged = true return await this.bot!.helpers.sendInteractionResponse(this.id!, this.token!, { type, data: response }) }, - - async edit(response) { - if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot edit an autocomplete interaction') + async edit(response, messageId) { + if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot edit an autocomplete interaction.') // If user provides a string, change it to a response object if (typeof response === 'string') response = { content: response } + if (messageId) { + return await this.bot?.helpers.editFollowupMessage(this.token!, messageId, response) + } + + if (!this.acknowledged) { + if (this.type !== InteractionTypes.MessageComponent) + throw new Error("This interaction has not been responded to yet and this isn't a MessageComponent interaction.") + + this.acknowledged = true + return await this.bot!.helpers.sendInteractionResponse(this.id!, this.token!, { type: InteractionResponseTypes.UpdateMessage, data: response }) + } + return await this.bot!.helpers.editOriginalInteractionResponse(this.token!, response) }, + async deferEdit() { + if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot edit an autocomplete interaction.') + if (this.acknowledged) throw new Error('Cannot defer an already responded interaction.') - async defer(isPrivate) { - if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot defer an autocomplete interaction') - if (this.acknowledged) throw new Error('Cannot defer an already responded interaction') - - // Determine the type of defer response - const type = - this.type === InteractionTypes.MessageComponent - ? InteractionResponseTypes.DeferredUpdateMessage - : InteractionResponseTypes.DeferredChannelMessageWithSource - - // If user wants to send a private message - const data: InteractionCallbackData = {} - if (isPrivate) data.flags = 64 + if (this.type !== InteractionTypes.MessageComponent) + throw new Error("Cannot defer to then edit an interaction that isn't a MessageComponent interaction.") this.acknowledged = true - return await this.bot!.helpers.sendInteractionResponse(this.id!, this.token!, { type, data }) + return await this.bot!.helpers.sendInteractionResponse(this.id!, this.token!, { type: InteractionResponseTypes.DeferredUpdateMessage }) }, + async defer(isPrivate) { + if (this.acknowledged) throw new Error('Cannot defer an already responded interaction.') - async delete(messageId?: BigString) { + this.acknowledged = true + return await this.bot!.helpers.sendInteractionResponse(this.id!, this.token!, { + type: InteractionResponseTypes.DeferredChannelMessageWithSource, + data: { + flags: isPrivate ? MessageFlags.Ephemeral : undefined, + }, + }) + }, + async premiumRequired() { + if (this.acknowledged) throw new Error('Cannot respond to an already responded interaction.') + + this.acknowledged = true + return await this.bot!.helpers.sendInteractionResponse(this.id!, this.token!, { type: InteractionResponseTypes.PremiumRequired }) + }, + async delete(messageId) { if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot delete an autocomplete interaction') if (messageId) return await this.bot?.helpers.deleteFollowupMessage(this.token!, messageId)