From 6eaf63fb7c8fecb5cbee9713f65d35ee7de900a1 Mon Sep 17 00:00:00 2001 From: Souji Date: Fri, 28 Feb 2020 18:16:19 +0100 Subject: [PATCH] feat(RichEmbed): backport spliceFields and normalizeField (#3762) * backport: RichEmbed#checkField, Util#resolveString * backport: RichEmbed#spliceFields * fix: typo * chore: use util.resolveString everywhere * chore: rename EmbedFIeld to EmbedFieldData * consistency with v12 * chore: rename checkField to normalizeField * consistency with v12 * fix: EmbedField instead of EmbedFieldData * fix(typings): EmbedFIeld#inline is guaranteed * fix(docs): add JSDocs typedef for EmbedFieldData * fix(typings): EmbedFIeldData#name/#value * should be StringResolvable * refactor(RichEmbed): do not duplicate field prop checking * docs(RichEmbed): document default for inline * fix(RichEmbed): pass correct parameters to normalizeField * typings(RichEmbed): add missing spaces Co-authored-by: SpaceEEC --- src/index.js | 1 + src/structures/RichEmbed.js | 71 ++++++++++++++++++++++++++++--------- src/util/Util.js | 19 ++++++++++ typings/index.d.ts | 15 ++++++++ 4 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/index.js b/src/index.js index b5b19c75f..f1fe01f12 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,7 @@ module.exports = { // Shortcuts to Util methods escapeMarkdown: Util.escapeMarkdown, fetchRecommendedShards: Util.fetchRecommendedShards, + resolveString: Util.resolveString, splitMessage: Util.splitMessage, // Structures diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index d8e16ccea..b4083501d 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -1,5 +1,6 @@ const Attachment = require('./Attachment'); const MessageEmbed = require('./MessageEmbed'); +const util = require('../util/Util'); let ClientDataResolver; /** @@ -87,7 +88,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setTitle(title) { - title = resolveString(title); + title = util.resolveString(title); if (title.length > 256) throw new RangeError('RichEmbed titles may not exceed 256 characters.'); this.title = title; return this; @@ -99,7 +100,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setDescription(description) { - description = resolveString(description); + description = util.resolveString(description); if (description.length > 2048) throw new RangeError('RichEmbed descriptions may not exceed 2048 characters.'); this.description = description; return this; @@ -134,7 +135,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setAuthor(name, icon, url) { - this.author = { name: resolveString(name), icon_url: icon, url }; + this.author = { name: util.resolveString(name), icon_url: icon, url }; return this; } @@ -158,13 +159,7 @@ class RichEmbed { */ addField(name, value, inline = false) { if (this.fields.length >= 25) throw new RangeError('RichEmbeds may not exceed 25 fields.'); - name = resolveString(name); - if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.'); - if (!/\S/.test(name)) throw new RangeError('RichEmbed field names may not be empty.'); - value = resolveString(value); - if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.'); - if (!/\S/.test(value)) throw new RangeError('RichEmbed field values may not be empty.'); - this.fields.push({ name, value, inline }); + this.fields.push(this.constructor.normalizeField(name, value, inline)); return this; } @@ -177,6 +172,37 @@ class RichEmbed { return this.addField('\u200B', '\u200B', inline); } + /** + * @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 + */ + + /** + * @typedef {Object} EmbedFieldData + * @property {StringResolvable} name The name of this field + * @property {StringResolvable} value The value of this field + * @property {boolean} [inline=false] If this field will be displayed inline + */ + + /** + * 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} [fields] The replacing field objects + * @returns {RichEmbed} + */ + spliceFields(index, deleteCount, ...fields) { + if (fields) { + const mapper = ({ name, value, inline }) => this.constructor.normalizeField(name, value, inline); + this.fields.splice(index, deleteCount, ...fields.map(mapper)); + } else { + this.fields.splice(index, deleteCount); + } + return this; + } + /** * Set the thumbnail of this embed. * @param {string} url The URL of the thumbnail @@ -204,7 +230,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setFooter(text, icon) { - text = resolveString(text); + text = util.resolveString(text); if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 2048 characters.'); this.footer = { text, icon_url: icon }; return this; @@ -284,12 +310,23 @@ class RichEmbed { } : null, }; } + + /** + * Normalizes field input and resolves strings. + * @param {StringResolvable} name The name of the field + * @param {StringResolvable} value The value of the field + * @param {boolean} [inline=false] Set the field to display inline + * @returns {EmbedField} + */ + static normalizeField(name, value, inline = false) { + name = util.resolveString(name); + if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.'); + if (!/\S/.test(name)) throw new RangeError('RichEmbed field names may not be empty.'); + value = util.resolveString(value); + if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.'); + if (!/\S/.test(value)) throw new RangeError('RichEmbed field values may not be empty.'); + return { name, value, inline }; + } } module.exports = RichEmbed; - -function resolveString(data) { - if (typeof data === 'string') return data; - if (data instanceof Array) return data.join('\n'); - return String(data); -} diff --git a/src/util/Util.js b/src/util/Util.js index 481d0ffa4..a286b076d 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -35,6 +35,25 @@ class Util { return messages; } + /** + * Data that can be resolved to give a string. This can be: + * * A string + * * An array (joined with a new line delimiter to give a string) + * * Any value + * @typedef {string|Array|*} StringResolvable + */ + + /** + * Resolves a StringResolvable to a string. + * @param {StringResolvable} data The string resolvable to resolve + * @returns {string} + */ + static resolveString(data) { + if (typeof data === 'string') return data; + if (Array.isArray(data)) return data.join('\n'); + return String(data); + } + /** * Escapes any Discord-flavour markdown in a string. * @param {string} text Content to escape diff --git a/typings/index.d.ts b/typings/index.d.ts index 977362b64..104a99037 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -234,6 +234,7 @@ declare module 'discord.js' { public resolveChannel(channel: ChannelResolvable): Channel; public resolveChannelID(channel: ChannelResolvable): Snowflake; public resolveColor(color: ColorResolvable): number; + public resolveString(data: StringResolvable): string; public resolveEmojiIdentifier(emoji: EmojiIdentifierResolvable): string; public resolveFile(resource: BufferResolvable | Stream): Promise; public resolveGuild(guild: GuildResolvable): Guild; @@ -1111,6 +1112,12 @@ declare module 'discord.js' { public push(request: object): void; } + interface EmbedField { + name: string; + value: string; + inline: boolean; + } + export class RichEmbed { constructor(data?: RichEmbedOptions | MessageEmbed); private _apiTransform(): object; @@ -1129,6 +1136,7 @@ declare module 'discord.js' { public title?: string; public url?: string; public addBlankField(inline?: boolean): this; + public spliceFields(index: number, deleteCount: number, ...fields: EmbedFieldData[]): this; public addField(name: StringResolvable, value: StringResolvable, inline?: boolean): this; public attachFile(file: Attachment | FileOptions | string): this; public attachFiles(file: Array): this; @@ -1141,6 +1149,7 @@ declare module 'discord.js' { public setTimestamp(timestamp?: Date | number): this; public setTitle(title: StringResolvable): this; public setURL(url: string): this; + public static normalizeField(name: StringResolvable, value: StringResolvable, inline?: boolean): EmbedField; } export class RichPresenceAssets { @@ -2261,6 +2270,12 @@ declare module 'discord.js' { interface RecursiveArray extends Array> { } + interface EmbedFieldData { + name: StringResolvable; + value: StringResolvable; + inline?: boolean; + } + type PermissionResolvable = BitFieldResolvable type PremiumTier = number;