MessageEmbed.js (12303B)
1 'use strict'; 2 3 const { RangeError } = require('../errors'); 4 const Util = require('../util/Util'); 5 6 /** 7 * Represents an embed in a message (image/video preview, rich embed, etc.) 8 */ 9 class MessageEmbed { 10 /** 11 * @name MessageEmbed 12 * @kind constructor 13 * @memberof MessageEmbed 14 * @param {MessageEmbed|Object} [data={}] MessageEmbed to clone or raw embed data 15 */ 16 17 constructor(data = {}, skipValidation = false) { 18 this.setup(data, skipValidation); 19 } 20 21 setup(data, skipValidation) { 22 /** 23 * The type of this embed, either: 24 * * `rich` - a rich embed 25 * * `image` - an image embed 26 * * `video` - a video embed 27 * * `gifv` - a gifv embed 28 * * `article` - an article embed 29 * * `link` - a link embed 30 * @type {string} 31 */ 32 this.type = data.type; 33 34 /** 35 * The title of this embed 36 * @type {?string} 37 */ 38 this.title = data.title; 39 40 /** 41 * The description of this embed 42 * @type {?string} 43 */ 44 this.description = data.description; 45 46 /** 47 * The URL of this embed 48 * @type {?string} 49 */ 50 this.url = data.url; 51 52 /** 53 * The color of this embed 54 * @type {?number} 55 */ 56 this.color = Util.resolveColor(data.color); 57 58 /** 59 * The timestamp of this embed 60 * @type {?number} 61 */ 62 this.timestamp = data.timestamp ? new Date(data.timestamp).getTime() : null; 63 64 /** 65 * @typedef {Object} EmbedField 66 * @property {string} name The name of this field 67 * @property {string} value The value of this field 68 * @property {boolean} inline If this field will be displayed inline 69 */ 70 71 /** 72 * The fields of this embed 73 * @type {EmbedField[]} 74 */ 75 this.fields = []; 76 if (data.fields) { 77 this.fields = skipValidation ? data.fields.map(Util.cloneObject) : this.constructor.normalizeFields(data.fields); 78 } 79 80 /** 81 * @typedef {Object} MessageEmbedThumbnail 82 * @property {string} url URL for this thumbnail 83 * @property {string} proxyURL ProxyURL for this thumbnail 84 * @property {number} height Height of this thumbnail 85 * @property {number} width Width of this thumbnail 86 */ 87 88 /** 89 * The thumbnail of this embed (if there is one) 90 * @type {?MessageEmbedThumbnail} 91 */ 92 this.thumbnail = data.thumbnail 93 ? { 94 url: data.thumbnail.url, 95 proxyURL: data.thumbnail.proxyURL || data.thumbnail.proxy_url, 96 height: data.thumbnail.height, 97 width: data.thumbnail.width, 98 } 99 : null; 100 101 /** 102 * @typedef {Object} MessageEmbedImage 103 * @property {string} url URL for this image 104 * @property {string} proxyURL ProxyURL for this image 105 * @property {number} height Height of this image 106 * @property {number} width Width of this image 107 */ 108 109 /** 110 * The image of this embed, if there is one 111 * @type {?MessageEmbedImage} 112 */ 113 this.image = data.image 114 ? { 115 url: data.image.url, 116 proxyURL: data.image.proxyURL || data.image.proxy_url, 117 height: data.image.height, 118 width: data.image.width, 119 } 120 : null; 121 122 /** 123 * @typedef {Object} MessageEmbedVideo 124 * @property {string} url URL of this video 125 * @property {string} proxyURL ProxyURL for this video 126 * @property {number} height Height of this video 127 * @property {number} width Width of this video 128 */ 129 130 /** 131 * The video of this embed (if there is one) 132 * @type {?MessageEmbedVideo} 133 * @readonly 134 */ 135 this.video = data.video 136 ? { 137 url: data.video.url, 138 proxyURL: data.video.proxyURL || data.video.proxy_url, 139 height: data.video.height, 140 width: data.video.width, 141 } 142 : null; 143 144 /** 145 * @typedef {Object} MessageEmbedAuthor 146 * @property {string} name The name of this author 147 * @property {string} url URL of this author 148 * @property {string} iconURL URL of the icon for this author 149 * @property {string} proxyIconURL Proxied URL of the icon for this author 150 */ 151 152 /** 153 * The author of this embed (if there is one) 154 * @type {?MessageEmbedAuthor} 155 */ 156 this.author = data.author 157 ? { 158 name: data.author.name, 159 url: data.author.url, 160 iconURL: data.author.iconURL || data.author.icon_url, 161 proxyIconURL: data.author.proxyIconURL || data.author.proxy_icon_url, 162 } 163 : null; 164 165 /** 166 * @typedef {Object} MessageEmbedProvider 167 * @property {string} name The name of this provider 168 * @property {string} url URL of this provider 169 */ 170 171 /** 172 * The provider of this embed (if there is one) 173 * @type {?MessageEmbedProvider} 174 */ 175 this.provider = data.provider 176 ? { 177 name: data.provider.name, 178 url: data.provider.name, 179 } 180 : null; 181 182 /** 183 * @typedef {Object} MessageEmbedFooter 184 * @property {string} text The text of this footer 185 * @property {string} iconURL URL of the icon for this footer 186 * @property {string} proxyIconURL Proxied URL of the icon for this footer 187 */ 188 189 /** 190 * The footer of this embed 191 * @type {?MessageEmbedFooter} 192 */ 193 this.footer = data.footer 194 ? { 195 text: data.footer.text, 196 iconURL: data.footer.iconURL || data.footer.icon_url, 197 proxyIconURL: data.footer.proxyIconURL || data.footer.proxy_icon_url, 198 } 199 : null; 200 201 /** 202 * The files of this embed 203 * @type {Array<FileOptions|string|MessageAttachment>} 204 */ 205 this.files = data.files || []; 206 } 207 208 /** 209 * The date displayed on this embed 210 * @type {?Date} 211 * @readonly 212 */ 213 get createdAt() { 214 return this.timestamp ? new Date(this.timestamp) : null; 215 } 216 217 /** 218 * The hexadecimal version of the embed color, with a leading hash 219 * @type {?string} 220 * @readonly 221 */ 222 get hexColor() { 223 return this.color ? `#${this.color.toString(16).padStart(6, '0')}` : null; 224 } 225 226 /** 227 * The accumulated length for the embed title, description, fields and footer text 228 * @type {number} 229 * @readonly 230 */ 231 get length() { 232 return ( 233 (this.title ? this.title.length : 0) + 234 (this.description ? this.description.length : 0) + 235 (this.fields.length >= 1 236 ? this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0) 237 : 0) + 238 (this.footer ? this.footer.text.length : 0) 239 ); 240 } 241 242 /** 243 * Adds a field to the embed (max 25). 244 * @param {StringResolvable} name The name of this field 245 * @param {StringResolvable} value The value of this field 246 * @param {boolean} [inline=false] If this field will be displayed inline 247 * @returns {MessageEmbed} 248 */ 249 addField(name, value, inline) { 250 return this.addFields({ name, value, inline }); 251 } 252 253 /** 254 * Adds fields to the embed (max 25). 255 * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to add 256 * @returns {MessageEmbed} 257 */ 258 addFields(...fields) { 259 this.fields.push(...this.constructor.normalizeFields(fields)); 260 return this; 261 } 262 263 /** 264 * Removes, replaces, and inserts fields in the embed (max 25). 265 * @param {number} index The index to start at 266 * @param {number} deleteCount The number of fields to remove 267 * @param {...EmbedFieldData|EmbedFieldData[]} [fields] The replacing field objects 268 * @returns {MessageEmbed} 269 */ 270 spliceFields(index, deleteCount, ...fields) { 271 this.fields.splice(index, deleteCount, ...this.constructor.normalizeFields(...fields)); 272 return this; 273 } 274 275 /** 276 * Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when 277 * setting an embed image or author/footer icons. Multiple files can be attached. 278 * @param {Array<FileOptions|string|MessageAttachment>} files Files to attach 279 * @returns {MessageEmbed} 280 */ 281 attachFiles(files) { 282 this.files = this.files.concat(files); 283 return this; 284 } 285 286 /** 287 * Sets the author of this embed. 288 * @param {StringResolvable} name The name of the author 289 * @param {string} [iconURL] The icon URL of the author 290 * @param {string} [url] The URL of the author 291 * @returns {MessageEmbed} 292 */ 293 setAuthor(name, iconURL, url) { 294 this.author = { name: Util.resolveString(name), iconURL, url }; 295 return this; 296 } 297 298 /** 299 * Sets the color of this embed. 300 * @param {ColorResolvable} color The color of the embed 301 * @returns {MessageEmbed} 302 */ 303 setColor(color) { 304 this.color = Util.resolveColor(color); 305 return this; 306 } 307 308 /** 309 * Sets the description of this embed. 310 * @param {StringResolvable} description The description 311 * @returns {MessageEmbed} 312 */ 313 setDescription(description) { 314 description = Util.resolveString(description); 315 this.description = description; 316 return this; 317 } 318 319 /** 320 * Sets the footer of this embed. 321 * @param {StringResolvable} text The text of the footer 322 * @param {string} [iconURL] The icon URL of the footer 323 * @returns {MessageEmbed} 324 */ 325 setFooter(text, iconURL) { 326 text = Util.resolveString(text); 327 this.footer = { text, iconURL }; 328 return this; 329 } 330 331 /** 332 * Sets the image of this embed. 333 * @param {string} url The URL of the image 334 * @returns {MessageEmbed} 335 */ 336 setImage(url) { 337 this.image = { url }; 338 return this; 339 } 340 341 /** 342 * Sets the thumbnail of this embed. 343 * @param {string} url The URL of the thumbnail 344 * @returns {MessageEmbed} 345 */ 346 setThumbnail(url) { 347 this.thumbnail = { url }; 348 return this; 349 } 350 351 /** 352 * Sets the timestamp of this embed. 353 * @param {Date|number} [timestamp=Date.now()] The timestamp or date 354 * @returns {MessageEmbed} 355 */ 356 setTimestamp(timestamp = Date.now()) { 357 if (timestamp instanceof Date) timestamp = timestamp.getTime(); 358 this.timestamp = timestamp; 359 return this; 360 } 361 362 /** 363 * Sets the title of this embed. 364 * @param {StringResolvable} title The title 365 * @returns {MessageEmbed} 366 */ 367 setTitle(title) { 368 title = Util.resolveString(title); 369 this.title = title; 370 return this; 371 } 372 373 /** 374 * Sets the URL of this embed. 375 * @param {string} url The URL 376 * @returns {MessageEmbed} 377 */ 378 setURL(url) { 379 this.url = url; 380 return this; 381 } 382 383 /** 384 * Transforms the embed to a plain object. 385 * @returns {Object} The raw data of this embed 386 */ 387 toJSON() { 388 return { 389 title: this.title, 390 type: 'rich', 391 description: this.description, 392 url: this.url, 393 timestamp: this.timestamp ? new Date(this.timestamp) : null, 394 color: this.color, 395 fields: this.fields, 396 thumbnail: this.thumbnail, 397 image: this.image, 398 author: this.author 399 ? { 400 name: this.author.name, 401 url: this.author.url, 402 icon_url: this.author.iconURL, 403 } 404 : null, 405 footer: this.footer 406 ? { 407 text: this.footer.text, 408 icon_url: this.footer.iconURL, 409 } 410 : null, 411 }; 412 } 413 414 /** 415 * Normalizes field input and resolves strings. 416 * @param {StringResolvable} name The name of the field 417 * @param {StringResolvable} value The value of the field 418 * @param {boolean} [inline=false] Set the field to display inline 419 * @returns {EmbedField} 420 */ 421 static normalizeField(name, value, inline = false) { 422 name = Util.resolveString(name); 423 if (!name) throw new RangeError('EMBED_FIELD_NAME'); 424 value = Util.resolveString(value); 425 if (!value) throw new RangeError('EMBED_FIELD_VALUE'); 426 return { name, value, inline }; 427 } 428 429 /** 430 * @typedef {Object} EmbedFieldData 431 * @property {StringResolvable} name The name of this field 432 * @property {StringResolvable} value The value of this field 433 * @property {boolean} [inline] If this field will be displayed inline 434 */ 435 436 /** 437 * Normalizes field input and resolves strings. 438 * @param {...EmbedFieldData|EmbedFieldData[]} fields Fields to normalize 439 * @returns {EmbedField[]} 440 */ 441 static normalizeFields(...fields) { 442 return fields 443 .flat(2) 444 .map(field => 445 this.normalizeField( 446 field && field.name, 447 field && field.value, 448 field && typeof field.inline === 'boolean' ? field.inline : false, 449 ), 450 ); 451 } 452 } 453 454 module.exports = MessageEmbed;