lib/nostrum/struct/user.ex

defmodule Nostrum.Struct.User do
  @moduledoc ~S"""
  Struct representing a Discord user.

  ## Mentioning Users in Messages

  A `Nostrum.Struct.User` can be mentioned in message content using the `String.Chars`
  protocol or `mention/1`.

  ```Elixir
  user = %Nostrum.Struct.User{id: 120571255635181568}
  Nostrum.Api.create_message!(184046599834435585, "#{user}")
  %Nostrum.Struct.Message{content: "<@120571255635181568>"}

  user = %Nostrum.Struct.User{id: 89918932789497856}
  Nostrum.Api.create_message!(280085880452939778, "#{Nostrum.Struct.User.mention(user)}")
  %Nostrum.Struct.Message{content: "<@89918932789497856>"}
  ```

  ## User vs. Member
  A `user` contains only general information about that user such as a `username` and an `avatar`.
  A `member` has everything that a `user` has, but also additional information on a per guild basis. This includes things like a `nickname` and a list of `roles`.
  """

  alias Nostrum.{Constants, Snowflake, Struct.User.Flags, Util}

  defstruct [
    :id,
    :username,
    :discriminator,
    :avatar,
    :bot,
    :mfa_enabled,
    :verified,
    :email,
    :public_flags
  ]

  defimpl String.Chars do
    def to_string(user), do: @for.mention(user)
  end

  @typedoc "The user's id"
  @type id :: Snowflake.t()

  @typedoc "The user's username"
  @type username :: String.t()

  @typedoc "The user's 4--digit discord-tag"
  @type discriminator :: String.t()

  @typedoc "User's avatar hash"
  @type avatar :: String.t() | nil

  @typedoc "Whether the user is a bot"
  @type bot :: boolean | nil

  @typedoc "Whether the user has two factor enabled"
  @type mfa_enabled :: boolean | nil

  @typedoc "Whether the email on the account has been verified"
  @type verified :: boolean | nil

  @typedoc "The user's email"
  @type email :: String.t() | nil

  @typedoc "The user's public flags"
  @type public_flags :: Flags.t()

  @type t :: %__MODULE__{
          id: id,
          username: username,
          discriminator: discriminator,
          avatar: avatar,
          bot: bot,
          mfa_enabled: mfa_enabled,
          verified: verified,
          email: email,
          public_flags: public_flags
        }

  @doc ~S"""
  Formats an `Nostrum.Struct.User` into a mention.

  ## Examples

  ```Elixir
  iex> user = %Nostrum.Struct.User{id: 177888205536886784}
  ...> Nostrum.Struct.User.mention(user)
  "<@177888205536886784>"
  ```
  """
  @spec mention(t) :: String.t()
  def mention(%__MODULE__{id: id}), do: "<@#{id}>"

  @doc """
  Returns the URL of a user's display avatar.

  If `:avatar` is `nil`, the default avatar url is returned.

  Supported image formats are PNG, JPEG, WebP, and GIF.

  ## Examples

  ```Elixir
  iex> user = %Nostrum.Struct.User{avatar: "8342729096ea3675442027381ff50dfe",
  ...>                             id: 80351110224678912}
  iex> Nostrum.Struct.User.avatar_url(user)
  "https://cdn.discordapp.com/avatars/80351110224678912/8342729096ea3675442027381ff50dfe.webp"
  iex> Nostrum.Struct.User.avatar_url(user, "png")
  "https://cdn.discordapp.com/avatars/80351110224678912/8342729096ea3675442027381ff50dfe.png"

  iex> user = %Nostrum.Struct.User{avatar: nil,
  ...>                             discriminator: "1337"}
  iex> Nostrum.Struct.User.avatar_url(user)
  "https://cdn.discordapp.com/embed/avatars/2.png"
  ```
  """
  @spec avatar_url(t, String.t()) :: String.t()
  def avatar_url(user, image_format \\ "webp")

  def avatar_url(%__MODULE__{avatar: nil, discriminator: disc}, _) do
    image_name =
      disc
      |> String.to_integer()
      |> rem(5)

    URI.encode(Constants.cdn_url() <> Constants.cdn_embed_avatar(image_name))
  end

  def avatar_url(%__MODULE__{id: id, avatar: avatar}, image_format),
    do: URI.encode(Constants.cdn_url() <> Constants.cdn_avatar(id, avatar, image_format))

  @doc """
  Returns a user's `:username` and `:discriminator` separated by a hashtag.

  ## Examples

  ```Elixir
  iex> user = %Nostrum.Struct.User{username: "b1nzy",
  ...>                             discriminator: "0852"}
  iex> Nostrum.Struct.User.full_name(user)
  "b1nzy#0852"
  ```
  """
  @spec full_name(t) :: String.t()
  def full_name(%__MODULE__{username: username, discriminator: disc}),
    do: "#{username}##{disc}"

  @doc false
  def p_encode do
    %__MODULE__{}
  end

  @doc false
  def to_struct(map) do
    new =
      map
      |> Map.new(fn {k, v} -> {Util.maybe_to_atom(k), v} end)
      |> Map.update(:id, nil, &Util.cast(&1, Snowflake))
      |> Map.update(:public_flags, %Flags{}, &Flags.from_integer(&1))

    struct(__MODULE__, new)
  end
end