diff --git a/src/client/WebhookClient.js b/src/client/WebhookClient.js index cea66319f60f..84a2bc04fb57 100644 --- a/src/client/WebhookClient.js +++ b/src/client/WebhookClient.js @@ -24,6 +24,19 @@ class WebhookClient extends BaseClient { this.id = id; Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true }); } + + // These are here only for documentation purposes - they are implemented by Webhook + /* eslint-disable no-empty-function */ + send() {} + sendSlackMessage() {} + fetchMessage() {} + edit() {} + editMessage() {} + delete() {} + deleteMessage() {} + get createdTimestamp() {} + get createdAt() {} + get url() {} } Webhook.applyToClass(WebhookClient); diff --git a/src/index.js b/src/index.js index 01b78ac70688..3504231bd5cf 100644 --- a/src/index.js +++ b/src/index.js @@ -91,6 +91,7 @@ module.exports = { Integration: require('./structures/Integration'), IntegrationApplication: require('./structures/IntegrationApplication'), Interaction: require('./structures/Interaction'), + InteractionWebhook: require('./structures/InteractionWebhook'), Invite: require('./structures/Invite'), Message: require('./structures/Message'), MessageActionRow: require('./structures/MessageActionRow'), diff --git a/src/structures/APIMessage.js b/src/structures/APIMessage.js index 4f85240becc6..7341b7a04d91 100644 --- a/src/structures/APIMessage.js +++ b/src/structures/APIMessage.js @@ -81,7 +81,8 @@ class APIMessage { */ get isInteraction() { const Interaction = require('./Interaction'); - return this.target instanceof Interaction; + const InteractionWebhook = require('./InteractionWebhook'); + return this.target instanceof Interaction || this.target instanceof InteractionWebhook; } /** @@ -369,10 +370,15 @@ class APIMessage { */ static create(target, content, options, extra = {}) { const Interaction = require('./Interaction'); + const InteractionWebhook = require('./InteractionWebhook'); const Webhook = require('./Webhook'); const WebhookClient = require('../client/WebhookClient'); - const isWebhook = target instanceof Interaction || target instanceof Webhook || target instanceof WebhookClient; + const isWebhook = + target instanceof Interaction || + target instanceof InteractionWebhook || + target instanceof Webhook || + target instanceof WebhookClient; const transformed = this.transformOptions(content, options, extra, isWebhook); return new this(target, transformed); } @@ -382,7 +388,7 @@ module.exports = APIMessage; /** * A target for a message. - * @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction} MessageTarget + * @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook} MessageTarget */ /** diff --git a/src/structures/CommandInteraction.js b/src/structures/CommandInteraction.js index 74127329bd3f..c36f7b5f8532 100644 --- a/src/structures/CommandInteraction.js +++ b/src/structures/CommandInteraction.js @@ -1,8 +1,8 @@ 'use strict'; const Interaction = require('./Interaction'); +const InteractionWebhook = require('./InteractionWebhook'); const InteractionResponses = require('./interfaces/InteractionResponses'); -const WebhookClient = require('../client/WebhookClient'); const { ApplicationCommandOptionTypes } = require('../util/Constants'); /** @@ -45,10 +45,10 @@ class CommandInteraction extends Interaction { this.replied = false; /** - * An associated webhook client, can be used to create deferred replies - * @type {WebhookClient} + * An associated interaction webhook, can be used to further interact with this interaction + * @type {InteractionWebhook} */ - this.webhook = new WebhookClient(this.applicationID, this.token, this.client.options); + this.webhook = new InteractionWebhook(this.client, this.applicationID, this.token); } /** diff --git a/src/structures/InteractionWebhook.js b/src/structures/InteractionWebhook.js new file mode 100644 index 000000000000..2bb197202400 --- /dev/null +++ b/src/structures/InteractionWebhook.js @@ -0,0 +1,44 @@ +'use strict'; + +const Webhook = require('./Webhook'); + +/** + * Represents a webhook for an Interaction + * @implements {Webhook} + */ +class InteractionWebhook { + /** + * @param {Client} client The instantiating client + * @param {Snowflake} id ID of the application + * @param {string} token Token of the interaction + */ + constructor(client, id, token) { + /** + * The client that instantiated the interaction webhook + * @name InteractionWebhook#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + this.id = id; + Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true }); + } + + // These are here only for documentation purposes - they are implemented by Webhook + /* eslint-disable no-empty-function, valid-jsdoc */ + /** + * Sends a message with this webhook. + * @param {string|APIMessage|MessageAdditions} content The content for the reply + * @param {InteractionReplyOptions} [options] Additional options for the reply + * @returns {Promise} + */ + send() {} + fetchMessage() {} + editMessage() {} + deleteMessage() {} + get url() {} +} + +Webhook.applyToClass(InteractionWebhook, ['sendSlackMessage', 'edit', 'delete', 'createdTimestamp', 'createdAt']); + +module.exports = InteractionWebhook; diff --git a/src/structures/MessageComponentInteraction.js b/src/structures/MessageComponentInteraction.js index 711f7c282522..2ca201adf3bd 100644 --- a/src/structures/MessageComponentInteraction.js +++ b/src/structures/MessageComponentInteraction.js @@ -1,8 +1,8 @@ 'use strict'; const Interaction = require('./Interaction'); +const InteractionWebhook = require('./InteractionWebhook'); const InteractionResponses = require('./interfaces/InteractionResponses'); -const WebhookClient = require('../client/WebhookClient'); const { MessageComponentTypes } = require('../util/Constants'); /** @@ -45,10 +45,10 @@ class MessageComponentInteraction extends Interaction { this.replied = false; /** - * An associated webhook client, can be used to create deferred replies - * @type {WebhookClient} + * An associated interaction webhook, can be used to further interact with this interaction + * @type {InteractionWebhook} */ - this.webhook = new WebhookClient(this.applicationID, this.token, this.client.options); + this.webhook = new InteractionWebhook(this.client, this.applicationID, this.token); } /** diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 9019f4573532..157ab74692e5 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -325,7 +325,7 @@ class Webhook { return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size); } - static applyToClass(structure) { + static applyToClass(structure, ignore = []) { for (const prop of [ 'send', 'sendSlackMessage', @@ -338,6 +338,7 @@ class Webhook { 'createdAt', 'url', ]) { + if (ignore.includes(prop)) continue; Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(Webhook.prototype, prop)); } } diff --git a/src/structures/interfaces/InteractionResponses.js b/src/structures/interfaces/InteractionResponses.js index b1062353996a..377d094c1998 100644 --- a/src/structures/interfaces/InteractionResponses.js +++ b/src/structures/interfaces/InteractionResponses.js @@ -1,9 +1,9 @@ 'use strict'; -const WebhookClient = require('../../client/WebhookClient'); const { InteractionResponseTypes } = require('../../util/Constants'); const MessageFlags = require('../../util/MessageFlags'); const APIMessage = require('../APIMessage'); +const InteractionWebhook = require('../InteractionWebhook'); /** * Interface for classes that support shared interaction response types. @@ -24,10 +24,10 @@ class InteractionResponses { this.replied = false; /** - * An associated webhook client, can be used to create deferred replies - * @type {WebhookClient} + * An associated interaction webhook, can be used to further interact with this interaction + * @type {InteractionWebhook} */ - this.webhook = new WebhookClient(this.applicationID, this.token, this.client.options); + this.webhook = new InteractionWebhook(this.client, this.applicationID, this.token); } /** @@ -114,9 +114,8 @@ class InteractionResponses { * .then(reply => console.log(`Replied with ${reply.content}`)) * .catch(console.error); */ - async fetchReply() { - const raw = await this.webhook.fetchMessage('@original'); - return this.channel?.messages.add(raw) ?? raw; + fetchReply() { + return this.webhook.fetchMessage('@original'); } /** @@ -131,9 +130,8 @@ class InteractionResponses { * .then(console.log) * .catch(console.error); */ - async editReply(content, options) { - const raw = await this.webhook.editMessage('@original', content, options); - return this.channel?.messages.add(raw) ?? raw; + editReply(content, options) { + return this.webhook.editMessage('@original', content, options); } /** @@ -156,16 +154,8 @@ class InteractionResponses { * @param {InteractionReplyOptions} [options] Additional options for the reply * @returns {Promise} */ - async followUp(content, options) { - const apiMessage = content instanceof APIMessage ? content : APIMessage.create(this, content, options); - const { data, files } = await apiMessage.resolveData().resolveFiles(); - - const raw = await this.client.api.webhooks(this.applicationID, this.token).post({ - data, - files, - }); - - return this.channel?.messages.add(raw) ?? raw; + followUp(content, options) { + return this.webhook.send(content, options); } /** diff --git a/typings/index.d.ts b/typings/index.d.ts index 568dd268a178..bc427646cd75 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -467,7 +467,7 @@ declare module 'discord.js' { public deferred: boolean; public options: CommandInteractionOption[]; public replied: boolean; - public webhook: WebhookClient; + public webhook: InteractionWebhook; public defer(options?: InteractionDeferOptions): Promise; public deleteReply(): Promise; public editReply( @@ -1136,6 +1136,30 @@ declare module 'discord.js' { public isMessageComponent(): this is MessageComponentInteraction; } + export class InteractionWebhook extends PartialWebhookMixin() { + constructor(client: Client, id: Snowflake, token: string); + public token: string; + public send( + content: APIMessageContentResolvable | (InteractionReplyOptions & { split?: false }) | MessageAdditions, + ): Promise; + public send(options: InteractionReplyOptions & { split: true | SplitOptions }): Promise<(Message | RawMessage)[]>; + public send( + options: InteractionReplyOptions | APIMessage, + ): Promise; + public send( + content: StringResolvable, + options: (InteractionReplyOptions & { split?: false }) | MessageAdditions, + ): Promise; + public send( + content: StringResolvable, + options: InteractionReplyOptions & { split: true | SplitOptions }, + ): Promise<(Message | RawMessage)[]>; + public send( + content: StringResolvable, + options: InteractionReplyOptions, + ): Promise; + } + export class Invite extends Base { constructor(client: Client, data: unknown); public channel: GuildChannel | PartialGroupDMChannel; @@ -1319,7 +1343,7 @@ declare module 'discord.js' { public deferred: boolean; public message: Message | RawMessage; public replied: boolean; - public webhook: WebhookClient; + public webhook: InteractionWebhook; public defer(ephemeral?: boolean): Promise; public deferUpdate(): Promise; public deleteReply(): Promise; @@ -2472,18 +2496,15 @@ declare module 'discord.js' { stopTyping(force?: boolean): void; } + function PartialWebhookMixin(Base?: Constructable): Constructable; function WebhookMixin(Base?: Constructable): Constructable; function VolumeMixin(base: Constructable): Constructable; - interface WebhookFields { + interface PartialWebhookFields { id: Snowflake; - readonly createdAt: Date; - readonly createdTimestamp: number; readonly url: string; - delete(reason?: string): Promise; deleteMessage(message: MessageResolvable | '@original'): Promise; - edit(options: WebhookEditData): Promise; editMessage( message: MessageResolvable | '@original', content: APIMessageContentResolvable | APIMessage | MessageAdditions, @@ -2511,7 +2532,14 @@ declare module 'discord.js' { content: StringResolvable, options: WebhookMessageOptions, ): Promise; - sendSlackMessage(body: unknown): Promise; + } + + interface WebhookFields extends PartialWebhookFields { + readonly createdAt: Date; + readonly createdTimestamp: number; + delete(reason?: string): Promise; + edit(options: WebhookEditData): Promise; + sendSlackMessage(body: object): Promise; } //#endregion @@ -3492,6 +3520,7 @@ declare module 'discord.js' { type MessageTarget = | Interaction + | InteractionWebhook | TextChannel | NewsChannel | DMChannel