Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add missing fields from member API object #573

Merged
merged 3 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/nostrum/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ defmodule Nostrum.Constants do
def cdn_icon(id, icon, image_format), do: "/icons/#{id}/#{icon}.#{image_format}"
def cdn_splash(id, splash, image_format), do: "/splashes/#{id}/#{splash}.#{image_format}"

def cdn_guild_avatar(guild_id, user_id, avatar_hash, image_format) do
"/guilds/#{guild_id}/users/#{user_id}/avatars/#{avatar_hash}.#{image_format}"
end

def thread_with_message(channel_id, message_id),
do: "/channels/#{channel_id}/messages/#{message_id}/threads"

Expand Down
92 changes: 78 additions & 14 deletions lib/nostrum/struct/guild/member.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,23 @@ defmodule Nostrum.Struct.Guild.Member do
```
"""

alias Nostrum.Permission
alias Nostrum.Struct.{Channel, Guild, User}
alias Nostrum.Struct.Guild.Role
alias Nostrum.{Snowflake, Util}
alias Nostrum.{Constants, Permission, Snowflake, Util}
import Bitwise

defstruct [
:user_id,
:nick,
:roles,
:joined_at,
:avatar,
:communication_disabled_until,
:deaf,
:flags,
:joined_at,
:mute,
:communication_disabled_until,
:premium_since
:nick,
:pending,
:premium_since,
:roles,
:user_id
]

defimpl String.Chars do
Expand Down Expand Up @@ -93,15 +95,40 @@ defmodule Nostrum.Struct.Guild.Member do
"""
@type premium_since :: DateTime.t() | nil

@typedoc """
Avatar hash of the custom avatar set by the user in the guild.

If animated, this is prefixed with `a_`.

You can use `avatar_url/3` to fetch a full-formed URL of this asset.
"""
@type avatar :: String.t() | nil

@typedoc """
Current guild member gate status. `false` if user has yet to pass the membership screening
configuration for the guild, `true` if the member has passed.
"""
@type pending :: boolean | nil

@typedoc """
Guild member flags represented as a bitset.

Look at the `Nostrum.Struct.Guild.Member.Flags` module for guidance parsing this value.
"""
@type flags :: pos_integer() | nil

@type t :: %__MODULE__{
user_id: user_id,
nick: nick,
roles: roles,
joined_at: joined_at,
avatar: avatar,
communication_disabled_until: communication_disabled_until,
deaf: deaf,
flags: flags,
joined_at: joined_at,
mute: mute,
communication_disabled_until: communication_disabled_until,
premium_since: premium_since
nick: nick,
pending: pending,
premium_since: premium_since,
roles: roles,
user_id: user_id
}

@doc ~S"""
Expand All @@ -120,6 +147,43 @@ defmodule Nostrum.Struct.Guild.Member do
"<@#{user_id}>"
end

@doc ~S"""
Returns a guild-specific avatar URL for a `Nostrum.Struct.Guild.Member`.

Supported formats are `png` (default), `jpg`, `webp` and `gif`.

As mentioned in the avatar hash typedoc, if the avatar hash begins with `a_`, the
avatar is animated and can be returned as a gif.

## Examples

```elixir
iex> member = %Nostrum.Struct.Guild.Member{
...> user_id: 165023948638126080,
...> avatar: "4c8319db8ea745275a1399f8f8aa74ab"
...> }
iex> guild_id = 1226944827137069107
iex> Nostrum.Struct.Guild.Member.avatar_url(member, guild_id)
"https://cdn.discordapp.com/guilds/1226944827137069107/users/165023948638126080/avatars/4c8319db8ea745275a1399f8f8aa74ab.png"
```
"""
@spec avatar_url(t, Nostrum.Struct.Guild.id()) :: String.t() | nil
def avatar_url(member, guild_id, image_format \\ "png")

def avatar_url(%{avatar: nil}, _, _) do
nil
end

def avatar_url(%{user_id: user_id, avatar: avatar}, guild_id, image_format) do
Constants.cdn_url() <>
Constants.cdn_guild_avatar(
guild_id,
user_id,
avatar,
image_format
)
end

@doc """
Returns a member's guild permissions.

Expand Down
104 changes: 104 additions & 0 deletions lib/nostrum/struct/guild/member/flags.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
defmodule Nostrum.Struct.Guild.Member.Flags do
@moduledoc """
Struct representing the flags a guild member can have.
"""

import Bitwise

defstruct did_rejoin: false,
completed_onboarding: false,
bypasses_verification: false,
started_onboarding: false

@typedoc """
Member has left and rejoined the guild
"""
@type did_rejoin :: boolean

@typedoc """
Member has completed onboarding
"""
@type completed_onboarding :: boolean

@typedoc """
Member is exempt from guild verification requirements
"""
@type bypasses_verification :: boolean

@typedoc """
Member has started onboarding
"""
@type started_onboarding :: boolean

@type flags :: %__MODULE__{
did_rejoin: did_rejoin,
completed_onboarding: completed_onboarding,
bypasses_verification: bypasses_verification,
started_onboarding: started_onboarding
}

@type t :: flags

@flag_values [
did_rejoin: 1 <<< 0,
completed_onboarding: 1 <<< 1,
bypasses_verification: 1 <<< 2,
started_onboarding: 1 <<< 3
]

@doc """
Constructs a flag struct based on an integer from the Discord API, normally from `t:Nostrum.Struct.Guild.Member.flags/0`.

## Examples

```elixir
iex> Nostrum.Struct.Guild.Member.Flags.from_integer(9)
%Nostrum.Struct.Guild.Member.Flags{
did_rejoin: true,
completed_onboarding: false,
bypasses_verification: false,
started_onboarding: true
}
```
"""
@spec from_integer(integer()) :: t
def from_integer(flag_value) do
boolean_list =
Enum.map(@flag_values, fn {flag, value} ->
{flag, (flag_value &&& value) == value}
end)

struct(__MODULE__, boolean_list)
end

@doc """
Convert a flag struct to an integer value.

## Examples

```elixir
iex> my_flags = %Nostrum.Struct.Guild.Member.Flags{
...> did_rejoin: true,
...> completed_onboarding: false,
...> bypasses_verification: false,
...> started_onboarding: true
...> }
iex> Nostrum.Struct.Guild.Member.Flags.to_integer(my_flags)
9
```
"""
@spec to_integer(t) :: integer()
def to_integer(flag_struct) do
booleans =
flag_struct
|> Map.from_struct()
|> Map.to_list()

Enum.reduce(booleans, 0, fn {flag, enabled}, flag_value ->
case enabled do
true -> flag_value ||| @flag_values[flag]
false -> flag_value
end
end)
end
end
7 changes: 7 additions & 0 deletions test/nostrum/struct/guild/member/flags_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule Nostrum.Struct.Guild.Member.FlagsTest do
use ExUnit.Case, async: true

alias Nostrum.Struct.Guild.Member.Flags

doctest Flags
end
Loading