structures/ScheduledEvent.js

import User from "./User.js";
import { CDN_BASE_URL, TO_JSON_TYPES_ENUM } from "../constants.js";
import GluonCacheOptions from "../managers/GluonCacheOptions.js";
import GuildCacheOptions from "../managers/GuildCacheOptions.js";
import util from "util";
import Client from "../Client.js";

/**
 * Represents an scheduled event.
 * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-structure}
 */
class ScheduledEvent {
  #_client;
  #_id;
  #_guild_id;
  #name;
  #_creator_id;
  #creator;
  #scheduled_start_time;
  #scheduled_end_time;
  #_image;
  #user_count;
  #_attributes;
  #location;
  #description;
  /**
   * Creates the structure for a scheduled event.
   * @param {Client} client The client instance.
   * @param {Object} data Scheduled event data from Discord.
   * @param {Object} options Additional options for this structure.
   * @param {String} options.guildId The ID of the guild that this event belongs to.
   * @param {Boolean?} options.nocache Whether this event should be cached or not.
   */
  constructor(client, data, { guildId, nocache = false } = { nocache: false }) {
    if (!(client instanceof Client))
      throw new TypeError("GLUON: Client must be an instance of Client");
    if (typeof data !== "object")
      throw new TypeError("GLUON: Data must be an object");
    if (typeof guildId !== "string")
      throw new TypeError("GLUON: Guild ID must be a string");
    if (typeof nocache !== "boolean")
      throw new TypeError("GLUON: No cache must be a boolean");

    /**
     * The client instance.
     * @type {Client}
     * @private
     */
    this.#_client = client;

    /**
     * The id of the scheduled event.
     * @type {BigInt}
     * @private
     */
    this.#_id = BigInt(data.id);

    /**
     * The id of the guild that this event belongs to.
     * @type {BigInt}
     * @private
     */
    this.#_guild_id = BigInt(guildId);

    /**
     * The name of the scheduled event.
     * @type {String}
     * @private
     */
    this.#name = data.name;

    if (data.creator_id)
      /**
       * The id of the user who created the event.
       * @type {BigInt?}
       * @private
       */
      this.#_creator_id = BigInt(data.creator_id);

    if (data.creator)
      /**
       * The user who created the event.
       * @type {User?}
       * @private
       */
      this.#creator = new User(this.#_client, data.creator);

    /**
     * The UNIX timestamp of the start time for the event.
     * @type {Number}
     * @private
     */
    this.#scheduled_start_time =
      (new Date(data.scheduled_start_time).getTime() / 1000) | 0;

    if (data.scheduled_end_time)
      /**
       * The UNIX timestamp of the end time for the event.
       * @type {Number?}
       * @private
       */
      this.#scheduled_end_time =
        (new Date(data.scheduled_end_time).getTime() / 1000) | 0;

    /**
     * The hash of the event's image.
     * @type {BigInt?}
     * @private
     */
    this.#_image = data.image ? BigInt(`0x${data.image}`) : null;

    if (data.description) this.#description = data.description;

    /**
     * The number of users who have signed up for the event.
     * @type {Number}
     * @private
     */
    this.#user_count = data.user_count ?? 0;

    /**
     * The attributes of the event.
     * @type {Number}
     * @private
     */
    this.#_attributes = 0;

    switch (data.entity_type) {
      case 1: {
        // STAGE_INSTANCE
        this.#_attributes |= 0b1 << 0;
        break;
      }
      case 2: {
        // VOICE
        this.#_attributes |= 0b1 << 1;
        break;
      }
      case 3: {
        // EXTERNAL
        this.#_attributes |= 0b1 << 2;
        break;
      }
      default:
        break;
    }

    switch (data.status) {
      case 1: {
        // SCHEDULED
        this.#_attributes |= 0b1 << 3;
        break;
      }
      case 2: {
        // ACTIVE
        this.#_attributes |= 0b1 << 4;
        break;
      }
      case 3: {
        // COMPLETED
        this.#_attributes |= 0b1 << 5;
        break;
      }
      case 4: {
        // CANCELED
        this.#_attributes |= 0b1 << 6;
        break;
      }
      default:
        break;
    }

    if (this.entityType == "EXTERNAL")
      /**
       * The location of the event.
       * @type {String?}
       * @private
       */
      this.#location = data.location ?? data.entity_metadata.location;

    if (
      nocache === false &&
      ScheduledEvent.shouldCache(
        this.#_client._cacheOptions,
        this.guild._cacheOptions,
      )
    )
      this.guild.scheduledEvents.set(data.id, this);
  }

