diff --git a/lib/nostrum/constants.ex b/lib/nostrum/constants.ex index 297adf2c1..16624da85 100644 --- a/lib/nostrum/constants.ex +++ b/lib/nostrum/constants.ex @@ -154,6 +154,10 @@ defmodule Nostrum.Constants do def cdn_emoji(id, image_format), do: "/emojis/#{id}.#{image_format}" 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_banner(id, banner, image_format), do: "/banners/#{id}/#{banner}.#{image_format}" + + def cdn_discovery_splash(id, splash, image_format), + do: "/discovery-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}" diff --git a/lib/nostrum/struct/guild.ex b/lib/nostrum/struct/guild.ex index 7c0e688f0..53106913b 100644 --- a/lib/nostrum/struct/guild.ex +++ b/lib/nostrum/struct/guild.ex @@ -41,6 +41,7 @@ defmodule Nostrum.Struct.Guild do :system_channel_id, :rules_channel_id, :public_updates_channel_id, + :safety_alerts_channel_id, :joined_at, :large, :unavailable, @@ -50,7 +51,21 @@ defmodule Nostrum.Struct.Guild do :guild_scheduled_events, :vanity_url_code, :threads, - :stickers + :stickers, + :discovery_splash, + :system_channel_flags, + :max_presences, + :max_members, + :description, + :banner, + :premium_tier, + :premium_subscription_count, + :preferred_locale, + :max_video_channel_users, + :max_stage_video_channel_users, + :welcome_screen, + :nsfw_level, + :premium_progress_bar_enabled ] @typedoc "The guild's id" @@ -165,6 +180,63 @@ defmodule Nostrum.Struct.Guild do @typedoc "Custom stickers registered to the guild" @type stickers :: [Sticker.t()] + @typedoc "Hash of the Discovery splash screen" + @type discovery_splash :: String.t() | nil + + @typedoc """ + Bitset representing the system channel flags + + See `Nostrum.Struct.Guild.SystemChannelFlags` for more information on the flag + contents as well as methods to parse and create your own values for this + field. + """ + @type system_channel_flags :: pos_integer() + + @typedoc """ + Maximum number of presences for the guild. + + This will be unset for most guilds, except for in Discord's terms, the + "largest of guilds", where the field will be set to the maximum number of + online (gateway connected) members. + """ + @type max_presences :: pos_integer() | nil + + @typedoc "Maximum members for the guild" + @type max_members :: pos_integer() | nil + + @typedoc "User-set description of the guild" + @type description :: String.t() | nil + + @typedoc "Banner hash for the guild, if prefixed with `a_` an animated GIF is available." + @type banner :: String.t() | nil + + @typedoc "Premium tier of the guild (0-3)" + @type premium_tier :: 0..3 + + @typedoc "Number of boosts received by the guild" + @type premium_subscription_count :: pos_integer() + + @typedoc "Preferred locale for the guild, set by the user" + @type preferred_locale :: String.t() | nil + + @typedoc "The maximum amount of users in a video channel" + @type max_video_channel_users :: pos_integer() | nil + + @typedoc "The maximum amount of users in a stage video channel" + @type max_stage_video_channel_users :: pos_integer() | nil + + @typedoc "The welcome screen of a Community guild, shown to new members, returned in an Invite's guild object" + @type welcome_screen :: map() | nil + + @typedoc "NSFW level for the guild, unrated guilds have `:default`" + @type nsfw_level :: :default | :explicit | :safe | :age_restricted + + @typedoc "Whether the guild has the boost progress bar enabled" + @type premium_progress_bar_enabled :: boolean + + @typedoc "The id of the channel where admins and moderators of Community guilds receive safety alerts from Discord" + @type safety_alerts_channel_id :: Channel.id() | nil + @typedoc """ A `Nostrum.Struct.Guild` that is sent on user-specific rest endpoints. """ @@ -198,7 +270,22 @@ defmodule Nostrum.Struct.Guild do channels: nil, vanity_url_code: nil, threads: nil, - stickers: nil + stickers: nil, + discovery_splash: nil, + system_channel_flags: nil, + max_presences: nil, + max_members: nil, + description: nil, + banner: nil, + premium_tier: nil, + premium_subscription_count: nil, + preferred_locale: nil, + max_video_channel_users: nil, + max_stage_video_channel_users: nil, + welcome_screen: nil, + nsfw_level: nil, + premium_progress_bar_enabled: nil, + safety_alerts_channel_id: nil } @typedoc """ @@ -235,7 +322,22 @@ defmodule Nostrum.Struct.Guild do voice_states: nil, channels: nil, guild_scheduled_events: nil, - threads: nil + threads: nil, + discovery_splash: nil, + system_channel_flags: nil, + max_presences: nil, + max_members: nil, + description: nil, + banner: nil, + premium_tier: nil, + premium_subscription_count: nil, + preferred_locale: nil, + max_video_channel_users: nil, + max_stage_video_channel_users: nil, + welcome_screen: nil, + nsfw_level: nil, + premium_progress_bar_enabled: nil, + safety_alerts_channel_id: nil } @typedoc """ @@ -272,7 +374,22 @@ defmodule Nostrum.Struct.Guild do guild_scheduled_events: nil, vanity_url_code: nil, threads: nil, - stickers: nil + stickers: nil, + discovery_splash: nil, + system_channel_flags: nil, + max_presences: nil, + max_members: nil, + description: nil, + banner: nil, + premium_tier: nil, + premium_subscription_count: nil, + preferred_locale: nil, + max_video_channel_users: nil, + max_stage_video_channel_users: nil, + welcome_screen: nil, + nsfw_level: nil, + premium_progress_bar_enabled: nil, + safety_alerts_channel_id: nil } @typedoc """ @@ -309,7 +426,22 @@ defmodule Nostrum.Struct.Guild do guild_scheduled_events: guild_scheduled_events, vanity_url_code: vanity_url_code, threads: threads, - stickers: stickers + stickers: stickers, + discovery_splash: discovery_splash, + system_channel_flags: system_channel_flags, + max_presences: max_presences, + max_members: max_members, + description: description, + banner: banner, + premium_tier: premium_tier, + premium_subscription_count: premium_subscription_count, + preferred_locale: preferred_locale, + max_video_channel_users: max_video_channel_users, + max_stage_video_channel_users: max_stage_video_channel_users, + welcome_screen: welcome_screen, + nsfw_level: nsfw_level, + premium_progress_bar_enabled: premium_progress_bar_enabled, + safety_alerts_channel_id: safety_alerts_channel_id } @type t :: @@ -372,6 +504,52 @@ defmodule Nostrum.Struct.Guild do def splash_url(%__MODULE__{splash: splash, id: id}, image_format), do: URI.encode(Constants.cdn_url() <> Constants.cdn_splash(id, splash, image_format)) + @doc ~S""" + Returns the URL of the guild's discovery splash, or `nil` if no discovery splash. + + Supported image formats are PNG, JPEG and WebP. + + ## Examples + + ```elixir + iex> guild = %Nostrum.Struct.Guild{discovery_splash: "656477617264736e6f7764656e", + ...> id: 112233445566778899} + iex> Nostrum.Struct.Guild.discovery_splash_url(guild) + "https://cdn.discordapp.com/discovery-splashes/112233445566778899/656477617264736e6f7764656e.webp" + iex> Nostrum.Struct.Guild.discovery_splash_url(guild, "png") + "https://cdn.discordapp.com/discovery-splashes/112233445566778899/656477617264736e6f7764656e.png" + ``` + """ + @spec discovery_splash_url(t, String.t()) :: String.t() | nil + def discovery_splash_url(guild, image_format \\ "webp") + def discovery_splash_url(%__MODULE__{discovery_splash: nil}, _), do: nil + + def discovery_splash_url(%__MODULE__{discovery_splash: dsp, id: id}, image_format), + do: Constants.cdn_url() <> Constants.cdn_discovery_splash(id, dsp, image_format) + + @doc ~S""" + Returns the URL of the guild's banner, or `nil` if no guild banner has been set. + + Supported image formats are PNG, GIF, JPEG and WebP. + + ## Examples + + ```elixir + iex> guild = %Nostrum.Struct.Guild{banner: "656477617264736e6f7764656e", + ...> id: 112233445566778899} + iex> Nostrum.Struct.Guild.banner_url(guild) + "https://cdn.discordapp.com/banners/112233445566778899/656477617264736e6f7764656e.webp" + iex> Nostrum.Struct.Guild.banner_url(guild, "png") + "https://cdn.discordapp.com/banners/112233445566778899/656477617264736e6f7764656e.png" + ``` + """ + @spec discovery_splash_url(t, String.t()) :: String.t() | nil + def banner_url(guild, image_format \\ "webp") + def banner_url(%__MODULE__{banner: nil}, _), do: nil + + def banner_url(%__MODULE__{banner: banner, id: id}, image_format), + do: Constants.cdn_url() <> Constants.cdn_guild_banner(id, banner, image_format) + @doc false def p_encode do %__MODULE__{} @@ -401,10 +579,23 @@ defmodule Nostrum.Struct.Guild do &Util.cast(&1, {:list, {:struct, ScheduledEvent}}) ) |> Map.update(:threads, nil, &Util.cast(&1, {:index, [:id], {:struct, Channel}})) + |> Map.update(:safety_alerts_channel_id, nil, &Util.cast(&1, Snowflake)) + |> Map.update(:nsfw_level, nil, &cast_nsfw_level/1) + |> Map.put_new(:premium_subscription_count, 0) struct(__MODULE__, new) end + @doc false + defp cast_nsfw_level(level) do + case level do + 0 -> :default + 1 -> :explicit + 2 -> :safe + 3 -> :age_restricted + end + end + @doc false @spec merge(t, t) :: t def merge(old_guild, new_guild) do diff --git a/lib/nostrum/struct/guild/system_channel_flags.ex b/lib/nostrum/struct/guild/system_channel_flags.ex new file mode 100644 index 000000000..15e474a79 --- /dev/null +++ b/lib/nostrum/struct/guild/system_channel_flags.ex @@ -0,0 +1,110 @@ +defmodule Nostrum.Struct.Guild.SystemChannelFlags do + @moduledoc """ + Struct representing the flags on a guild's system channel + """ + + import Bitwise + + defstruct suppress_join_notifications: false, + suppress_premium_subscriptions: false, + suppress_guild_reminder_notifications: false, + suppress_join_notification_replies: false, + suppress_role_subscription_purchase_notifications: false, + suppress_role_subscription_purchase_notification_replies: false + + @typedoc "Suppress member join notifications" + @type suppress_join_notifications :: boolean + + @typedoc "Suppress server boost notifications" + @type suppress_premium_subscriptions :: boolean + + @typedoc "Suppress server setup tips" + @type suppress_guild_reminder_notifications :: boolean + + @typedoc "Hide member join sticker reply buttons" + @type suppress_join_notification_replies :: boolean + + @typedoc "Suppress role subscription purchase and renewal notifications" + @type suppress_role_subscription_purchase_notifications :: boolean + + @typedoc "Hide role subscription sticker reply buttons" + @type suppress_role_subscription_purchase_notifications_replies :: boolean + + @type flags :: %__MODULE__{ + suppress_join_notifications: suppress_join_notifications, + suppress_premium_subscriptions: suppress_premium_subscriptions, + suppress_guild_reminder_notifications: suppress_guild_reminder_notifications, + suppress_join_notification_replies: suppress_join_notification_replies, + suppress_role_subscription_purchase_notifications: + suppress_role_subscription_purchase_notifications, + suppress_role_subscription_purchase_notification_replies: + suppress_role_subscription_purchase_notifications_replies + } + + @type t :: flags + + @flag_values [ + suppress_join_notifications: 1 <<< 0, + suppress_premium_subscriptions: 1 <<< 1, + suppress_guild_reminder_notifications: 1 <<< 2, + suppress_join_notification_replies: 1 <<< 3, + suppress_role_subscription_purchase_notifications: 1 <<< 4, + suppress_role_subscription_purchase_notification_replies: 1 <<< 5 + ] + + @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.SystemChannelFlags.from_integer(3) + %Nostrum.Struct.Guild.SystemChannelFlags{ + suppress_guild_reminder_notifications: false, + suppress_join_notification_replies: false, + suppress_join_notifications: true, + suppress_premium_subscriptions: true, + suppress_role_subscription_purchase_notification_replies: false, + suppress_role_subscription_purchase_notifications: false + } + ``` + """ + @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.SystemChannelFlags{ + ...> suppress_join_notifications: true, + ...> suppress_join_notification_replies: true + ...> } + iex> Nostrum.Struct.Guild.SystemChannelFlags.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 diff --git a/test/nostrum/struct/guild/system_channel_flags_test.exs b/test/nostrum/struct/guild/system_channel_flags_test.exs new file mode 100644 index 000000000..894867496 --- /dev/null +++ b/test/nostrum/struct/guild/system_channel_flags_test.exs @@ -0,0 +1,7 @@ +defmodule Nostrum.Struct.Guild.SystemChannelFlagsTest do + use ExUnit.Case, async: true + + alias Nostrum.Struct.Guild.SystemChannelFlags + + doctest SystemChannelFlags +end