From e4450c8417624b71d779cb4f41692538f9165e10 Mon Sep 17 00:00:00 2001 From: sowgro Date: Sat, 2 Sep 2023 19:12:47 -0400 Subject: first commit --- node_modules/discord.js/src/structures/Webhook.js | 479 ++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 node_modules/discord.js/src/structures/Webhook.js (limited to 'node_modules/discord.js/src/structures/Webhook.js') diff --git a/node_modules/discord.js/src/structures/Webhook.js b/node_modules/discord.js/src/structures/Webhook.js new file mode 100644 index 0000000..738d9e7 --- /dev/null +++ b/node_modules/discord.js/src/structures/Webhook.js @@ -0,0 +1,479 @@ +'use strict'; + +const { makeURLSearchParams } = require('@discordjs/rest'); +const { lazy } = require('@discordjs/util'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { Routes, WebhookType } = require('discord-api-types/v10'); +const MessagePayload = require('./MessagePayload'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const DataResolver = require('../util/DataResolver'); + +const getMessage = lazy(() => require('./Message').Message); + +/** + * Represents a webhook. + */ +class Webhook { + constructor(client, data) { + /** + * The client that instantiated the webhook + * @name Webhook#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + if (data) this._patch(data); + } + + _patch(data) { + if ('name' in data) { + /** + * The name of the webhook + * @type {string} + */ + this.name = data.name; + } + + /** + * The token for the webhook, unavailable for follower webhooks and webhooks owned by another application. + * @name Webhook#token + * @type {?string} + */ + Object.defineProperty(this, 'token', { + value: data.token ?? null, + writable: true, + configurable: true, + }); + + if ('avatar' in data) { + /** + * The avatar for the webhook + * @type {?string} + */ + this.avatar = data.avatar; + } + + /** + * The webhook's id + * @type {Snowflake} + */ + this.id = data.id; + + if ('type' in data) { + /** + * The type of the webhook + * @type {WebhookType} + */ + this.type = data.type; + } + + if ('guild_id' in data) { + /** + * The guild the webhook belongs to + * @type {Snowflake} + */ + this.guildId = data.guild_id; + } + + if ('channel_id' in data) { + /** + * The id of the channel the webhook belongs to + * @type {Snowflake} + */ + this.channelId = data.channel_id; + } + + if ('user' in data) { + /** + * The owner of the webhook + * @type {?(User|APIUser)} + */ + this.owner = this.client.users?._add(data.user) ?? data.user; + } else { + this.owner ??= null; + } + + if ('application_id' in data) { + /** + * The application that created this webhook + * @type {?Snowflake} + */ + this.applicationId = data.application_id; + } else { + this.applicationId ??= null; + } + + if ('source_guild' in data) { + /** + * The source guild of the webhook + * @type {?(Guild|APIGuild)} + */ + this.sourceGuild = this.client.guilds?.resolve(data.source_guild.id) ?? data.source_guild; + } else { + this.sourceGuild ??= null; + } + + if ('source_channel' in data) { + /** + * The source channel of the webhook + * @type {?(NewsChannel|APIChannel)} + */ + this.sourceChannel = this.client.channels?.resolve(data.source_channel?.id) ?? data.source_channel; + } else { + this.sourceChannel ??= null; + } + } + + /** + * Options that can be passed into send. + * @typedef {BaseMessageOptions} WebhookMessageCreateOptions + * @property {boolean} [tts=false] Whether the message should be spoken aloud + * @property {MessageFlags} [flags] Which flags to set for the message. + * Only the {@link MessageFlags.SuppressEmbeds} flag can be set. + * @property {string} [username=this.name] Username override for the message + * @property {string} [avatarURL] Avatar URL override for the message + * @property {Snowflake} [threadId] The id of the thread in the channel to send to. + * For interaction webhooks, this property is ignored + * @property {string} [threadName] Name of the thread to create (only available if webhook is in a forum channel) + */ + + /** + * Options that can be passed into editMessage. + * @typedef {BaseMessageOptions} WebhookMessageEditOptions + * @property {Attachment[]} [attachments] Attachments to send with the message + * @property {Snowflake} [threadId] The id of the thread this message belongs to + * For interaction webhooks, this property is ignored + */ + + /** + * The channel the webhook belongs to + * @type {?(TextChannel|VoiceChannel|StageChannel|NewsChannel|ForumChannel)} + * @readonly + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * Sends a message with this webhook. + * @param {string|MessagePayload|WebhookMessageCreateOptions} options The options to provide + * @returns {Promise} + * @example + * // Send a basic message + * webhook.send('hello!') + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a basic message in a thread + * webhook.send({ content: 'hello!', threadId: '836856309672348295' }) + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a remote file + * webhook.send({ + * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send a local file + * webhook.send({ + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send an embed with a local image inside + * webhook.send({ + * content: 'This is an embed', + * embeds: [{ + * thumbnail: { + * url: 'attachment://file.jpg' + * } + * }], + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + */ + async send(options) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + let messagePayload; + + if (options instanceof MessagePayload) { + messagePayload = options.resolveBody(); + } else { + messagePayload = MessagePayload.create(this, options).resolveBody(); + } + + const query = makeURLSearchParams({ + wait: true, + thread_id: messagePayload.options.threadId, + }); + + const { body, files } = await messagePayload.resolveFiles(); + const d = await this.client.rest.post(Routes.webhook(this.id, this.token), { + body, + files, + query, + auth: false, + }); + + if (!this.client.channels) return d; + return this.client.channels.cache.get(d.channel_id)?.messages._add(d, false) ?? new (getMessage())(this.client, d); + } + + /** + * Sends a raw slack message with this webhook. + * @param {Object} body The raw body to send + * @returns {Promise} + * @example + * // Send a slack message + * webhook.sendSlackMessage({ + * 'username': 'Wumpus', + * 'attachments': [{ + * 'pretext': 'this looks pretty cool', + * 'color': '#F0F', + * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png', + * 'footer': 'Powered by sneks', + * 'ts': Date.now() / 1_000 + * }] + * }).catch(console.error); + * @see {@link https://api.slack.com/messaging/webhooks} + */ + async sendSlackMessage(body) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + const data = await this.client.rest.post(Routes.webhookPlatform(this.id, this.token, 'slack'), { + query: makeURLSearchParams({ wait: true }), + auth: false, + body, + }); + return data.toString() === 'ok'; + } + + /** + * Options used to edit a {@link Webhook}. + * @typedef {Object} WebhookEditOptions + * @property {string} [name=this.name] The new name for the webhook + * @property {?(BufferResolvable)} [avatar] The new avatar for the webhook + * @property {GuildTextChannelResolvable|VoiceChannel|StageChannel|ForumChannel} [channel] + * The new channel for the webhook + * @property {string} [reason] Reason for editing the webhook + */ + + /** + * Edits this webhook. + * @param {WebhookEditOptions} options Options for editing the webhook + * @returns {Promise} + */ + async edit({ name = this.name, avatar, channel, reason }) { + if (avatar && !(typeof avatar === 'string' && avatar.startsWith('data:'))) { + avatar = await DataResolver.resolveImage(avatar); + } + channel &&= channel.id ?? channel; + const data = await this.client.rest.patch(Routes.webhook(this.id, channel ? undefined : this.token), { + body: { name, avatar, channel_id: channel }, + reason, + auth: !this.token || Boolean(channel), + }); + + this.name = data.name; + this.avatar = data.avatar; + this.channelId = data.channel_id; + return this; + } + + /** + * Options that can be passed into fetchMessage. + * @typedef {options} WebhookFetchMessageOptions + * @property {boolean} [cache=true] Whether to cache the message. + * @property {Snowflake} [threadId] The id of the thread this message belongs to. + * For interaction webhooks, this property is ignored + */ + + /** + * Gets a message that was sent by this webhook. + * @param {Snowflake|'@original'} message The id of the message to fetch + * @param {WebhookFetchMessageOptions} [options={}] The options to provide to fetch the message. + * @returns {Promise} Returns the message sent by this webhook + */ + async fetchMessage(message, { threadId } = {}) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + const data = await this.client.rest.get(Routes.webhookMessage(this.id, this.token, message), { + query: threadId ? makeURLSearchParams({ thread_id: threadId }) : undefined, + auth: false, + }); + + if (!this.client.channels) return data; + return ( + this.client.channels.cache.get(data.channel_id)?.messages._add(data, false) ?? + new (getMessage())(this.client, data) + ); + } + + /** + * Edits a message that was sent by this webhook. + * @param {MessageResolvable|'@original'} message The message to edit + * @param {string|MessagePayload|WebhookMessageEditOptions} options The options to provide + * @returns {Promise} Returns the message edited by this webhook + */ + async editMessage(message, options) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + let messagePayload; + + if (options instanceof MessagePayload) messagePayload = options; + else messagePayload = MessagePayload.create(this, options); + + const { body, files } = await messagePayload.resolveBody().resolveFiles(); + + const d = await this.client.rest.patch( + Routes.webhookMessage(this.id, this.token, typeof message === 'string' ? message : message.id), + { + body, + files, + query: messagePayload.options.threadId + ? makeURLSearchParams({ thread_id: messagePayload.options.threadId }) + : undefined, + auth: false, + }, + ); + + const channelManager = this.client.channels; + if (!channelManager) return d; + + const messageManager = channelManager.cache.get(d.channel_id)?.messages; + if (!messageManager) return new (getMessage())(this.client, d); + + const existing = messageManager.cache.get(d.id); + if (!existing) return messageManager._add(d); + + const clone = existing._clone(); + clone._patch(d); + return clone; + } + + /** + * Deletes the webhook. + * @param {string} [reason] Reason for deleting this webhook + * @returns {Promise} + */ + delete(reason) { + return this.client.deleteWebhook(this.id, { token: this.token, reason }); + } + + /** + * Delete a message that was sent by this webhook. + * @param {MessageResolvable|'@original'} message The message to delete + * @param {Snowflake} [threadId] The id of the thread this message belongs to + * @returns {Promise} + */ + async deleteMessage(message, threadId) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + await this.client.rest.delete( + Routes.webhookMessage(this.id, this.token, typeof message === 'string' ? message : message.id), + { + query: threadId ? makeURLSearchParams({ thread_id: threadId }) : undefined, + auth: false, + }, + ); + } + + /** + * The timestamp the webhook was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the webhook was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The URL of this webhook + * @type {string} + * @readonly + */ + get url() { + return this.client.options.rest.api + Routes.webhook(this.id, this.token); + } + + /** + * A link to the webhook's avatar. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + avatarURL(options = {}) { + return this.avatar && this.client.rest.cdn.avatar(this.id, this.avatar, options); + } + + /** + * Whether this webhook is created by a user. + * @returns {boolean} + */ + isUserCreated() { + return Boolean(this.type === WebhookType.Incoming && this.owner && !this.owner.bot); + } + + /** + * Whether this webhook is created by an application. + * @returns {boolean} + */ + isApplicationCreated() { + return this.type === WebhookType.Application; + } + + /** + * Whether or not this webhook is a channel follower webhook. + * @returns {boolean} + */ + isChannelFollower() { + return this.type === WebhookType.ChannelFollower; + } + + /** + * Whether or not this webhook is an incoming webhook. + * @returns {boolean} + */ + isIncoming() { + return this.type === WebhookType.Incoming; + } + + static applyToClass(structure, ignore = []) { + for (const prop of [ + 'send', + 'sendSlackMessage', + 'fetchMessage', + 'edit', + 'editMessage', + 'delete', + 'deleteMessage', + 'createdTimestamp', + 'createdAt', + 'url', + ]) { + if (ignore.includes(prop)) continue; + Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(Webhook.prototype, prop)); + } + } +} + +module.exports = Webhook; -- cgit v1.2.3