CRBT-Team/Purplet

View on GitHub
packages/purplet/src/structures/user.ts

Summary

Maintainability
A
25 mins
Test Coverage
import type { Immutable } from '@paperdave/utils';
import type { ImageURLOptions } from '@purplet/utils';
import type { APIUser } from 'purplet/types';
import { UserFlagsBitfield } from './bit-field';
import type { Interaction } from './interaction';
import { rest } from '../lib/env';
import type { PartialClass } from '../utils/class';
import { createPartialClass } from '../utils/class';

/** Structure for APIUser. */
export class User {
  constructor(readonly raw: Immutable<APIUser>) {}

  async fetch() {
    return new User(await rest.user.getUser({ userId: this.id }));
  }

  get id() {
    return this.raw.id;
  }

  get username() {
    return this.raw.username;
  }

  get discriminator() {
    return this.raw.discriminator;
  }

  get tag() {
    return `${this.username}#${this.discriminator}`;
  }

  get avatarHash() {
    return this.raw.avatar;
  }

  get bannerHash() {
    return this.raw.banner;
  }

  get bannerColor(): string | null {
    // @ts-expect-error Missing from discord-api-types???
    return this.raw.banner_color;
  }

  get accentColor() {
    return this.raw.accent_color;
  }

  get flags() {
    return new UserFlagsBitfield(this.raw.flags ?? this.raw.public_flags);
  }

  get hypesquadHouse() {
    const flags = this.flags;
    return flags.hasHypeSquadOnlineHouse1
      ? 'bravery'
      : flags.hasHypeSquadOnlineHouse2
      ? 'brilliance'
      : flags.hasHypeSquadOnlineHouse3
      ? 'balance'
      : null;
  }

  get isBot() {
    return this.raw.bot ?? this.raw.system;
  }

  get defaultAvatarURL() {
    return rest.cdn.defaultAvatar(parseInt(this.discriminator, 10) % 5);
  }

  avatarURL(options: ImageURLOptions = {}) {
    return this.avatarHash
      ? rest.cdn.avatar(this.id, this.avatarHash, options)
      : this.defaultAvatarURL;
  }

  bannerURL(options: ImageURLOptions = {}) {
    return this.bannerHash ? rest.cdn.banner(this.id, this.bannerHash, options) : null;
  }

  // TODO: uncomment and tweak when released.
  // get avatarDecorationId() {
  //   // @ts-expect-error Unreleased, so obviously missing from discord-api-types
  //   return this.raw.avatar_decoration;
  // }

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

// Uncomment if needed
// export class OAuthUser extends User {
//   get isEmailVerified() {
//     return this.raw.verified;
//   }
//
//   get email() {
//     return this.raw.email;
//   }
// }

// InteractionExecutingUser refers to the user that is executing an interaction, seen on `interaction.user`.
// InteractionUser refers to user data that is passed to an interaction.
export type PartialUser = PartialClass<
  // Class, Required properties from `raw`, Allowed methods from class
  typeof User,
  'id' | 'username' | 'public_flags' | 'discriminator' | 'avatar', // | 'avatar_decoration',
  | 'id'
  | 'username'
  | 'flags'
  | 'discriminator'
  | 'tag'
  | 'avatarHash'
  | 'avatarURL'
  | 'defaultAvatarURL'
  | 'hypesquadHouse'
  | 'isBot'
  | 'fetch'
  | 'toString'
>;
export const PartialUser = createPartialClass<PartialUser>(User);

export type EmptyUser = PartialClass<
  // Class, Required properties from `raw`, Allowed methods from class
  typeof User,
  'id',
  'id' | 'fetch' | 'toString'
>;
export const EmptyUser = createPartialClass<EmptyUser>(User);

export class InteractionExecutingUser extends PartialUser {
  constructor(readonly raw: APIUser, readonly interaction: Interaction) {
    super(raw);
  }

  get locale() {
    return this.interaction.locale;
  }
}