defmodule Nostrum.Struct.Guild.Member do @moduledoc ~S""" Struct representing a Discord guild member. A `Nostrum.Struct.Guild.Member` stores a `Nostrum.Struct.User`'s properties pertaining to a specific `Nostrum.Struct.Guild`. ## Mentioning Members in Messages A `Nostrum.Struct.Guild.Member` can be mentioned in message content using the `String.Chars` protocol or `mention/1`. ```Elixir member = %Nostrum.Struct.Guild.Member{user: Nostrum.Struct.User{id: 120571255635181568}} Nostrum.Api.create_message!(184046599834435585, "#{member}") %Nostrum.Struct.Message{content: "<@120571255635181568>"} member = %Nostrum.Struct.Guild.Member{user: Nostrum.Struct.User{id: 89918932789497856}} Nostrum.Api.create_message!(280085880452939778, "#{Nostrum.Struct.Guild.Member.mention(member)}") %Nostrum.Struct.Message{content: "<@89918932789497856>"} ``` """ alias Nostrum.Permission alias Nostrum.Struct.{Channel, Guild, User} alias Nostrum.Struct.Guild.Role alias Nostrum.{Snowflake, Util} defstruct [ :user, :nick, :roles, :joined_at, :deaf, :mute, :communication_disabled_until, :premium_since ] defimpl String.Chars do def to_string(member), do: @for.mention(member) end @typedoc """ The user struct. This field can be `nil` if the Member struct came as a partial Member object included in a message received from a guild channel. """ @type user :: User.t() | nil @typedoc "The nickname of the user" @type nick :: String.t() | nil @typedoc "A list of role ids" @type roles :: [Role.id()] @typedoc """ Date the user joined the guild. If you dont request offline guild members this field will be `nil` for any members that come online. """ @type joined_at :: String.t() | nil @typedoc """ Whether the user is deafened. If you dont request offline guild members this field will be `nil` for any members that come online. """ @type deaf :: boolean | nil @typedoc """ Whether the user is muted. If you dont request offline guild members this field will be `nil` for any members that come online. """ @type mute :: boolean | nil @typedoc """ Current timeout status of the user. If user is currently timed out this will be a `t:DateTime.t/0` of the unmute time, it will be `nil` or a date in the past if the user is not currently timed out. """ @type communication_disabled_until :: DateTime.t() | nil @typedoc """ Current guild booster status of the user. If user is currently boosting a guild this will be a `t:DateTime.t/0` since the start of the boosting, it will be `nil` if the user is not currently boosting the guild. """ @type premium_since :: DateTime.t() | nil @type t :: %__MODULE__{ user: user, nick: nick, roles: roles, joined_at: joined_at, deaf: deaf, mute: mute, communication_disabled_until: communication_disabled_until, premium_since: premium_since } @doc ~S""" Formats a `Nostrum.Struct.Guild.Member` into a mention. ## Examples ```Elixir iex> member = %Nostrum.Struct.Guild.Member{user: %Nostrum.Struct.User{id: 177888205536886784}} ...> Nostrum.Struct.Guild.Member.mention(member) "<@177888205536886784>" ``` """ @spec mention(t) :: String.t() def mention(%__MODULE__{user: user}), do: User.mention(user) @doc """ Returns a member's guild permissions. ## Examples ```Elixir guild = Nostrum.Cache.GuildCache.get!(279093381723062272) member = Map.get(guild.members, 177888205536886784) Nostrum.Struct.Guild.Member.guild_permissions(member, guild) #=> [:administrator] ``` """ @spec guild_permissions(t, Guild.t()) :: [Permission.t()] def guild_permissions(member, guild) def guild_permissions(%__MODULE__{user: %{id: user_id}}, %Guild{owner_id: owner_id}) when user_id === owner_id, do: Permission.all() def guild_permissions(%__MODULE__{} = member, %Guild{} = guild) do use Bitwise everyone_role_id = guild.id member_role_ids = member.roles ++ [everyone_role_id] member_permissions = member_role_ids |> Enum.map(&Map.get(guild.roles, &1)) |> Enum.filter(&(!match?(nil, &1))) |> Enum.reduce(0, fn role, bitset_acc -> bitset_acc ||| role.permissions end) |> Permission.from_bitset() if Enum.member?(member_permissions, :administrator) do Permission.all() else member_permissions end end @doc """ Returns a member's permissions in a guild channel, based on its `Nostrum.Struct.Overwrite`s. ## Examples ```Elixir guild = Nostrum.Cache.GuildCache.get!(279093381723062272) member = Map.get(guild.members, 177888205536886784) channel_id = 381889573426429952 Nostrum.Struct.Guild.Member.guild_channel_permissions(member, guild, channel_id) #=> [:manage_messages] ``` """ @spec guild_channel_permissions(t, Guild.t(), Channel.id()) :: [Permission.t()] def guild_channel_permissions(%__MODULE__{} = member, guild, channel_id) do use Bitwise guild_perms = guild_permissions(member, guild) if Enum.member?(guild_perms, :administrator) do Permission.all() else channel = Map.get(guild.channels, channel_id) everyone_role_id = guild.id role_ids = [everyone_role_id | member.roles] overwrite_ids = role_ids ++ [member.user.id] {allow, deny} = channel.permission_overwrites |> Enum.filter(&(&1.id in overwrite_ids)) |> Enum.map(fn overwrite -> {overwrite.allow, overwrite.deny} end) |> Enum.reduce({0, 0}, fn {allow, deny}, {allow_acc, deny_acc} -> {allow_acc ||| allow, deny_acc ||| deny} end) allow_perms = allow |> Permission.from_bitset() deny_perms = deny |> Permission.from_bitset() guild_perms |> Enum.reject(&(&1 in deny_perms)) |> Enum.concat(allow_perms) |> Enum.dedup() end end @doc """ Return the topmost role of the given member on the given guild. The topmost role is determined via `t:Nostrum.Struct.Guild.Role.position`. ## Parameters - `member`: The member whose top role to return. - `guild`: The guild which the member belongs to. ## Return value The topmost role of the member on the given guild, if the member has roles assigned. Otherwise, `nil` is returned. """ @doc since: "0.5.0" @spec top_role(__MODULE__.t(), Guild.t()) :: Role.t() | nil def top_role(%__MODULE__{roles: member_roles}, %Guild{roles: guild_roles}) do guild_roles |> Stream.filter(fn {id, _role} -> id in member_roles end) |> Stream.map(fn {_id, role} -> role end) |> Enum.max_by(& &1.position, fn -> nil end) end @doc false def p_encode do %__MODULE__{ user: User.p_encode() } end @doc false def to_struct(map) do new = map |> Map.new(fn {k, v} -> {Util.maybe_to_atom(k), v} end) |> Map.update(:user, nil, &Util.cast(&1, {:struct, User})) |> Map.update(:roles, nil, &Util.cast(&1, {:list, Snowflake})) |> Map.update(:communication_disabled_until, nil, &Util.maybe_to_datetime/1) |> Map.update(:premium_since, nil, &Util.maybe_to_datetime/1) struct(__MODULE__, new) end end