From d2d3a80c556a104099a1ddb1b24f1b921c553257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Rom=C3=A1n?= Date: Mon, 24 Jan 2022 20:17:21 +0100 Subject: [PATCH] refactor: switch to /builders `Embed` (#7067) --- .github/COMMIT_CONVENTION.md | 2 +- packages/discord.js/src/errors/Messages.js | 7 - packages/discord.js/src/index.js | 2 +- packages/discord.js/src/structures/Message.js | 9 +- .../discord.js/src/structures/MessageEmbed.js | 577 ------------------ .../src/structures/MessagePayload.js | 6 +- packages/discord.js/src/structures/Webhook.js | 2 +- .../interfaces/InteractionResponses.js | 2 +- .../structures/interfaces/TextBasedChannel.js | 2 +- packages/discord.js/test/sendtest.js | 10 +- packages/discord.js/test/webhooktest.js | 22 +- packages/discord.js/typings/index.d.ts | 107 +--- packages/discord.js/typings/index.test-d.ts | 4 +- 13 files changed, 23 insertions(+), 729 deletions(-) delete mode 100644 packages/discord.js/src/structures/MessageEmbed.js diff --git a/.github/COMMIT_CONVENTION.md b/.github/COMMIT_CONVENTION.md index f7207c4d2cab..2e89be41fc90 100644 --- a/.github/COMMIT_CONVENTION.md +++ b/.github/COMMIT_CONVENTION.md @@ -68,7 +68,7 @@ Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore` ### Scope -The scope could be anything specifying the place of the commit change. For example `GuildMember`, `Guild`, `Message`, `MessageEmbed` etc... +The scope could be anything specifying the place of the commit change. For example `GuildMember`, `Guild`, `Message`, `TextChannel` etc... ### Subject diff --git a/packages/discord.js/src/errors/Messages.js b/packages/discord.js/src/errors/Messages.js index 97618abdee3e..c93563d8843b 100644 --- a/packages/discord.js/src/errors/Messages.js +++ b/packages/discord.js/src/errors/Messages.js @@ -41,13 +41,6 @@ const Messages = { INVITE_OPTIONS_MISSING_CHANNEL: 'A valid guild channel must be provided when GuildScheduledEvent is EXTERNAL.', - EMBED_TITLE: 'MessageEmbed title must be a string.', - EMBED_FIELD_NAME: 'MessageEmbed field names must be non-empty strings.', - EMBED_FIELD_VALUE: 'MessageEmbed field values must be non-empty strings.', - EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.', - EMBED_DESCRIPTION: 'MessageEmbed description must be a string.', - EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.', - BUTTON_LABEL: 'MessageButton label must be a string', BUTTON_URL: 'MessageButton URL must be a string', BUTTON_CUSTOM_ID: 'MessageButton customId must be a string', diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index b23532ac5a5c..ec43ca603f45 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -90,6 +90,7 @@ exports.Collector = require('./structures/interfaces/Collector'); exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver'); exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction'); exports.DMChannel = require('./structures/DMChannel'); +exports.Embed = require('@discordjs/builders').Embed; exports.Emoji = require('./structures/Emoji').Emoji; exports.Guild = require('./structures/Guild').Guild; exports.GuildAuditLogs = require('./structures/GuildAuditLogs'); @@ -115,7 +116,6 @@ exports.MessageAttachment = require('./structures/MessageAttachment'); exports.MessageCollector = require('./structures/MessageCollector'); exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction'); exports.MessageContextMenuCommandInteraction = require('./structures/MessageContextMenuCommandInteraction'); -exports.MessageEmbed = require('./structures/MessageEmbed'); exports.MessageMentions = require('./structures/MessageMentions'); exports.MessagePayload = require('./structures/MessagePayload'); exports.MessageReaction = require('./structures/MessageReaction'); diff --git a/packages/discord.js/src/structures/Message.js b/packages/discord.js/src/structures/Message.js index a9c52bc780e2..556f71bca3ed 100644 --- a/packages/discord.js/src/structures/Message.js +++ b/packages/discord.js/src/structures/Message.js @@ -1,6 +1,6 @@ 'use strict'; -const { createComponent } = require('@discordjs/builders'); +const { createComponent, Embed } = require('@discordjs/builders'); const { Collection } = require('@discordjs/collection'); const { DiscordSnowflake } = require('@sapphire/snowflake'); const { InteractionType, ChannelType, MessageType } = require('discord-api-types/v9'); @@ -8,7 +8,6 @@ const Base = require('./Base'); const ClientApplication = require('./ClientApplication'); const InteractionCollector = require('./InteractionCollector'); const MessageAttachment = require('./MessageAttachment'); -const Embed = require('./MessageEmbed'); const Mentions = require('./MessageMentions'); const MessagePayload = require('./MessagePayload'); const ReactionCollector = require('./ReactionCollector'); @@ -128,9 +127,9 @@ class Message extends Base { if ('embeds' in data) { /** * A list of embeds in the message - e.g. YouTube Player - * @type {MessageEmbed[]} + * @type {Embed[]} */ - this.embeds = data.embeds.map(e => new Embed(e, true)); + this.embeds = data.embeds.map(e => new Embed(e)); } else { this.embeds = this.embeds?.slice() ?? []; } @@ -632,7 +631,7 @@ class Message extends Base { * Options that can be passed into {@link Message#edit}. * @typedef {Object} MessageEditOptions * @property {?string} [content] Content to be edited - * @property {MessageEmbed[]|APIEmbed[]} [embeds] Embeds to be added/edited + * @property {Embed[]|APIEmbed[]} [embeds] Embeds to be added/edited * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content * @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be edited. * @property {MessageAttachment[]} [attachments] An array of attachments to keep, diff --git a/packages/discord.js/src/structures/MessageEmbed.js b/packages/discord.js/src/structures/MessageEmbed.js deleted file mode 100644 index 06a808449841..000000000000 --- a/packages/discord.js/src/structures/MessageEmbed.js +++ /dev/null @@ -1,577 +0,0 @@ -'use strict'; - -const process = require('node:process'); -const { RangeError } = require('../errors'); -const Util = require('../util/Util'); - -let deprecationEmittedForSetAuthor = false; -let deprecationEmittedForSetFooter = false; - -// TODO: Remove the deprecated code for `setAuthor()` and `setFooter()`. - -/** - * Represents an embed in a message (image/video preview, rich embed, etc.) - */ -class MessageEmbed { - /** - * A `Partial` object is a representation of any existing object. - * This object contains between 0 and all of the original objects parameters. - * This is true regardless of whether the parameters are optional in the base object. - * @typedef {Object} Partial - */ - - /** - * Represents the possible options for a MessageEmbed - * @typedef {Object} MessageEmbedOptions - * @property {string} [title] The title of this embed - * @property {string} [description] The description of this embed - * @property {string} [url] The URL of this embed - * @property {Date|number} [timestamp] The timestamp of this embed - * @property {ColorResolvable} [color] The color of this embed - * @property {EmbedFieldData[]} [fields] The fields of this embed - * @property {Partial} [author] The author of this embed - * @property {Partial} [thumbnail] The thumbnail of this embed - * @property {Partial} [image] The image of this embed - * @property {Partial} [video] The video of this embed - * @property {Partial} [footer] The footer of this embed - */ - - // eslint-disable-next-line valid-jsdoc - /** - * @param {MessageEmbed|MessageEmbedOptions|APIEmbed} [data={}] MessageEmbed to clone or raw embed data - */ - constructor(data = {}, skipValidation = false) { - this.setup(data, skipValidation); - } - - setup(data, skipValidation) { - /** - * The type of this embed, either: - * * `rich` - a generic embed rendered from embed attributes - * * `image` - an image embed - * * `video` - a video embed - * * `gifv` - an animated gif image embed rendered as a video embed - * * `article` - an article embed - * * `link` - a link embed - * @type {string} - * @see {@link https://discord.com/developers/docs/resources/channel#embed-object-embed-types} - * @deprecated - */ - this.type = data.type ?? 'rich'; - - /** - * The title of this embed - * @type {?string} - */ - this.title = data.title ?? null; - - /** - * The description of this embed - * @type {?string} - */ - this.description = data.description ?? null; - - /** - * The URL of this embed - * @type {?string} - */ - this.url = data.url ?? null; - - /** - * The color of this embed - * @type {?number} - */ - this.color = 'color' in data ? Util.resolveColor(data.color) : null; - - /** - * The timestamp of this embed - * @type {?number} - */ - // Date.parse() cannot be used here because data.timestamp might be a number - // eslint-disable-next-line eqeqeq - this.timestamp = data.timestamp != null ? new Date(data.timestamp).getTime() : null; - - /** - * Represents a field of a MessageEmbed - * @typedef {Object} EmbedField - * @property {string} name The name of this field - * @property {string} value The value of this field - * @property {boolean} inline If this field will be displayed inline - */ - - /** - * The fields of this embed - * @type {EmbedField[]} - */ - this.fields = []; - if (data.fields) { - this.fields = skipValidation ? data.fields.map(Util.cloneObject) : this.constructor.normalizeFields(data.fields); - } - - /** - * Represents the thumbnail of a MessageEmbed - * @typedef {Object} MessageEmbedThumbnail - * @property {string} url URL for this thumbnail - * @property {string} proxyURL ProxyURL for this thumbnail - * @property {number} height Height of this thumbnail - * @property {number} width Width of this thumbnail - */ - - /** - * The thumbnail of this embed (if there is one) - * @type {?MessageEmbedThumbnail} - */ - this.thumbnail = data.thumbnail - ? { - url: data.thumbnail.url, - proxyURL: data.thumbnail.proxyURL ?? data.thumbnail.proxy_url, - height: data.thumbnail.height, - width: data.thumbnail.width, - } - : null; - - /** - * Represents the image of a MessageEmbed - * @typedef {Object} MessageEmbedImage - * @property {string} url URL for this image - * @property {string} proxyURL ProxyURL for this image - * @property {number} height Height of this image - * @property {number} width Width of this image - */ - - /** - * The image of this embed, if there is one - * @type {?MessageEmbedImage} - */ - this.image = data.image - ? { - url: data.image.url, - proxyURL: data.image.proxyURL ?? data.image.proxy_url, - height: data.image.height, - width: data.image.width, - } - : null; - - /** - * Represents the video of a MessageEmbed - * @typedef {Object} MessageEmbedVideo - * @property {string} url URL of this video - * @property {string} proxyURL ProxyURL for this video - * @property {number} height Height of this video - * @property {number} width Width of this video - */ - - /** - * The video of this embed (if there is one) - * @type {?MessageEmbedVideo} - * @readonly - */ - this.video = data.video - ? { - url: data.video.url, - proxyURL: data.video.proxyURL ?? data.video.proxy_url, - height: data.video.height, - width: data.video.width, - } - : null; - - /** - * Represents the author field of a MessageEmbed - * @typedef {Object} MessageEmbedAuthor - * @property {string} name The name of this author - * @property {string} url URL of this author - * @property {string} iconURL URL of the icon for this author - * @property {string} proxyIconURL Proxied URL of the icon for this author - */ - - /** - * The author of this embed (if there is one) - * @type {?MessageEmbedAuthor} - */ - this.author = data.author - ? { - name: data.author.name, - url: data.author.url, - iconURL: data.author.iconURL ?? data.author.icon_url, - proxyIconURL: data.author.proxyIconURL ?? data.author.proxy_icon_url, - } - : null; - - /** - * Represents the provider of a MessageEmbed - * @typedef {Object} MessageEmbedProvider - * @property {string} name The name of this provider - * @property {string} url URL of this provider - */ - - /** - * The provider of this embed (if there is one) - * @type {?MessageEmbedProvider} - */ - this.provider = data.provider - ? { - name: data.provider.name, - url: data.provider.name, - } - : null; - - /** - * Represents the footer field of a MessageEmbed - * @typedef {Object} MessageEmbedFooter - * @property {string} text The text of this footer - * @property {string} iconURL URL of the icon for this footer - * @property {string} proxyIconURL Proxied URL of the icon for this footer - */ - - /** - * The footer of this embed - * @type {?MessageEmbedFooter} - */ - this.footer = data.footer - ? { - text: data.footer.text, - iconURL: data.footer.iconURL ?? data.footer.icon_url, - proxyIconURL: data.footer.proxyIconURL ?? data.footer.proxy_icon_url, - } - : null; - } - - /** - * The date displayed on this embed - * @type {?Date} - * @readonly - */ - get createdAt() { - return typeof this.timestamp === 'number' ? new Date(this.timestamp) : null; - } - - /** - * The hexadecimal version of the embed color, with a leading hash - * @type {?string} - * @readonly - */ - get hexColor() { - return this.color ? `#${this.color.toString(16).padStart(6, '0')}` : null; - } - - /** - * The accumulated length for the embed title, description, fields, footer text, and author name - * @type {number} - * @readonly - */ - get length() { - return ( - (this.title?.length ?? 0) + - (this.description?.length ?? 0) + - (this.fields.length >= 1 - ? this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0) - : 0) + - (this.footer?.text.length ?? 0) + - (this.author?.name.length ?? 0) - ); - } - - /** - * Checks if this embed is equal to another one by comparing every single one of their properties. - * @param {MessageEmbed|APIEmbed} embed The embed to compare with - * @returns {boolean} - */ - equals(embed) { - return ( - this.type === embed.type && - this.author?.name === embed.author?.name && - this.author?.url === embed.author?.url && - this.author?.iconURL === (embed.author?.iconURL ?? embed.author?.icon_url) && - this.color === embed.color && - this.title === embed.title && - this.description === embed.description && - this.url === embed.url && - this.timestamp === embed.timestamp && - this.fields.length === embed.fields.length && - this.fields.every((field, i) => this._fieldEquals(field, embed.fields[i])) && - this.footer?.text === embed.footer?.text && - this.footer?.iconURL === (embed.footer?.iconURL ?? embed.footer?.icon_url) && - this.image?.url === embed.image?.url && - this.thumbnail?.url === embed.thumbnail?.url && - this.video?.url === embed.video?.url && - this.provider?.name === embed.provider?.name && - this.provider?.url === embed.provider?.url - ); - } - - /** - * Compares two given embed fields to see if they are equal - * @param {EmbedFieldData} field The first field to compare - * @param {EmbedFieldData} other The second field to compare - * @returns {boolean} - * @private - */ - _fieldEquals(field, other) { - return field.name === other.name && field.value === other.value && field.inline === other.inline; - } - - /** - * Adds a field to the embed (max 25). - * @param {string} name The name of this field - * @param {string} value The value of this field - * @param {boolean} [inline=false] If this field will be displayed inline - * @returns {MessageEmbed} - */ - addField(name, value, inline) { - return this.addFields({ name, value, inline }); - } - - /** - * Adds fields to the embed (max 25). - * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to add - * @returns {MessageEmbed} - */ - addFields(...fields) { - this.fields.push(...this.constructor.normalizeFields(fields)); - return this; - } - - /** - * Removes, replaces, and inserts fields in the embed (max 25). - * @param {number} index The index to start at - * @param {number} deleteCount The number of fields to remove - * @param {...EmbedFieldData|EmbedFieldData[]} [fields] The replacing field objects - * @returns {MessageEmbed} - */ - spliceFields(index, deleteCount, ...fields) { - this.fields.splice(index, deleteCount, ...this.constructor.normalizeFields(...fields)); - return this; - } - - /** - * Sets the embed's fields (max 25). - * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to set - * @returns {MessageEmbed} - */ - setFields(...fields) { - this.spliceFields(0, this.fields.length, fields); - return this; - } - - /** - * The options to provide for setting an author for a {@link MessageEmbed}. - * @typedef {Object} EmbedAuthorData - * @property {string} name The name of this author. - * @property {string} [url] The URL of this author. - * @property {string} [iconURL] The icon URL of this author. - */ - - /** - * Sets the author of this embed. - * @param {string|EmbedAuthorData|null} options The options to provide for the author. - * Provide `null` to remove the author data. - * @param {string} [deprecatedIconURL] The icon URL of this author. - * This parameter is **deprecated**. Use the `options` parameter instead. - * @param {string} [deprecatedURL] The URL of this author. - * This parameter is **deprecated**. Use the `options` parameter instead. - * @returns {MessageEmbed} - */ - setAuthor(options, deprecatedIconURL, deprecatedURL) { - if (options === null) { - this.author = {}; - return this; - } - - if (typeof options === 'string') { - if (!deprecationEmittedForSetAuthor) { - process.emitWarning( - 'Passing strings for MessageEmbed#setAuthor is deprecated. Pass a sole object instead.', - 'DeprecationWarning', - ); - - deprecationEmittedForSetAuthor = true; - } - - options = { name: options, url: deprecatedURL, iconURL: deprecatedIconURL }; - } - - const { name, url, iconURL } = options; - this.author = { name: Util.verifyString(name, RangeError, 'EMBED_AUTHOR_NAME'), url, iconURL }; - return this; - } - - /** - * Sets the color of this embed. - * @param {ColorResolvable} color The color of the embed - * @returns {MessageEmbed} - */ - setColor(color) { - this.color = Util.resolveColor(color); - return this; - } - - /** - * Sets the description of this embed. - * @param {string} description The description - * @returns {MessageEmbed} - */ - setDescription(description) { - this.description = Util.verifyString(description, RangeError, 'EMBED_DESCRIPTION'); - return this; - } - - /** - * The options to provide for setting a footer for a {@link MessageEmbed}. - * @typedef {Object} EmbedFooterData - * @property {string} text The text of the footer. - * @property {string} [iconURL] The icon URL of the footer. - */ - - /** - * Sets the footer of this embed. - * @param {string|EmbedFooterData|null} options The options to provide for the footer. - * Provide `null` to remove the footer data. - * @param {string} [deprecatedIconURL] The icon URL of this footer. - * This parameter is **deprecated**. Use the `options` parameter instead. - * @returns {MessageEmbed} - */ - setFooter(options, deprecatedIconURL) { - if (options === null) { - this.footer = {}; - return this; - } - - if (typeof options === 'string') { - if (!deprecationEmittedForSetFooter) { - process.emitWarning( - 'Passing strings for MessageEmbed#setFooter is deprecated. Pass a sole object instead.', - 'DeprecationWarning', - ); - - deprecationEmittedForSetFooter = true; - } - - options = { text: options, iconURL: deprecatedIconURL }; - } - - const { text, iconURL } = options; - this.footer = { text: Util.verifyString(text, RangeError, 'EMBED_FOOTER_TEXT'), iconURL }; - return this; - } - - /** - * Sets the image of this embed. - * @param {string} url The URL of the image - * @returns {MessageEmbed} - */ - setImage(url) { - this.image = { url }; - return this; - } - - /** - * Sets the thumbnail of this embed. - * @param {string} url The URL of the thumbnail - * @returns {MessageEmbed} - */ - setThumbnail(url) { - this.thumbnail = { url }; - return this; - } - - /** - * Sets the timestamp of this embed. - * @param {Date|number|null} [timestamp=Date.now()] The timestamp or date. - * If `null` then the timestamp will be unset (i.e. when editing an existing {@link MessageEmbed}) - * @returns {MessageEmbed} - */ - setTimestamp(timestamp = Date.now()) { - if (timestamp instanceof Date) timestamp = timestamp.getTime(); - this.timestamp = timestamp; - return this; - } - - /** - * Sets the title of this embed. - * @param {string} title The title - * @returns {MessageEmbed} - */ - setTitle(title) { - this.title = Util.verifyString(title, RangeError, 'EMBED_TITLE'); - return this; - } - - /** - * Sets the URL of this embed. - * @param {string} url The URL - * @returns {MessageEmbed} - */ - setURL(url) { - this.url = url; - return this; - } - - /** - * Transforms the embed to a plain object. - * @returns {APIEmbed} The raw data of this embed - */ - toJSON() { - return { - title: this.title, - type: 'rich', - description: this.description, - url: this.url, - timestamp: this.createdAt?.toISOString(), - color: this.color, - fields: this.fields, - thumbnail: this.thumbnail, - image: this.image, - author: this.author && { - name: this.author.name, - url: this.author.url, - icon_url: this.author.iconURL, - }, - footer: this.footer && { - text: this.footer.text, - icon_url: this.footer.iconURL, - }, - }; - } - - /** - * Normalizes field input and verifies strings. - * @param {string} name The name of the field - * @param {string} value The value of the field - * @param {boolean} [inline=false] Set the field to display inline - * @returns {EmbedField} - */ - static normalizeField(name, value, inline = false) { - return { - name: Util.verifyString(name, RangeError, 'EMBED_FIELD_NAME', false), - value: Util.verifyString(value, RangeError, 'EMBED_FIELD_VALUE', false), - inline, - }; - } - - /** - * @typedef {Object} EmbedFieldData - * @property {string} name The name of this field - * @property {string} value The value of this field - * @property {boolean} [inline] If this field will be displayed inline - */ - - /** - * Normalizes field input and resolves strings. - * @param {...EmbedFieldData|EmbedFieldData[]} fields Fields to normalize - * @returns {EmbedField[]} - */ - static normalizeFields(...fields) { - return fields - .flat(2) - .map(field => - this.normalizeField(field.name, field.value, typeof field.inline === 'boolean' ? field.inline : false), - ); - } -} - -module.exports = MessageEmbed; - -/** - * @external APIEmbed - * @see {@link https://discord.com/developers/docs/resources/channel#embed-object} - */ diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index 2ee34f771486..49ad3d9bcb99 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -2,7 +2,7 @@ const { Buffer } = require('node:buffer'); const { createComponent } = require('@discordjs/builders'); -const MessageEmbed = require('./MessageEmbed'); +const { Embed } = require('@discordjs/builders'); const { RangeError } = require('../errors'); const DataResolver = require('../util/DataResolver'); const MessageFlags = require('../util/MessageFlags'); @@ -192,9 +192,7 @@ class MessagePayload { content, tts, nonce, - embeds: this.options.embeds?.map(embed => - (embed instanceof MessageEmbed ? embed : new MessageEmbed(embed)).toJSON(), - ), + embeds: this.options.embeds?.map(embed => (embed instanceof Embed ? embed : new Embed(embed)).toJSON()), components, username, avatar_url: avatarURL, diff --git a/packages/discord.js/src/structures/Webhook.js b/packages/discord.js/src/structures/Webhook.js index 116faf51e262..0632c1c72b6d 100644 --- a/packages/discord.js/src/structures/Webhook.js +++ b/packages/discord.js/src/structures/Webhook.js @@ -118,7 +118,7 @@ class Webhook { /** * Options that can be passed into editMessage. * @typedef {Object} WebhookEditMessageOptions - * @property {MessageEmbed[]|APIEmbed[]} [embeds] See {@link WebhookMessageOptions#embeds} + * @property {Embed[]|APIEmbed[]} [embeds] See {@link WebhookMessageOptions#embeds} * @property {string} [content] See {@link BaseMessageOptions#content} * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] See {@link BaseMessageOptions#files} * @property {MessageMentionOptions} [allowedMentions] See {@link BaseMessageOptions#allowedMentions} diff --git a/packages/discord.js/src/structures/interfaces/InteractionResponses.js b/packages/discord.js/src/structures/interfaces/InteractionResponses.js index d7e56357ada6..c571f20b5769 100644 --- a/packages/discord.js/src/structures/interfaces/InteractionResponses.js +++ b/packages/discord.js/src/structures/interfaces/InteractionResponses.js @@ -80,7 +80,7 @@ class InteractionResponses { * .catch(console.error); * @example * // Create an ephemeral reply with an embed - * const embed = new MessageEmbed().setDescription('Pong!'); + * const embed = new Embed().setDescription('Pong!'); * * interaction.reply({ embeds: [embed], ephemeral: true }) * .then(() => console.log('Reply sent.')) diff --git a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js index 21259c09e85d..bc31e9f45512 100644 --- a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js +++ b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -57,7 +57,7 @@ class TextBasedChannel { * @property {boolean} [tts=false] Whether or not the message should be spoken aloud * @property {string} [nonce=''] The nonce for the message * @property {string} [content=''] The content for the message - * @property {MessageEmbed[]|APIEmbed[]} [embeds] The embeds for the message + * @property {Embed[]|APIEmbed[]} [embeds] The embeds for the message * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details) * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details) diff --git a/packages/discord.js/test/sendtest.js b/packages/discord.js/test/sendtest.js index 089f3fd6ae77..c9ed3fa98b78 100644 --- a/packages/discord.js/test/sendtest.js +++ b/packages/discord.js/test/sendtest.js @@ -7,7 +7,7 @@ const process = require('node:process'); const { setTimeout: sleep } = require('node:timers/promises'); const util = require('node:util'); const { owner, token } = require('./auth.js'); -const { Client, Intents, MessageAttachment, MessageEmbed } = require('../src'); +const { Client, Intents, MessageAttachment, Embed } = require('../src'); const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] }); @@ -19,7 +19,7 @@ const linkA = 'https://lolisafe.moe/iiDMtAXA.png'; const linkB = 'https://lolisafe.moe/9hSpedPh.png'; const fileA = path.join(__dirname, 'blobReach.png'); -const embed = () => new MessageEmbed(); +const embed = () => new Embed(); const attach = (attachment, name) => new MessageAttachment(attachment, name); const tests = [ @@ -72,12 +72,6 @@ const tests = [ embed: embed().setImage('attachment://two.png'), files: [attach(linkB, 'two.png')], }), - m => - m.channel.send({ - embed: embed() - .setImage('attachment://two.png') - .attachFiles([attach(linkB, 'two.png')]), - }), m => m.channel.send('x', attach(fileA)), m => m.channel.send({ files: [fileA] }), m => m.channel.send(attach(fileA)), diff --git a/packages/discord.js/test/webhooktest.js b/packages/discord.js/test/webhooktest.js index fe5ba5e04733..d7a45d3ac6fc 100644 --- a/packages/discord.js/test/webhooktest.js +++ b/packages/discord.js/test/webhooktest.js @@ -6,7 +6,7 @@ const path = require('node:path'); const { setTimeout: sleep } = require('node:timers/promises'); const util = require('node:util'); const { owner, token, webhookChannel, webhookToken } = require('./auth.js'); -const { Client, Intents, MessageAttachment, MessageEmbed, WebhookClient } = require('../src'); +const { Client, Intents, MessageAttachment, Embed, WebhookClient } = require('../src'); const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] }); @@ -18,7 +18,7 @@ const linkA = 'https://lolisafe.moe/iiDMtAXA.png'; const linkB = 'https://lolisafe.moe/9hSpedPh.png'; const fileA = path.join(__dirname, 'blobReach.png'); -const embed = () => new MessageEmbed(); +const embed = () => new Embed(); const attach = (attachment, name) => new MessageAttachment(attachment, name); const tests = [ @@ -63,24 +63,6 @@ const tests = [ embeds: [embed().setImage('attachment://two.png')], files: [attach(linkB, 'two.png')], }), - (m, hook) => - hook.send({ - embeds: [ - embed() - .setImage('attachment://two.png') - .attachFiles([attach(linkB, 'two.png')]), - ], - }), - async (m, hook) => - hook.send(['x', 'y', 'z'], { - code: 'js', - embeds: [ - embed() - .setImage('attachment://two.png') - .attachFiles([attach(linkB, 'two.png')]), - ], - files: [{ attachment: await buffer(linkA) }], - }), (m, hook) => hook.send('x', attach(fileA)), (m, hook) => hook.send({ files: [fileA] }), diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index df27021f12fa..5501b68e5167 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -7,6 +7,7 @@ import { channelMention, codeBlock, Component, + Embed, formatEmoji, hideLinkEmbed, hyperlink, @@ -1489,7 +1490,7 @@ export class Message extends Base { public readonly editable: boolean; public readonly editedAt: Date | null; public editedTimestamp: number | null; - public embeds: MessageEmbed[]; + public embeds: Embed[]; public groupActivityApplication: ClientApplication | null; public guildId: If; public readonly guild: If; @@ -1619,51 +1620,6 @@ export class MessageContextMenuCommandInteraction< public inRawGuild(): this is MessageContextMenuCommandInteraction<'raw'>; } -export class MessageEmbed { - private _fieldEquals(field: EmbedField, other: EmbedField): boolean; - - public constructor(data?: MessageEmbed | MessageEmbedOptions | APIEmbed); - public author: MessageEmbedAuthor | null; - public color: number | null; - public readonly createdAt: Date | null; - public description: string | null; - public fields: EmbedField[]; - public footer: MessageEmbedFooter | null; - public readonly hexColor: HexColorString | null; - public image: MessageEmbedImage | null; - public readonly length: number; - public provider: MessageEmbedProvider | null; - public thumbnail: MessageEmbedThumbnail | null; - public timestamp: number | null; - public title: string | null; - /** @deprecated */ - public type: string; - public url: string | null; - public readonly video: MessageEmbedVideo | null; - public addField(name: string, value: string, inline?: boolean): this; - public addFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): this; - public setFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): this; - public setAuthor(options: EmbedAuthorData | null): this; - /** @deprecated Supply a lone object of interface {@link EmbedAuthorData} instead. */ - public setAuthor(name: string, iconURL?: string, url?: string): this; - public setColor(color: ColorResolvable): this; - public setDescription(description: string): this; - public setFooter(options: EmbedFooterData | null): this; - /** @deprecated Supply a lone object of interface {@link EmbedFooterData} instead. */ - public setFooter(text: string, iconURL?: string): this; - public setImage(url: string): this; - public setThumbnail(url: string): this; - public setTimestamp(timestamp?: Date | number | null): this; - public setTitle(title: string): this; - public setURL(url: string): this; - public spliceFields(index: number, deleteCount: number, ...fields: EmbedFieldData[] | EmbedFieldData[][]): this; - public equals(embed: MessageEmbed | APIEmbed): boolean; - public toJSON(): APIEmbed; - - public static normalizeField(name: string, value: string, inline?: boolean): Required; - public static normalizeFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): Required[]; -} - export class MessageFlags extends BitField { public static FLAGS: Record; public static resolve(bit?: BitFieldResolvable): number; @@ -4657,66 +4613,13 @@ export type MessageComponentOptions = export interface MessageEditOptions { attachments?: MessageAttachment[]; content?: string | null; - embeds?: (MessageEmbed | MessageEmbedOptions | APIEmbed)[] | null; + embeds?: (Embed | APIEmbed)[] | null; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; flags?: BitFieldResolvable; allowedMentions?: MessageMentionOptions; components?: (ActionRow | (Required & ActionRowOptions))[]; } -export interface MessageEmbedAuthor { - name: string; - url?: string; - iconURL?: string; - proxyIconURL?: string; -} - -export interface MessageEmbedFooter { - text: string; - iconURL?: string; - proxyIconURL?: string; -} - -export interface MessageEmbedImage { - url: string; - proxyURL?: string; - height?: number; - width?: number; -} - -export interface MessageEmbedOptions { - title?: string; - description?: string; - url?: string; - timestamp?: Date | number; - color?: ColorResolvable; - fields?: EmbedFieldData[]; - author?: Partial & { icon_url?: string; proxy_icon_url?: string }; - thumbnail?: Partial & { proxy_url?: string }; - image?: Partial & { proxy_url?: string }; - video?: Partial & { proxy_url?: string }; - footer?: Partial & { icon_url?: string; proxy_icon_url?: string }; -} - -export interface MessageEmbedProvider { - name: string; - url: string; -} - -export interface MessageEmbedThumbnail { - url: string; - proxyURL?: string; - height?: number; - width?: number; -} - -export interface MessageEmbedVideo { - url?: string; - proxyURL?: string; - height?: number; - width?: number; -} - export interface MessageEvent { data: WebSocket.Data; type: string; @@ -4755,11 +4658,13 @@ export interface MessageMentionOptions { export type MessageMentionTypes = 'roles' | 'users' | 'everyone'; +export { Embed }; + export interface MessageOptions { tts?: boolean; nonce?: string | number; content?: string | null; - embeds?: (MessageEmbed | MessageEmbedOptions | APIEmbed)[]; + embeds?: (Embed | APIEmbed)[]; components?: (ActionRow | (Required & ActionRowOptions))[]; allowedMentions?: MessageMentionOptions; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 2dbff3739b45..24fd16c8ea7e 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -54,7 +54,6 @@ import { MessageAttachment, MessageCollector, MessageComponentInteraction, - MessageEmbed, MessageReaction, NewsChannel, Options, @@ -99,6 +98,7 @@ import { ThreadChannelType, } from '.'; import { expectAssignable, expectDeprecated, expectNotAssignable, expectNotType, expectType } from 'tsd'; +import { Embed } from '@discordjs/builders'; // Test type transformation: declare const serialize: (value: T) => Serialized; @@ -561,7 +561,7 @@ client.on('messageCreate', async message => { assertIsMessage(channel.send({ embeds: [] })); const attachment = new MessageAttachment('file.png'); - const embed = new MessageEmbed(); + const embed = new Embed(); assertIsMessage(channel.send({ files: [attachment] })); assertIsMessage(channel.send({ embeds: [embed] })); assertIsMessage(channel.send({ embeds: [embed], files: [attachment] }));