  /**
   * The ID of the event.
   * @type {String}
   * @readonly
   * @public
   */
  get id() {
    return String(this.#_id);
  }

  /**
   * The guild ID of the event.
   * @type {String}
   * @readonly
   * @public
   */
  get guildId() {
    return String(this.#_guild_id);
  }

  /**
   * The name of the event.
   * @type {String}
   * @readonly
   * @public
   */
  get name() {
    return this.#name;
  }

  /**
   * The ID of the user who created the event.
   * @type {String?}
   * @readonly
   * @public
   */
  get creatorId() {
    return this.#_creator_id ? String(this.#_creator_id) : null;
  }

  /**
   * The user who created the event.
   * @type {User?}
   * @readonly
   * @public
   */
  get creator() {
    return this.#creator;
  }

  /**
   * The description of the event.
   * @type {String}
   * @readonly
   * @public
   */
  get description() {
    return this.#description;
  }

  /**
   * The hash of the event's image, as it was received from Discord.
   * @readonly
   * @type {String?}
   * @private
   */
  get #_originalImageHash() {
    return this.#_image
      ? // eslint-disable-next-line quotes
        `${this.#_formattedImageHash}`
      : null;
  }

  /**
   * The hash of the event's image as a string.
   * @readonly
   * @type {String}
   * @private
   */
  get #_formattedImageHash() {
    let formattedHash = this.#_image.toString(16);

    while (formattedHash.length != 32)
      // eslint-disable-next-line quotes
      formattedHash = `0${formattedHash}`;

    return formattedHash;
  }

