structures/User.js

"use strict";

const Base = require("./Base");
const Endpoints = require("../rest/Endpoints");

/**
 * Represents a user
 * @prop {Number?} accentColor The user's banner color, or null if no banner color (REST only)
 * @prop {String?} avatar The hash of the user's avatar, or null if no avatar
 * @prop {Object?} avatarDecorationData The data of the user's avatar decoration, including the asset and sku ID, or null if no avatar decoration
 * @prop {String?} avatarDecorationURL The URL of the user's avatar decoration
 * @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF
 * @prop {String?} banner The hash of the user's banner, or null if no banner (REST only)
 * @prop {String?} bannerURL The URL of the user's banner
 * @prop {Boolean} bot Whether the user is an OAuth bot or not
 * @prop {Number} createdAt Timestamp of the user's creation
 * @prop {String} defaultAvatar The hash for the default avatar of a user if there is no avatar set
 * @prop {String} defaultAvatarURL The URL of the user's default avatar
 * @prop {String} discriminator The discriminator of the user. If they've migrated to the new username system, this will be "0"
 * @prop {String?} globalName The user's display name, if it is set. For bots, this is the application name
 * @prop {String} id The ID of the user
 * @prop {String} mention A string that mentions the user
 * @prop {Number?} publicFlags Publicly visible flags for this user
 * @prop {String} staticAvatarURL The URL of the user's avatar (always a JPG)
 * @prop {Boolean} system Whether the user is an official Discord system user (e.g. urgent messages)
 * @prop {String} username The username of the user
 */
class User extends Base {
  constructor(data, client) {
    super(data.id);
    if (!client) {
      this._missingClientError = new Error("Missing client in constructor"); // Preserve constructor callstack
    }
    this._client = client;
    this.bot = !!data.bot;
    this.system = !!data.system;
    this.update(data);
  }

  update(data) {
    if (data.avatar !== undefined) {
      this.avatar = data.avatar;
    }
    if (data.username !== undefined) {
      this.username = data.username;
    }
    if (data.global_name !== undefined) {
      this.globalName = data.global_name;
    }
    if (data.discriminator !== undefined) {
      this.discriminator = data.discriminator;
    }
    if (data.public_flags !== undefined) {
      this.publicFlags = data.public_flags;
    }
    if (data.banner !== undefined) {
      this.banner = data.banner;
    }
    if (data.accent_color !== undefined) {
      this.accentColor = data.accent_color;
    }
    if (data.avatar_decoration_data !== undefined) {
      this.avatarDecorationData = data.avatar_decoration_data;
    }
  }

  get avatarDecorationURL() {
    if (!this.avatarDecorationData) {
      return null;
    }
    if (this._missingClientError) {
      throw this._missingClientError;
    }
    return this._client._formatImage(Endpoints.USER_AVATAR_DECORATION(this.avatarDecorationData.asset));
  }

  get avatarURL() {
    if (this._missingClientError) {
      throw this._missingClientError;
    }
    return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar)) : this.defaultAvatarURL;
  }

  get bannerURL() {
    if (!this.banner) {
      return null;
    }
    if (this._missingClientError) {
      throw this._missingClientError;
    }
    return this._client._formatImage(Endpoints.BANNER(this.id, this.banner));
  }

  get defaultAvatar() {
    return this.discriminator === "0" ? Base.getDiscordEpoch(this.id) % 6 : this.discriminator % 5;
  }

  get defaultAvatarURL() {
    return `${Endpoints.CDN_URL}${Endpoints.DEFAULT_USER_AVATAR(this.defaultAvatar)}.png`;
  }

  get mention() {
    return `<@${this.id}>`;
  }

  get staticAvatarURL() {
    if (this._missingClientError) {
      throw this._missingClientError;
    }
    return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), "jpg") : this.defaultAvatarURL;
  }

  /**
   * Get the user's avatar with the given format and size
   * @arg {String} [format] The filetype of the avatar ("jpg", "jpeg", "png", "gif", or "webp")
   * @arg {Number} [size] The size of the avatar (any power of two between 16 and 4096)
   * @returns {String}
   */
  dynamicAvatarURL(format, size) {
    if (!this.avatar) {
      return this.defaultAvatarURL;
    }
    if (this._missingClientError) {
      throw this._missingClientError;
    }
    return this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), format, size);
  }

  /**
   * Get the user's banner with the given format and size
   * @arg {String} [format] The filetype of the banner ("jpg", "jpeg", "png", "gif", or "webp")
   * @arg {Number} [size] The size of the banner (any power of two between 16 and 4096)
   * @returns {String?}
   */
  dynamicBannerURL(format, size) {
    if (!this.banner) {
      return null;
    }
    if (this._missingClientError) {
      throw this._missingClientError;
    }
    return this._client._formatImage(Endpoints.BANNER(this.id, this.banner), format, size);
  }

  /**
   * Get a DM channel with the user, or create one if it does not exist
   * @returns {Promise<DMChannel>}
   */
  getDMChannel() {
    return this._client.getDMChannel.call(this._client, this.id);
  }

  toJSON(props = []) {
    return super.toJSON([
      "accentColor",
      "avatar",
      "avatarDecorationData",
      "banner",
      "bot",
      "discriminator",
      "globalName",
      "publicFlags",
      "system",
      "username",
      ...props,
    ]);
  }
}

module.exports = User;