'use strict'; const { Error } = require('../../errors'); const { InteractionResponseTypes } = require('../../util/Constants'); const MessageFlags = require('../../util/MessageFlags'); const MessagePayload = require('../MessagePayload'); /** * Interface for classes that support shared interaction response types. * @interface */ class InteractionResponses { /** * Options for deferring the reply to an {@link Interaction}. * @typedef {Object} InteractionDeferOptions * @property {boolean} [ephemeral] Whether the reply should be ephemeral */ /** * Options for a reply to an {@link Interaction}. * @typedef {BaseMessageOptions} InteractionReplyOptions * @property {boolean} [ephemeral] Whether the reply should be ephemeral */ /** * Defers the reply to this interaction. * @param {InteractionDeferOptions} [options] Options for deferring the reply to this interaction * @returns {Promise} * @example * // Defer the reply to this interaction * interaction.defer() * .then(console.log) * .catch(console.error) * @example * // Defer to send an ephemeral reply later * interaction.defer({ ephemeral: true }) * .then(console.log) * .catch(console.error); */ async defer(options = {}) { if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED'); if (options.fetchReply && options.ephemeral) throw new Error('INTERACTION_FETCH_EPHEMERAL'); this.ephemeral = options.ephemeral ?? false; await this.client.api.interactions(this.id, this.token).callback.post({ data: { type: InteractionResponseTypes.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, data: { flags: options.ephemeral ? MessageFlags.FLAGS.EPHEMERAL : undefined, }, }, }); this.deferred = true; return options.fetchReply ? this.fetchReply() : undefined; } /** * Creates a reply to this interaction. * @param {string|MessagePayload|InteractionReplyOptions} options The options for the reply * @returns {Promise} * @example * // Reply to the interaction with an embed * const embed = new MessageEmbed().setDescription('Pong!'); * * interaction.reply(embed) * .then(console.log) * .catch(console.error); * @example * // Create an ephemeral reply * interaction.reply({ content: 'Pong!', ephemeral: true }) * .then(console.log) * .catch(console.error); */ async reply(options) { if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED'); if (options.fetchReply && options.ephemeral) throw new Error('INTERACTION_FETCH_EPHEMERAL'); this.ephemeral = options.ephemeral ?? false; let messagePayload; if (options instanceof MessagePayload) messagePayload = options; else messagePayload = MessagePayload.create(this, options); const { data, files } = await messagePayload.resolveData().resolveFiles(); await this.client.api.interactions(this.id, this.token).callback.post({ data: { type: InteractionResponseTypes.CHANNEL_MESSAGE_WITH_SOURCE, data, }, files, }); this.replied = true; return options.fetchReply ? this.fetchReply() : undefined; } /** * Fetches the initial reply to this interaction. * @see Webhook#fetchMessage * @returns {Promise} * @example * // Fetch the reply to this interaction * interaction.fetchReply() * .then(reply => console.log(`Replied with ${reply.content}`)) * .catch(console.error); */ fetchReply() { if (this.ephemeral) throw new Error('INTERACTION_EPHEMERAL_REPLIED'); return this.webhook.fetchMessage('@original'); } /** * Edits the initial reply to this interaction. * @see Webhook#editMessage * @param {string|MessagePayload|WebhookEditMessageOptions} options The new options for the message * @returns {Promise} * @example * // Edit the reply to this interaction * interaction.editReply('New content') * .then(console.log) * .catch(console.error); */ async editReply(options) { if (!this.deferred && !this.replied) throw new Error('INTERACTION_NOT_REPLIED'); const message = await this.webhook.editMessage('@original', options); this.replied = true; return message; } /** * Deletes the initial reply to this interaction. * @see Webhook#deleteMessage * @returns {Promise} * @example * // Delete the reply to this interaction * interaction.deleteReply() * .then(console.log) * .catch(console.error); */ async deleteReply() { if (this.ephemeral) throw new Error('INTERACTION_EPHEMERAL_REPLIED'); await this.webhook.deleteMessage('@original'); } /** * Send a follow-up message to this interaction. * @param {string|MessagePayload|InteractionReplyOptions} options The options for the reply * @returns {Promise} */ followUp(options) { return this.webhook.send(options); } /** * Defers an update to the message to which the component was attached. * @param {InteractionDeferUpdateOptions} [options] Options for deferring the update to this interaction * @returns {Promise} * @example * // Defer updating and reset the component's loading state * interaction.deferUpdate() * .then(console.log) * .catch(console.error); */ async deferUpdate(options = {}) { if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED'); if (options.fetchReply && new MessageFlags(this.message.flags).has(MessageFlags.FLAGS.EPHEMERAL)) { throw new Error('INTERACTION_FETCH_EPHEMERAL'); } await this.client.api.interactions(this.id, this.token).callback.post({ data: { type: InteractionResponseTypes.DEFERRED_MESSAGE_UPDATE, }, }); this.deferred = true; return options.fetchReply ? this.fetchReply() : undefined; } /** * Updates the original message whose button was pressed. * @param {string|MessagePayload|WebhookEditMessageOptions} options The options for the reply * @returns {Promise} * @example * // Remove the components from the message * interaction.update({ * content: "A button was clicked", * components: [] * }) * .then(console.log) * .catch(console.error); */ async update(options) { if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED'); if (options.fetchReply && new MessageFlags(this.message.flags).has(MessageFlags.FLAGS.EPHEMERAL)) { throw new Error('INTERACTION_FETCH_EPHEMERAL'); } let messagePayload; if (options instanceof MessagePayload) messagePayload = options; else messagePayload = MessagePayload.create(this, options); const { data, files } = await messagePayload.resolveData().resolveFiles(); await this.client.api.interactions(this.id, this.token).callback.post({ data: { type: InteractionResponseTypes.UPDATE_MESSAGE, data, }, files, }); this.replied = true; return options.fetchReply ? this.fetchReply() : undefined; } static applyToClass(structure, ignore = []) { const props = ['defer', 'reply', 'fetchReply', 'editReply', 'deleteReply', 'followUp', 'deferUpdate', 'update']; for (const prop of props) { if (ignore.includes(prop)) continue; Object.defineProperty( structure.prototype, prop, Object.getOwnPropertyDescriptor(InteractionResponses.prototype, prop), ); } } } module.exports = InteractionResponses;