  /**
   * The url of the events's image.
   * @readonly
   * @type {String?}
   * @public
   */
  get displayImageURL() {
    return ScheduledEvent.getImageUrl(this.id, this.#_originalImageHash);
  }

  /**
   * Where the event is scheduled to take place.
   * @readonly
   * @type {String}
   * @public
   */
  get entityType() {
    if ((this.#_attributes & (0b1 << 0)) == 0b1 << 0) return "STAGE_INSTANCE";
    else if ((this.#_attributes & (0b1 << 1)) == 0b1 << 1) return "VOICE";
    else if ((this.#_attributes & (0b1 << 2)) == 0b1 << 2) return "EXTERNAL";
    else return "UNKNOWN";
  }

  get #rawEntityType() {
    if ((this.#_attributes & (0b1 << 0)) == 0b1 << 0) return 1;
    else if ((this.#_attributes & (0b1 << 1)) == 0b1 << 1) return 2;
    else if ((this.#_attributes & (0b1 << 2)) == 0b1 << 2) return 3;
    else return 0;
  }

  /**
   * The status of the event.
   * @readonly
   * @type {String}
   * @public
   */
  get status() {
    if ((this.#_attributes & (0b1 << 3)) == 0b1 << 3) return "SCHEDULED";
    else if ((this.#_attributes & (0b1 << 4)) == 0b1 << 4) return "ACTIVE";
    else if ((this.#_attributes & (0b1 << 5)) == 0b1 << 5) return "COMPLETED";
    else if ((this.#_attributes & (0b1 << 6)) == 0b1 << 6) return "CANCELED";
    else return "UNKNOWN";
  }

  get #rawStatus() {
    if ((this.#_attributes & (0b1 << 3)) == 0b1 << 3) return 1;
    else if ((this.#_attributes & (0b1 << 4)) == 0b1 << 4) return 2;
    else if ((this.#_attributes & (0b1 << 5)) == 0b1 << 5) return 3;
    else if ((this.#_attributes & (0b1 << 6)) == 0b1 << 6) return 4;
    else return 0;
  }

  /**
   * The guild that this event belongs to.
   * @type {Guild?}
   * @readonly
   * @public
   */
  get guild() {
    return this.#_client.guilds.get(this.guildId) || null;
  }

  /**
   * The UNIX timestamp of the start time for the event.
   * @type {Number}
   * @readonly
   * @public
   */
  get scheduledStartTime() {
    return this.#scheduled_start_time;
  }

  /**
   * The UNIX timestamp of the end time for the event.
   * @type {Number?}
   * @readonly
   * @public
   */
  get scheduledEndTime() {
    return this.#scheduled_end_time;
  }

  /**
   * The number of users who have signed up for the event.
   * @type {Number}
   * @readonly
   * @public
   */
  get userCount() {
    return this.#user_count;
  }

  /**
   * The location of the event.
   * @type {String?}
   * @readonly
   * @public
   */
  get location() {
    return this.#location;
  }

  /**
   * Returns the URL of the event's image.
   * @param {String} id The id of the event.
   * @param {String?} hash The hash of the event's image.
   * @returns {String}
   * @public
   * @static
   * @method
   */
  static getImageUrl(id, hash) {
    if (typeof id !== "string")
      throw new TypeError("GLUON: Event id must be a string.");
    if (hash && typeof hash !== "string")
      throw new TypeError("GLUON: Event hash must be a string.");
    return hash
      ? `${CDN_BASE_URL}/guild-events/${id}/${hash}.${
          hash.startsWith("a_") ? "gif" : "png"
        }`
      : null;
  }

  /**
   * Determines whether the scheduled event should be cached.
   * @param {GluonCacheOptions} gluonCacheOptions The cache options for the client.
   * @param {GuildCacheOptions} guildCacheOptions The cache options for the guild.
   * @returns {Boolean}
   * @public
   * @static
   * @method
   */
  static shouldCache(gluonCacheOptions, guildCacheOptions) {
    if (!(gluonCacheOptions instanceof GluonCacheOptions))
      throw new TypeError(
        "GLUON: Gluon cache options must be a GluonCacheOptions.",
      );
    if (!(guildCacheOptions instanceof GuildCacheOptions))
      throw new TypeError(
        "GLUON: Guild cache options must be a GuildCacheOptions.",
      );
    if (gluonCacheOptions.cacheScheduledEvents === false) return false;
    if (guildCacheOptions.scheduledEventCaching === false) return false;
    return true;
  }

  /**
   * @method
   * @public
   */
  toString() {
    return `<ScheduledEvent: ${this.id}>`;
  }

  /**
   * @method
   * @public
   */
  [util.inspect.custom]() {
    return this.toString();
  }

  /**
   * Returns the JSON representation of this structure.
   * @param {Number} format The format to return the data in.
   * @returns {Object}
   * @public
   * @method
   */
  toJSON(format) {
    switch (format) {
      case TO_JSON_TYPES_ENUM.CACHE_FORMAT:
      case TO_JSON_TYPES_ENUM.STORAGE_FORMAT:
      case TO_JSON_TYPES_ENUM.DISCORD_FORMAT:
      default: {
        return {
          id: this.id,
          guild_id: this.guildId,
          name: this.name,
          description: this.description,
          creator_id: this.creatorId ?? undefined,
          creator: this.creator.toJSON(format),
          scheduled_start_time: this.scheduledStartTime * 1000,
          scheduled_end_time: this.scheduledEndTime
            ? this.scheduledEndTime * 1000
            : undefined,
          image: this.#_originalImageHash,
          user_count: this.userCount,
          entity_type: this.#rawEntityType,
          status: this.#rawStatus,
          entity_metadata: {
            location: this.location,
          },
        };
      }
    }
  }
}

export default ScheduledEvent;