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 <spaceeec@yahoo.com>
This commit is contained in:
Souji
2020-02-28 18:16:19 +01:00
committed by GitHub
parent cf646b5394
commit 6eaf63fb7c
4 changed files with 89 additions and 17 deletions

View File

@@ -26,6 +26,7 @@ module.exports = {
// Shortcuts to Util methods
escapeMarkdown: Util.escapeMarkdown,
fetchRecommendedShards: Util.fetchRecommendedShards,
resolveString: Util.resolveString,
splitMessage: Util.splitMessage,
// Structures

View File

@@ -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);
}

View File

@@ -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

15
typings/index.d.ts vendored
View File

@@ -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<Buffer>;
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<Attachment | FileOptions | string>): 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<T> extends Array<T | RecursiveArray<T>> { }
interface EmbedFieldData {
name: StringResolvable;
value: StringResolvable;
inline?: boolean;
}
type PermissionResolvable = BitFieldResolvable<PermissionString>
type PremiumTier = number;