From 9d439b90e15ed487aa6756f12c925ce4c6e7e86a Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Sat, 19 Oct 2024 22:26:22 -0700 Subject: [PATCH 01/42] Update Message type typedoc from latest API docs --- lib/nostrum/struct/message.ex | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/nostrum/struct/message.ex b/lib/nostrum/struct/message.ex index d208f7da1..9b16e3693 100644 --- a/lib/nostrum/struct/message.ex +++ b/lib/nostrum/struct/message.ex @@ -145,10 +145,10 @@ defmodule Nostrum.Struct.Message do - `5` - `CHANNEL_ICON_CHANGE` - `6` - `CHANNEL_PINNED_MESSAGE` - `7` - `GUILD_MEMBER_JOIN` - - `8` - `USER_PREMIUM_GUILD_SUBSCRIPTION` - - `9` - `USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1` - - `10` - `USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2` - - `11` - `USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3` + - `8` - `GUILD_BOOST` + - `9` - `GUILD_BOOST_TIER_1` + - `10` - `GUILD_BOOST_TIER_2` + - `11` - `GUILD_BOOST_TIER_3` - `12` - `CHANNEL_FOLLOW_ADD` - `14` - `GUILD_DISCOVERY_DISQUALIFIED` - `15` - `GUILD_DISCOVERY_REQUALIFIED` @@ -156,9 +156,24 @@ defmodule Nostrum.Struct.Message do - `17` - `GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING` - `18` - `THREAD_CREATED` - `19` - `REPLY` - - `20` - `APPLICATION_COMMAND` + - `20` - `CHAT_INPUT_COMMAND` - `21` - `THREAD_STARTER_MESSAGE` - `22` - `GUILD_INVITE_REMINDER` + - `23` - `CONTEXT_MENU_COMMAND` + - `24` - `AUTO_MODERATION_ACTION` + - `25` - `ROLE_SUBSCRIPTION_PURCHASE` + - `26` - `INTERACTION_PREMIUM_UPSELL` + - `27` - `STAGE_START` + - `28` - `STAGE_END` + - `29` - `STAGE_SPEAKER` + - `31` - `STAGE_TOPIC` + - `32` - `GUILD_APPLICATION_PREMIUM_SUBSCRIPTION` + - `36` - `GUILD_INCIDENT_ALERT_MODE_ENABLED` + - `37` - `GUILD_INCIDENT_ALERT_MODE_DISABLED` + - `38` - `GUILD_INCIDENT_REPORT_RAID` + - `39` - `GUILD_INCIDENT_REPORT_FALSE_ALARM` + - `44` - `PURCHASE_NOTIFICATION` + - `46` - `POLL_RESULT` """ @type type :: integer() From 9c97daf9b22fe2ef63720379085e259d0f608e0f Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Sat, 19 Oct 2024 22:26:57 -0700 Subject: [PATCH 02/42] Update buttons guide image from API docs --- guides/assets/buttons.png | Bin 40520 -> 47651 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/guides/assets/buttons.png b/guides/assets/buttons.png index 7480e1da90a413f8dfe8e13b66a9a2a8c5dc2bb2..f89e6e82ab4094a33b8c863fc49cb0ad9745ec87 100644 GIT binary patch literal 47651 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 z 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 literal 40520 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 CfS#QI From d32e1788e7bdedbfc9b042bb6d763a935bd93364 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Sat, 19 Oct 2024 22:28:56 -0700 Subject: [PATCH 03/42] Update `Struct.Channel` with default_sort_order and default_forum_layout Update guild_forum_channel type spec accordingly --- lib/nostrum/struct/channel.ex | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/nostrum/struct/channel.ex b/lib/nostrum/struct/channel.ex index 79858d80d..283446c3a 100644 --- a/lib/nostrum/struct/channel.ex +++ b/lib/nostrum/struct/channel.ex @@ -148,7 +148,9 @@ defmodule Nostrum.Struct.Channel do :available_tags, :applied_tags, :default_reaction_emoji, - :default_thread_rate_limit_per_user + :default_thread_rate_limit_per_user, + :default_sort_order, + :default_forum_layout ] @typedoc """ @@ -219,6 +221,18 @@ defmodule Nostrum.Struct.Channel do @typedoc since: "0.7.0" @type default_thread_rate_limit_per_user :: integer() | nil + @typedoc """ + The default sort order for posts in a `GUILD_FORUM` (`15`) channel. + """ + @typedoc since: "0.11.0" + @type default_sort_order :: integer() | nil + + @typedoc """ + The default layout for posts in a `GUILD_FORUM` (`15`) channel. + """ + @typedoc since: "0.11.0" + @type default_forum_layout :: integer() | nil + @typedoc """ The user limit of a voice channel. """ @@ -620,7 +634,9 @@ defmodule Nostrum.Struct.Channel do available_tags: [forum_tag], rate_limit_per_user: rate_limit_per_user, default_reaction_emoji: default_reaction_emoji, - default_thread_rate_limit_per_user: default_thread_rate_limit_per_user + default_thread_rate_limit_per_user: default_thread_rate_limit_per_user, + default_sort_order: default_sort_order, + default_forum_layout: default_forum_layout } @typedoc """ From 41506814764b0a3bf95fc6899a8f66ccaa24484c Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Sat, 19 Oct 2024 22:49:14 -0700 Subject: [PATCH 04/42] Remove id callout to match rest of typedocs --- lib/nostrum/struct/channel.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nostrum/struct/channel.ex b/lib/nostrum/struct/channel.ex index 283446c3a..1de3f7928 100644 --- a/lib/nostrum/struct/channel.ex +++ b/lib/nostrum/struct/channel.ex @@ -222,13 +222,13 @@ defmodule Nostrum.Struct.Channel do @type default_thread_rate_limit_per_user :: integer() | nil @typedoc """ - The default sort order for posts in a `GUILD_FORUM` (`15`) channel. + The default sort order for posts in a `GUILD_FORUM` channel. """ @typedoc since: "0.11.0" @type default_sort_order :: integer() | nil @typedoc """ - The default layout for posts in a `GUILD_FORUM` (`15`) channel. + The default layout for posts in a `GUILD_FORUM` channel. """ @typedoc since: "0.11.0" @type default_forum_layout :: integer() | nil From d4969e2ad2eb149838960f60acb4f351e9543158 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 22 Oct 2024 20:45:47 -0700 Subject: [PATCH 05/42] Rename base to adapter to indicate direction towards building behavior driven adapters --- lib/nostrum/api/{base.ex => adapter.ex} | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename lib/nostrum/api/{base.ex => adapter.ex} (96%) diff --git a/lib/nostrum/api/base.ex b/lib/nostrum/api/adapter.ex similarity index 96% rename from lib/nostrum/api/base.ex rename to lib/nostrum/api/adapter.ex index 069d24a8f..98e631792 100644 --- a/lib/nostrum/api/base.ex +++ b/lib/nostrum/api/adapter.ex @@ -1,6 +1,4 @@ -defmodule Nostrum.Api.Base do - @moduledoc false - +defmodule Nostrum.Api.Adapter do @version Nostrum.Mixfile.project()[:version] import Nostrum.Constants, only: [base_route: 0] From 6d0350b60b0305e9acd12eb10d89aaff62c834e2 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 22 Oct 2024 20:46:43 -0700 Subject: [PATCH 06/42] Add `Nostrum.Api.ApplicationCommand` module and functions --- lib/nostrum/api/application_command.ex | 406 +++++++++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 lib/nostrum/api/application_command.ex diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex new file mode 100644 index 000000000..91283af06 --- /dev/null +++ b/lib/nostrum/api/application_command.ex @@ -0,0 +1,406 @@ +defmodule Nostrum.Api.ApplicationCommand do + alias Nostrum.Api + alias Nostrum.Struct + alias Nostrum.Constants + alias Nostrum.Snowflake + alias Nostrum.Cache.Me + alias Nostrum.Struct.User + alias Nostrum.Struct.Guild + + @doc """ + Edits command permissions for a specific command for your application in a guild. You can only add up to 10 permission overwrites for a command. + + ## Parameters + - `application_id`: Application ID commands are registered under. + If not given, this will be fetched from `Me`. + - `guild_id`: Guild ID to fetch command permissions from. + - `command_id`: Command ID to fetch permissions for. + - `permissions`: List of partial [guild application command permissions](hhttps://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure) with `id` and `permissions`. You can add up to 10 overwrites per command. + + ## Return value + This method returns a guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + """ + @doc since: "0.5.0" + @spec batch_edit_permissions(Guild.id(), [ + %{ + id: Snowflake.t(), + permissions: [Struct.ApplicationCommand.application_command_permissions()] + } + ]) :: + {:ok, map()} | Api.error() + @spec batch_edit_permissions(User.id(), Guild.id(), [ + %{ + id: Snowflake.t(), + permissions: [Struct.ApplicationCommand.application_command_permissions()] + } + ]) :: + {:ok, map()} | Api.error() + def batch_edit_permissions( + application_id \\ Me.get().id, + guild_id, + permissions + ) do + Api.request( + :put, + Constants.guild_application_command_permissions(application_id, guild_id), + permissions + ) + |> Api.handle_request_with_decode() + end + + @doc """ + Overwrite the existing global application commands. + + This action will: + - Create any command that was provided and did not already exist + - Update any command that was provided and already existed if its configuration changed + - Delete any command that was not provided but existed on Discord's end + + Updates will be available in all guilds after 1 hour. + Commands that do not already exist will count toward daily application command create limits. + + ## Parameters + - `application_id`: Application ID for which to overwrite the commands. + If not given, this will be fetched from `Me`. + - `commands`: List of command configurations, see the linked API documentation for reference. + + ## Return value + Updated list of global application commands. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands + """ + @doc since: "0.5.0" + @spec bulk_overwrite_global_commands([ + Struct.ApplicationCommand.application_command_map() + ]) :: + {:ok, [map()]} | Api.error() + @spec bulk_overwrite_global_commands(User.id(), [ + Struct.ApplicationCommand.application_command_map() + ]) :: {:ok, [map()]} | Api.error() + def bulk_overwrite_global_commands(application_id \\ Me.get().id, commands) do + Api.request(:put, Constants.global_application_commands(application_id), commands) + |> Api.handle_request_with_decode() + end + + @doc """ + Create a new global application command. + + The new command will be available on all guilds in around an hour. + If you want to test commands, use `create_guild_command/2` instead, + as commands will become available instantly there. + If an existing command with the same name exists, it will be overwritten. + + ## Parameters + - `application_id`: Application ID for which to create the command. + If not given, this will be fetched from `Me`. + - `command`: Command configuration, see the linked API documentation for reference. + + ## Return value + The created command. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#create-global-application-command + + ## Example + + ```elixir + Nostrum.Api.create_global_command( + %{name: "edit", description: "ed, man! man, ed", options: []} + ) + ``` + """ + @spec create_global_command(Struct.ApplicationCommand.application_command_map()) :: + {:ok, map()} | Api.error() + @spec create_global_command( + User.id(), + Struct.ApplicationCommand.application_command_map() + ) :: + {:ok, map()} | Api.error() + def create_global_command(application_id \\ Me.get().id, command) do + Api.request(:post, Constants.global_application_commands(application_id), command) + |> Api.handle_request_with_decode() + end + + @doc """ + Create a guild application command on the specified guild. + + The new command will be available immediately. + + ## Parameters + - `application_id`: Application ID for which to create the command. + If not given, this will be fetched from `Me`. + - `guild_id`: Guild on which to create the command. + - `command`: Command configuration, see the linked API documentation for reference. + + ## Return value + The created command. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command + """ + @spec create_guild_command(Guild.id(), Struct.ApplicationCommand.application_command_map()) :: + {:ok, map()} | Api.error() + @spec create_guild_command( + User.id(), + Guild.id(), + Struct.ApplicationCommand.application_command_map() + ) :: {:ok, map()} | Api.error() + def create_guild_command( + application_id \\ Me.get().id, + guild_id, + command + ) do + Api.request(:post, Constants.guild_application_commands(application_id, guild_id), command) + |> Api.handle_request_with_decode() + end + + @doc """ + Delete an existing global application command. + + ## Parameters + - `application_id`: Application ID for which to create the command. + If not given, this will be fetched from `Me`. + - `command_id`: The current snowflake of the command. + """ + @spec delete_global_command(Snowflake.t()) :: {:ok} | Api.error() + @spec delete_global_command(User.id(), Snowflake.t()) :: {:ok} | Api.error() + def delete_global_command(application_id \\ Me.get().id, command_id) do + Api.request(:delete, Constants.global_application_command(application_id, command_id)) + end + + @doc """ + Delete an existing guild application command. + + ## Parameters + - `application_id`: Application ID for which to create the command. + If not given, this will be fetched from `Me`. + - `guild_id`: The guild on which the command exists. + - `command_id`: The current snowflake of the command. + """ + @spec delete_guild_command(Guild.id(), Snowflake.t()) :: {:ok} | Api.error() + @spec delete_guild_command(User.id(), Guild.id(), Snowflake.t()) :: + {:ok} | Api.error() + def delete_guild_command( + application_id \\ Me.get().id, + guild_id, + command_id + ) do + Api.request( + :delete, + Constants.guild_application_command(application_id, guild_id, command_id) + ) + end + + @doc """ + Edits command permissions for a specific command for your application in a guild. You can only add up to 10 permission overwrites for a command. + + ## Parameters + - `application_id`: Application ID commands are registered under. + If not given, this will be fetched from `Me`. + - `guild_id`: Guild ID to fetch command permissions from. + - `command_id`: Command ID to fetch permissions for. + - `permissions`: List of [application command permissions](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure) + + ## Return value + This method returns a guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + """ + @doc since: "0.5.0" + @spec edit_command_permissions(Guild.id(), Snowflake.t(), [ + Struct.ApplicationCommand.application_command_permissions() + ]) :: + {:ok, map()} | Api.error() + @spec edit_command_permissions(User.id(), Guild.id(), Snowflake.t(), [ + Struct.ApplicationCommand.application_command_permissions() + ]) :: + {:ok, map()} | Api.error() + def edit_command_permissions( + application_id \\ Me.get().id, + guild_id, + command_id, + permissions + ) do + Api.request( + :put, + Constants.guild_application_command_permissions(application_id, guild_id, command_id), + %{ + permissions: permissions + } + ) + |> Api.handle_request_with_decode() + end + + @doc """ + Update an existing global application command. + + The updated command will be available on all guilds in around an hour. + + ## Parameters + - `application_id`: Application ID for which to edit the command. + If not given, this will be fetched from `Me`. + - `command_id`: The current snowflake of the command. + - `command`: Command configuration, see the linked API documentation for reference. + + ## Return value + The updated command. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command + """ + @spec edit_global_command( + Snowflake.t(), + Struct.ApplicationCommand.application_command_edit_map() + ) :: {:ok, map()} | Api.error() + @spec edit_global_command( + User.id(), + Snowflake.t(), + Struct.ApplicationCommand.application_command_edit_map() + ) :: {:ok, map()} | Api.error() + def edit_global_command( + application_id \\ Me.get().id, + command_id, + command + ) do + Api.request(:patch, Constants.global_application_command(application_id, command_id), command) + |> Api.handle_request_with_decode() + end + + @doc """ + Update an existing guild application command. + + The updated command will be available immediately. + + ## Parameters + - `application_id`: Application ID for which to edit the command. + If not given, this will be fetched from `Me`. + - `guild_id`: Guild for which to update the command. + - `command_id`: The current snowflake of the command. + - `command`: Command configuration, see the linked API documentation for reference. + + ## Return value + The updated command. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command + """ + @spec edit_guild_command( + Guild.id(), + Snowflake.t(), + Struct.ApplicationCommand.application_command_edit_map() + ) :: {:ok, map()} | Api.error() + @spec edit_guild_command( + User.id(), + Guild.id(), + Snowflake.t(), + Struct.ApplicationCommand.application_command_edit_map() + ) :: + {:ok, map()} | Api.error() + def edit_guild_command( + application_id \\ Me.get().id, + guild_id, + command_id, + command + ) do + Api.request( + :patch, + Constants.guild_application_command(application_id, guild_id, command_id), + command + ) + |> Api.handle_request_with_decode() + end + + @doc """ + Fetches command permissions for a specific command for your application in a guild. + + ## Parameters + - `application_id`: Application ID commands are registered under. + If not given, this will be fetched from `Me`. + - `guild_id`: Guild ID to fetch command permissions from. + - `command_id`: Command ID to fetch permissions for. + + ## Return value + This method returns a single guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + """ + @doc since: "0.5.0" + @spec get_command_permissions(Guild.id(), Snowflake.t()) :: + {:ok, map()} | Api.error() + @spec get_command_permissions(User.id(), Guild.id(), Snowflake.t()) :: + {:ok, map()} | Api.error() + def get_command_permissions( + application_id \\ Me.get().id, + guild_id, + command_id + ) do + Api.request( + :get, + Constants.guild_application_command_permissions(application_id, guild_id, command_id) + ) + |> Api.handle_request_with_decode() + end + + @doc """ + Fetch all global commands. + + ## Parameters + - `application_id`: Application ID for which to search commands. + If not given, this will be fetched from `Me`. + + ## Return value + A list of ``ApplicationCommand``s on success. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure + + ## Example + + ```elixir + iex> Nostrum.Api.get_global_commands + {:ok, + [ + %{ + application_id: "455589479713865749", + description: "ed, man! man, ed", + id: "789841753196331029", + name: "edit" + } + ]} + ``` + """ + @spec get_global_commands() :: {:ok, [map()]} | Api.error() + @spec get_global_commands(User.id()) :: {:ok, [map()]} | Api.error() + def get_global_commands(application_id \\ Me.get().id) do + Api.request(:get, Constants.global_application_commands(application_id)) + |> Api.handle_request_with_decode() + end + + @doc """ + Fetches command permissions for all commands for your application in a guild. + + ## Parameters + - `application_id`: Application ID commands are registered under. + If not given, this will be fetched from `Me`. + - `guild_id`: Guild ID to fetch command permissions from. + + ## Return value + This method returns a list of guild application command permission objects, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + """ + @doc since: "0.5.0" + @spec get_guild_command_permissions(Guild.id()) :: {:ok, [map()]} | Api.error() + @spec get_guild_command_permissions(User.id(), Guild.id()) :: + {:ok, [map()]} | Api.error() + def get_guild_command_permissions( + application_id \\ Me.get().id, + guild_id + ) do + Api.request(:get, Constants.guild_application_command_permissions(application_id, guild_id)) + |> Api.handle_request_with_decode() + end + + @doc """ + Fetch all guild application commands for the given guild. + + ## Parameters + - `application_id`: Application ID for which to fetch commands. + If not given, this will be fetched from `Me`. + - `guild_id`: The guild ID for which guild application commands + should be requested. + + ## Return value + A list of ``ApplicationCommand``s on success. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure + """ + @spec get_guild_commands(Guild.id()) :: {:ok, [map()]} | Api.error() + @spec get_guild_commands(User.id(), Guild.id()) :: {:ok, [map()]} | Api.error() + def get_guild_commands(application_id \\ Me.get().id, guild_id) do + Api.request(:get, Constants.guild_application_commands(application_id, guild_id)) + |> Api.handle_request_with_decode() + end +end From 77f0c2749329f1ed76e9cf3e6ccd43e43f1add86 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 22 Oct 2024 20:46:58 -0700 Subject: [PATCH 07/42] Add `Nostrum.Api.AutoModeration` module and functions --- lib/nostrum/api/auto_moderation.ex | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 lib/nostrum/api/auto_moderation.ex diff --git a/lib/nostrum/api/auto_moderation.ex b/lib/nostrum/api/auto_moderation.ex new file mode 100644 index 000000000..8ad63cef2 --- /dev/null +++ b/lib/nostrum/api/auto_moderation.ex @@ -0,0 +1,82 @@ +defmodule Nostrum.Api.AutoModeration do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.AutoModerationRule + alias Nostrum.Struct.Guild + + @doc """ + Create a new auto-moderation rule for a guild. + + ## Options + * `:name` (`t:String.t/0`) - The name of the rule. + * `:event_type` (`t:AutoModerationRule.event_type/0`) - The type of event that triggers the rule. + * `:trigger_type` (`t:AutoModerationRule.trigger_type/0`) - The type of content that triggers the rule. + * `:trigger_metadata` (`t:AutoModerationRule.trigger_metadata/0`) - The metadata associated with the rule trigger. + - optional, based on the `:trigger_type`. + * `:actions` (`t:AutoModerationRule.actions/0`) - The actions to take when the rule is triggered. + * `:enabled` (`t:AutoModerationRule.enabled/0`) - Whether the rule is enabled or not. + - optional, defaults to `false`. + * `:exempt_roles` - (`t:AutoModerationRule.exempt_roles/0`) - A list of role id's that are exempt from the rule. + - optional, defaults to `[]`, maximum of 20. + * `:exempt_channels` - (`t:AutoModerationRule.exempt_channels/0`) - A list of channel id's that are exempt from the rule. + - optional, defaults to `[]`, maximum of 50. + """ + @doc since: "0.7.0" + @spec create_auto_moderation_rule(Guild.id(), Api.options()) :: + {:ok, AutoModerationRule.t()} | Api.error() + def create_auto_moderation_rule(guild_id, options) when is_list(options), + do: create_auto_moderation_rule(guild_id, Map.new(options)) + + def create_auto_moderation_rule(guild_id, options) do + Api.request(:post, Constants.guild_auto_moderation_rule(guild_id), options) + |> Api.handle_request_with_decode({:struct, AutoModerationRule}) + end + + @doc """ + Delete an auto-moderation rule for a guild. + """ + @doc since: "0.7.0" + @spec delete_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: + {:ok} | Api.error() + def delete_auto_moderation_rule(guild_id, rule_id) do + Api.request(:delete, Constants.guild_auto_moderation_rule(guild_id, rule_id)) + end + + @doc """ + Get a list of all auto-moderation rules for a guild. + """ + @doc since: "0.7.0" + @spec get_auto_moderation_rules(Guild.id()) :: + {:ok, [AutoModerationRule.t()]} | Api.error() + def get_auto_moderation_rules(guild_id) do + Api.request(:get, Constants.guild_auto_moderation_rule(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, AutoModerationRule}}) + end + + @doc """ + Get a single auto-moderation rule for a guild. + """ + @doc since: "0.7.0" + @spec get_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: + {:ok, AutoModerationRule.t()} | Api.error() + def get_auto_moderation_rule(guild_id, rule_id) do + Api.request(:get, Constants.guild_auto_moderation_rule(guild_id, rule_id)) + |> Api.handle_request_with_decode({:struct, AutoModerationRule}) + end + + @doc """ + Modify an auto-moderation rule for a guild. + + Takes the same options as `create_auto_moderation_rule/2`, however all fields are optional. + """ + @doc since: "0.7.0" + @spec modify_auto_moderation_rule(Guild.id(), AutoModerationRule.id(), Api.options()) :: + {:ok, AutoModerationRule.t()} | Api.error() + def modify_auto_moderation_rule(guild_id, rule_id, options) when is_list(options), + do: modify_auto_moderation_rule(guild_id, rule_id, Map.new(options)) + + def modify_auto_moderation_rule(guild_id, rule_id, options) do + Api.request(:patch, Constants.guild_auto_moderation_rule(guild_id, rule_id), options) + |> Api.handle_request_with_decode({:struct, AutoModerationRule}) + end +end From 64ccea58debb25d4694f512fbccde1e5ef395e20 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 22 Oct 2024 20:47:16 -0700 Subject: [PATCH 08/42] Add `Nostrum.Api.Channel` module and functions --- lib/nostrum/api/channel.ex | 411 +++++++++++++++++++++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 lib/nostrum/api/channel.ex diff --git a/lib/nostrum/api/channel.ex b/lib/nostrum/api/channel.ex new file mode 100644 index 000000000..9fbcfcbf8 --- /dev/null +++ b/lib/nostrum/api/channel.ex @@ -0,0 +1,411 @@ +defmodule Nostrum.Api.Channel do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Snowflake + alias Nostrum.Struct.Channel + alias Nostrum.Struct.Guild + alias Nostrum.Struct.Message + alias Nostrum.Struct.Webhook + alias Nostrum.Struct.Guild.AuditLogEntry + + import Nostrum.Snowflake, only: [is_snowflake: 1] + + @doc ~S""" + Pins a message in a channel. + + This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and + 'MANAGE_MESSAGES' permissions. It fires the + `t:Nostrum.Consumer.message_update/0` and + `t:Nostrum.Consumer.channel_pins_update/0` events. + + If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.pin_message(43189401384091, 18743893102394) + ``` + """ + @spec pin_message(Channel.id(), Message.id()) :: Api.error() | {:ok} + def pin_message(channel_id, message_id) + when is_snowflake(channel_id) and is_snowflake(message_id) do + Api.request(:put, Constants.channel_pin(channel_id, message_id)) + end + + @doc ~S""" + Retrieves all pinned messages from a channel. + + This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions. + + If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Channel.get_pinned_messages(43189401384091) + ``` + """ + @spec get_pinned_messages(Channel.id()) :: Api.error() | {:ok, [Message.t()]} + def get_pinned_messages(channel_id) when is_snowflake(channel_id) do + Api.request(:get, Constants.channel_pins(channel_id)) + |> Api.handle_request_with_decode({:list, {:struct, Message}}) + end + + @doc """ + Unpins a message in a channel. + + This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and + 'MANAGE_MESSAGES' permissions. It fires the + `t:Nostrum.Consumer.message_update/0` and + `t:Nostrum.Consumer.channel_pins_update/0` events. + + Returns `{:ok}` if successful. `error` otherwise. + """ + @spec unpin_message(Channel.id(), Message.id()) :: Api.error() | {:ok} + def unpin_message(channel_id, message_id) + when is_snowflake(channel_id) and is_snowflake(message_id) do + Api.request(:delete, Constants.channel_pin(channel_id, message_id)) + end + + @doc """ + Deletes multiple messages from a channel. + + `messages` is a list of `Nostrum.Struct.Message.id` that you wish to delete. + When given more than 100 messages, this function will chunk the given message + list into blocks of 100 and send them off to the API. It will stop deleting + on the first error that occurs. Keep in mind that deleting thousands of + messages will take a pretty long time and it may be proper to just delete + the channel you want to bulk delete in and recreate it. + + This method can only delete messages sent within the last two weeks. + `Filter` is an optional parameter that specifies whether messages sent over + two weeks ago should be filtered out; defaults to `true`. + """ + @spec bulk_delete_messages(Channel.id(), [Message.id()], boolean) :: Api.error() | {:ok} + def bulk_delete_messages(channel_id, messages, filter \\ true) + + def bulk_delete_messages(channel_id, messages, false), + do: send_chunked_delete(messages, channel_id) + + def bulk_delete_messages(channel_id, messages, true) do + snowflake_two_weeks_ago = + DateTime.utc_now() + |> DateTime.to_unix() + # 60 seconds * 60 * 24 * 14 = 14 days / 2 weeks + |> Kernel.-(60 * 60 * 24 * 14) + |> DateTime.from_unix!() + |> Snowflake.from_datetime!() + + messages + |> Stream.filter(&(&1 > snowflake_two_weeks_ago)) + |> send_chunked_delete(channel_id) + end + + @spec send_chunked_delete( + [Message.id()] | Enum.t(), + Snowflake.t() + ) :: Api.error() | {:ok} + defp send_chunked_delete(messages, channel_id) do + messages + |> Stream.chunk_every(100) + |> Stream.map(fn message_chunk -> + Api.request( + :post, + Constants.channel_bulk_delete(channel_id), + %{messages: message_chunk} + ) + end) + |> Enum.find({:ok}, &match?({:error, _}, &1)) + end + + @doc """ + Creates a channel for a guild. + + This endpoint requires the `MANAGE_CHANNELS` permission. It fires a + `t:Nostrum.Consumer.channel_create/0` event. + + If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - channel name (2-100 characters) + * `:type` (integer) - the type of channel (See `Nostrum.Struct.Channel`) + * `:topic` (string) - channel topic (0-1024 characters) + * `:bitrate` (integer) - the bitrate (in bits) of the voice channel (voice only) + * `:user_limit` (integer) - the user limit of the voice channel (voice only) + * `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) - + the channel's permission overwrites + * `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) - id of the parent category for a channel + * `:nsfw` (boolean) - if the channel is nsfw + + `:name` is always required. + + ## Examples + + ```elixir + Nostrum.Api.Channel.create(81384788765712384, name: "elixir-nostrum", topic: "craig's domain") + {:ok, %Nostrum.Struct.Channel{guild_id: 81384788765712384}} + ``` + """ + @spec create(Guild.id(), Api.options()) :: Api.error() | {:ok, Channel.guild_channel()} + def create(guild_id, options) + + def create(guild_id, options) when is_list(options), + do: create(guild_id, Map.new(options)) + + def create(guild_id, %{} = options) when is_snowflake(guild_id) do + Api.request(:post, Constants.guild_channels(guild_id), options) + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @doc ~S""" + Gets a channel. + + If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Channel.get(381889573426429952) + {:ok, %Nostrum.Struct.Channel{id: 381889573426429952}} + ``` + """ + @spec get(Channel.id()) :: Api.error() | {:ok, Channel.t()} + def get(channel_id) when is_snowflake(channel_id) do + Api.request(:get, Constants.channel(channel_id)) + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @doc ~S""" + Modifies a channel's settings. + + An optional `reason` can be given for the guild audit log. + + If a `t:Nostrum.Struct.Channel.guild_channel/0` is being modified, this + endpoint requires the `MANAGE_CHANNEL` permission. It fires a + `t:Nostrum.Consumer.channel_update/0` event. If a + `t:Nostrum.Struct.Channel.guild_category_channel/0` is being modified, then this + endpoint fires multiple `t:Nostrum.Consumer.channel_update/0` events. + + If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - 2-100 character channel name + * `:position` (integer) - the position of the channel in the left-hand listing + * `:topic` (string) (`t:Nostrum.Struct.Channel.text_channel/0` only) - + 0-1024 character channel topic + * `:nsfw` (boolean) (`t:Nostrum.Struct.Channel.text_channel/0` only) - + if the channel is nsfw + * `:bitrate` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - + the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers) + * `:user_limit` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - + the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit + * `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) - + channel or category-specific permissions + * `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) (`t:Nostrum.Struct.Channel.guild_channel/0` only) - + id of the new parent category for a channel + + ## Examples + + ```elixir + Nostrum.Api.Channel.modify(41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion") + {:ok, %Nostrum.Struct.Channel{id: 41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion"}} + + Nostrum.Api.Channel.modify(41771983423143933) + {:ok, %Nostrum.Struct.Channel{id: 41771983423143933}} + ``` + """ + @spec modify(Channel.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Channel.t()} + def modify(channel_id, options, reason \\ nil) + + def modify(channel_id, options, reason) when is_list(options), + do: modify(channel_id, Map.new(options), reason) + + def modify(channel_id, %{} = options, reason) when is_snowflake(channel_id) do + %{ + method: :patch, + route: Constants.channel(channel_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @doc ~S""" + Deletes a channel. + + An optional `reason` can be provided for the guild audit log. + + If deleting a `t:Nostrum.Struct.Channel.guild_channel/0`, this endpoint requires + the `MANAGE_CHANNELS` permission. It fires a + `t:Nostrum.Consumer.channel_delete/0`. If a `t:Nostrum.Struct.Channel.guild_category_channel/0` + is deleted, then a `t:Nostrum.Consumer.channel_update/0` event will fire + for each channel under the category. + + If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Channel.delete(421533712753360896) + {:ok, %Nostrum.Struct.Channel{id: 421533712753360896}} + ``` + """ + @spec delete(Channel.id(), AuditLogEntry.reason()) :: Api.error() | {:ok, Channel.t()} + def delete(channel_id, reason \\ nil) when is_snowflake(channel_id) do + %{ + method: :delete, + route: Constants.channel(channel_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @doc """ + Edit the permission overwrites for a user or role. + + Role or user to overwrite is specified by `overwrite_id`. + + `permission_info` is a map with the following keys: + * `type` - Required; `member` if editing a user, `role` if editing a role. + * `allow` - Bitwise value of allowed permissions. + * `deny` - Bitwise value of denied permissions. + * `type` - `member` if editing a user, `role` if editing a role. + + An optional `reason` can be provided for the audit log. + + `allow` and `deny` are defaulted to `0`, meaning that even if you don't + specify them, they will override their respective former values in an + existing overwrite. + """ + @spec edit_permissions( + Channel.id(), + integer, + %{ + required(:type) => String.t(), + optional(:allow) => integer, + optional(:deny) => integer + }, + AuditLogEntry.reason() + ) :: Api.error() | {:ok} + def edit_permissions(channel_id, overwrite_id, permission_info, reason \\ nil) do + Api.request(%{ + method: :put, + route: Constants.channel_permission(channel_id, overwrite_id), + body: permission_info, + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc """ + Delete a channel permission for a user or role. + + Role or user overwrite to delete is specified by `channel_id` and `overwrite_id`. + An optional `reason` can be given for the audit log. + """ + @spec delete_permissions(Channel.id(), integer, AuditLogEntry.reason()) :: Api.error() | {:ok} + def delete_permissions(channel_id, overwrite_id, reason \\ nil) do + Api.request(%{ + method: :delete, + route: Constants.channel_permission(channel_id, overwrite_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc ~S""" + Retrieves a channel's messages around a `locator` up to a `limit`. + + This endpoint requires the 'VIEW_CHANNEL' permission. If the current user + is missing the 'READ_MESSAGE_HISTORY' permission, then this function will + return no messages. + + If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Channel.get_messages(43189401384091, 5, {:before, 130230401384}) + ``` + """ + @spec get_messages(Channel.id(), Api.limit(), Api.locator()) :: + Api.error() | {:ok, [Message.t()]} + def get_messages(channel_id, limit, locator \\ {}) when is_snowflake(channel_id) do + get_messages_sync(channel_id, limit, [], locator) + end + + defp get_messages_sync(channel_id, limit, messages, locator) when limit <= 100 do + case get_messages_call(channel_id, limit, locator) do + {:ok, new_messages} -> {:ok, messages ++ new_messages} + other -> other + end + end + + defp get_messages_sync(channel_id, limit, messages, locator) do + case get_messages_call(channel_id, 100, locator) do + {:error, message} -> + {:error, message} + + {:ok, []} -> + {:ok, messages} + + {:ok, new_messages} -> + new_limit = get_new_limit(limit, length(new_messages)) + new_locator = get_new_locator(locator, List.last(new_messages)) + get_messages_sync(channel_id, new_limit, messages ++ new_messages, new_locator) + end + end + + defp get_new_locator({}, last_message), do: {:before, last_message.id} + defp get_new_locator(locator, last_message), do: put_elem(locator, 1, last_message.id) + + defp get_new_limit(:infinity, _new_message_count), do: :infinity + defp get_new_limit(limit, message_count), do: limit - message_count + + # We're decoding the response at each call to catch any errors + @doc false + def get_messages_call(channel_id, limit, locator) do + qs_params = + case locator do + {} -> [{:limit, limit}] + non_empty_locator -> [{:limit, limit}, non_empty_locator] + end + + Api.request(:get, Constants.channel_messages(channel_id), "", qs_params) + |> Api.handle_request_with_decode({:list, {:struct, Message}}) + end + + @doc """ + Gets a list of webhooks for a channel. + + ## Parameters + - `channel_id` - Channel to get webhooks for. + """ + @spec get_webhooks(Channel.id()) :: Api.error() | {:ok, [Webhook.t()]} + def get_webhooks(channel_id) do + Api.request(:get, Constants.webhooks_channel(channel_id)) + |> Api.handle_request_with_decode() + end + + @doc """ + Triggers the typing indicator. + + Triggers the typing indicator in the channel specified by `channel_id`. + The typing indicator lasts for about 8 seconds and then automatically stops. + + Returns `{:ok}` if successful. `error` otherwise. + """ + @spec start_typing(integer) :: Api.error() | {:ok} + def start_typing(channel_id) do + Api.request(:post, Constants.channel_typing(channel_id)) + end +end From 955c3616e3cc01cff7d5d4710a14c1b018fb132c Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 22 Oct 2024 20:47:50 -0700 Subject: [PATCH 09/42] Adjust Api Ratelimited for base rename --- lib/nostrum/api/ratelimiter.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nostrum/api/ratelimiter.ex b/lib/nostrum/api/ratelimiter.ex index b193c8c34..215160dc3 100644 --- a/lib/nostrum/api/ratelimiter.ex +++ b/lib/nostrum/api/ratelimiter.ex @@ -197,7 +197,7 @@ defmodule Nostrum.Api.Ratelimiter do @behaviour :gen_statem - alias Nostrum.Api.Base + alias Nostrum.Api.Adapter alias Nostrum.Api.RatelimiterGroup alias Nostrum.Constants alias Nostrum.Error.ApiError @@ -296,7 +296,7 @@ defmodule Nostrum.Api.Ratelimiter do }, conn: pid() | nil, remaining_in_window: non_neg_integer(), - wrapped_token: Base.wrapped_token() + wrapped_token: Adapter.wrapped_token() } @doc """ @@ -471,7 +471,7 @@ defmodule Nostrum.Api.Ratelimiter do ) when remaining_for_user > 0 and is_map_key(outstanding, bucket) do stream = - Base.request( + Adapter.request( conn, request.method, request.route, From 34874aaf5afe1472a3653e51ecdc59aa25550a91 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 22 Oct 2024 20:47:59 -0700 Subject: [PATCH 10/42] Remove unused alias --- lib/nostrum/voice/payload.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/nostrum/voice/payload.ex b/lib/nostrum/voice/payload.ex index 9a4ee92e4..5e915642a 100644 --- a/lib/nostrum/voice/payload.ex +++ b/lib/nostrum/voice/payload.ex @@ -5,7 +5,6 @@ defmodule Nostrum.Voice.Payload do alias Nostrum.Constants alias Nostrum.Struct.VoiceState alias Nostrum.Struct.VoiceWSState - alias Nostrum.Voice.Crypto require Logger From e32f4dfc8652ae480074f113ce5a8dd3de870fdd Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 22 Oct 2024 20:49:28 -0700 Subject: [PATCH 11/42] WIP begin delegating `Nostrum.Api` function calls to submodules Also begin working towards a sane pattern for error returning --- lib/nostrum/api.ex | 287 +++++++++++---------------------------- lib/nostrum/api/guild.ex | 55 ++++++++ 2 files changed, 136 insertions(+), 206 deletions(-) create mode 100644 lib/nostrum/api/guild.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index f06833234..db2c8d8de 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -69,6 +69,41 @@ defmodule Nostrum.Api do alias Nostrum.Struct.Guild.{AuditLog, AuditLogEntry, Member, Role, ScheduledEvent} alias Nostrum.Shard.{Session, Supervisor} + defguardp has_files(args) when is_map_key(args, :files) or is_map_key(args, :file) + + def handle_request_with_decode(response) + def handle_request_with_decode({:ok, body}), do: {:ok, Jason.decode!(body, keys: :atoms)} + def handle_request_with_decode({:error, _} = error), do: error + + def handle_request_with_decode(response, type) + # add_guild_member/3 can return both a 201 and a 204 + def handle_request_with_decode({:ok}, _type), do: {:ok} + def handle_request_with_decode({:error, _} = error, _type), do: error + + def handle_request_with_decode({:ok, body}, type) do + convert = + body + |> Jason.decode!(keys: :atoms) + |> Util.cast(type) + + {:ok, convert} + end + + def handle_request_with_decode!(response) + def handle_request_with_decode!({:ok, body}), do: Jason.decode!(body, keys: :atoms) + def handle_request_with_decode!({:error, error}), do: raise(error) + + def handle_request_with_decode!(response, type) + # add_guild_member/3 can return both a 201 and a 204 + def handle_request_with_decode!({:ok}, _type), do: {:ok} + def handle_request_with_decode!({:error, error}, _type), do: raise(error) + + def handle_request_with_decode!({:ok, body}, type) do + body + |> Jason.decode!(keys: :atoms) + |> Util.cast(type) + end + @typedoc """ Represents a failed response from the API. @@ -154,8 +189,6 @@ defmodule Nostrum.Api do @typedoc since: "0.7.0" @type allowed_mentions :: allowed_mention | [allowed_mention] - defguardp has_files(args) when is_map_key(args, :files) or is_map_key(args, :file) - @doc """ Updates the status of the bot for a certain shard. @@ -687,8 +720,7 @@ defmodule Nostrum.Api do """ @spec get_channel(Channel.id()) :: error | {:ok, Channel.t()} def get_channel(channel_id) when is_snowflake(channel_id) do - request(:get, Constants.channel(channel_id)) - |> handle_request_with_decode({:struct, Channel}) + Nostrum.Api.Channel.get(channel_id) end @doc ~S""" @@ -742,21 +774,8 @@ defmodule Nostrum.Api do """ @spec modify_channel(Channel.id(), options, AuditLogEntry.reason()) :: error | {:ok, Channel.t()} - def modify_channel(channel_id, options, reason \\ nil) - - def modify_channel(channel_id, options, reason) when is_list(options), - do: modify_channel(channel_id, Map.new(options), reason) - - def modify_channel(channel_id, %{} = options, reason) when is_snowflake(channel_id) do - %{ - method: :patch, - route: Constants.channel(channel_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - } - |> request - |> handle_request_with_decode({:struct, Channel}) + def modify_channel(channel_id, options, reason \\ nil) do + Nostrum.Api.Channel.modify(channel_id, options, reason) end @doc ~S""" @@ -790,15 +809,7 @@ defmodule Nostrum.Api do """ @spec delete_channel(Channel.id(), AuditLogEntry.reason()) :: error | {:ok, Channel.t()} def delete_channel(channel_id, reason \\ nil) when is_snowflake(channel_id) do - %{ - method: :delete, - route: Constants.channel(channel_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:struct, Channel}) + Nostrum.Api.Channel.delete(channel_id, reason) end @doc ~S""" @@ -827,48 +838,7 @@ defmodule Nostrum.Api do """ @spec get_channel_messages(Channel.id(), limit, locator) :: error | {:ok, [Message.t()]} def get_channel_messages(channel_id, limit, locator \\ {}) when is_snowflake(channel_id) do - get_messages_sync(channel_id, limit, [], locator) - end - - defp get_messages_sync(channel_id, limit, messages, locator) when limit <= 100 do - case get_channel_messages_call(channel_id, limit, locator) do - {:ok, new_messages} -> {:ok, messages ++ new_messages} - other -> other - end - end - - defp get_messages_sync(channel_id, limit, messages, locator) do - case get_channel_messages_call(channel_id, 100, locator) do - {:error, message} -> - {:error, message} - - {:ok, []} -> - {:ok, messages} - - {:ok, new_messages} -> - new_limit = get_new_limit(limit, length(new_messages)) - new_locator = get_new_locator(locator, List.last(new_messages)) - get_messages_sync(channel_id, new_limit, messages ++ new_messages, new_locator) - end - end - - defp get_new_locator({}, last_message), do: {:before, last_message.id} - defp get_new_locator(locator, last_message), do: put_elem(locator, 1, last_message.id) - - defp get_new_limit(:infinity, _new_message_count), do: :infinity - defp get_new_limit(limit, message_count), do: limit - message_count - - # We're decoding the response at each call to catch any errors - @doc false - def get_channel_messages_call(channel_id, limit, locator) do - qs_params = - case locator do - {} -> [{:limit, limit}] - non_empty_locator -> [{:limit, limit}, non_empty_locator] - end - - request(:get, Constants.channel_messages(channel_id), "", qs_params) - |> handle_request_with_decode({:list, {:struct, Message}}) + Nostrum.Api.Channel.get_messages(channel_id, limit, locator) end @doc ~S""" @@ -924,42 +894,8 @@ defmodule Nostrum.Api do two weeks ago should be filtered out; defaults to `true`. """ @spec bulk_delete_messages(integer, [Nostrum.Struct.Message.id()], boolean) :: error | {:ok} - def bulk_delete_messages(channel_id, messages, filter \\ true) - - def bulk_delete_messages(channel_id, messages, false), - do: send_chunked_delete(messages, channel_id) - - def bulk_delete_messages(channel_id, messages, true) do - alias Nostrum.Snowflake - - snowflake_two_weeks_ago = - DateTime.utc_now() - |> DateTime.to_unix() - # 60 seconds * 60 * 24 * 14 = 14 days / 2 weeks - |> Kernel.-(60 * 60 * 24 * 14) - |> DateTime.from_unix!() - |> Snowflake.from_datetime!() - - messages - |> Stream.filter(&(&1 > snowflake_two_weeks_ago)) - |> send_chunked_delete(channel_id) - end - - @spec send_chunked_delete( - [Nostrum.Struct.Message.id()] | Enum.t(), - Nostrum.Snowflake.t() - ) :: error | {:ok} - defp send_chunked_delete(messages, channel_id) do - messages - |> Stream.chunk_every(100) - |> Stream.map(fn message_chunk -> - request( - :post, - Constants.channel_bulk_delete(channel_id), - %{messages: message_chunk} - ) - end) - |> Enum.find({:ok}, &match?({:error, _}, &1)) + def bulk_delete_messages(channel_id, messages, filter) do + Nostrum.Api.Channel.bulk_delete_messages(channel_id, messages, filter) end @doc """ @@ -1000,13 +936,7 @@ defmodule Nostrum.Api do AuditLogEntry.reason() ) :: error | {:ok} def edit_channel_permissions(channel_id, overwrite_id, permission_info, reason \\ nil) do - request(%{ - method: :put, - route: Constants.channel_permission(channel_id, overwrite_id), - body: permission_info, - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Channel.edit_permissions(channel_id, overwrite_id, permission_info, reason) end @doc """ @@ -1035,13 +965,7 @@ defmodule Nostrum.Api do """ @spec delete_channel_permissions(integer, integer, AuditLogEntry.reason()) :: error | {:ok} def delete_channel_permissions(channel_id, overwrite_id, reason \\ nil) do - request(%{ - method: :delete, - route: Constants.channel_permission(channel_id, overwrite_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Channel.delete_permissions(channel_id, overwrite_id, reason) end @doc ~S""" @@ -1144,7 +1068,7 @@ defmodule Nostrum.Api do """ @spec start_typing(integer) :: error | {:ok} def start_typing(channel_id) do - request(:post, Constants.channel_typing(channel_id)) + Nostrum.Api.Channel.start_typing(channel_id) end @doc """ @@ -1171,8 +1095,7 @@ defmodule Nostrum.Api do """ @spec get_pinned_messages(Channel.id()) :: error | {:ok, [Message.t()]} def get_pinned_messages(channel_id) when is_snowflake(channel_id) do - request(:get, Constants.channel_pins(channel_id)) - |> handle_request_with_decode({:list, {:struct, Message}}) + Nostrum.Api.Channel.get_pinned_messages(channel_id) end @doc ~S""" @@ -1203,7 +1126,7 @@ defmodule Nostrum.Api do @spec add_pinned_channel_message(Channel.id(), Message.id()) :: error | {:ok} def add_pinned_channel_message(channel_id, message_id) when is_snowflake(channel_id) and is_snowflake(message_id) do - request(:put, Constants.channel_pin(channel_id, message_id)) + Nostrum.Api.Channel.pin_message(channel_id, message_id) end @doc ~S""" @@ -1228,7 +1151,7 @@ defmodule Nostrum.Api do @spec delete_pinned_channel_message(Channel.id(), Message.id()) :: error | {:ok} def delete_pinned_channel_message(channel_id, message_id) when is_snowflake(channel_id) and is_snowflake(message_id) do - request(:delete, Constants.channel_pin(channel_id, message_id)) + Nostrum.Api.Channel.unpin_message(channel_id, message_id) end @doc ~S""" @@ -1768,14 +1691,8 @@ defmodule Nostrum.Api do ``` """ @spec create_guild_channel(Guild.id(), options) :: error | {:ok, Channel.guild_channel()} - def create_guild_channel(guild_id, options) - - def create_guild_channel(guild_id, options) when is_list(options), - do: create_guild_channel(guild_id, Map.new(options)) - - def create_guild_channel(guild_id, %{} = options) when is_snowflake(guild_id) do - request(:post, Constants.guild_channels(guild_id), options) - |> handle_request_with_decode({:struct, Channel}) + def create_guild_channel(guild_id, options) do + Nostrum.Api.Channel.create(guild_id, options) end @doc ~S""" @@ -1917,15 +1834,8 @@ defmodule Nostrum.Api do ``` """ @spec add_guild_member(Guild.id(), User.id(), options) :: error | {:ok, Member.t()} | {:ok} - def add_guild_member(guild_id, user_id, options) - - def add_guild_member(guild_id, user_id, options) when is_list(options), - do: add_guild_member(guild_id, user_id, Map.new(options)) - - def add_guild_member(guild_id, user_id, %{} = options) - when is_snowflake(guild_id) and is_snowflake(user_id) do - request(:put, Constants.guild_member(guild_id, user_id), options) - |> handle_request_with_decode({:struct, Member}) + def add_guild_member(guild_id, user_id, options) do + Nostrum.Api.Guild.add_member(guild_id, user_id, options) end @doc """ @@ -3064,8 +2974,7 @@ defmodule Nostrum.Api do """ @spec get_channel_webhooks(Channel.id()) :: error | {:ok, [Nostrum.Struct.Webhook.t()]} def get_channel_webhooks(channel_id) do - request(:get, Constants.webhooks_channel(channel_id)) - |> handle_request_with_decode + Nostrum.Api.Channel.get_webhooks(channel_id) end @doc """ @@ -3387,8 +3296,7 @@ defmodule Nostrum.Api do @spec get_global_application_commands() :: {:ok, [map()]} | error @spec get_global_application_commands(User.id()) :: {:ok, [map()]} | error def get_global_application_commands(application_id \\ Me.get().id) do - request(:get, Constants.global_application_commands(application_id)) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.get_global_commands(application_id) end @doc """ @@ -3421,8 +3329,7 @@ defmodule Nostrum.Api do @spec create_global_application_command(User.id(), ApplicationCommand.application_command_map()) :: {:ok, map()} | error def create_global_application_command(application_id \\ Me.get().id, command) do - request(:post, Constants.global_application_commands(application_id), command) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.create_global_command(application_id, command) end @doc """ @@ -3454,8 +3361,7 @@ defmodule Nostrum.Api do command_id, command ) do - request(:patch, Constants.global_application_command(application_id, command_id), command) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.edit_global_command(application_id, command_id, command) end @doc """ @@ -3469,7 +3375,7 @@ defmodule Nostrum.Api do @spec delete_global_application_command(Snowflake.t()) :: {:ok} | error @spec delete_global_application_command(User.id(), Snowflake.t()) :: {:ok} | error def delete_global_application_command(application_id \\ Me.get().id, command_id) do - request(:delete, Constants.global_application_command(application_id, command_id)) + Nostrum.Api.ApplicationCommand.delete_global_command(application_id, command_id) end @doc """ @@ -3499,8 +3405,7 @@ defmodule Nostrum.Api do ApplicationCommand.application_command_map() ]) :: {:ok, [map()]} | error def bulk_overwrite_global_application_commands(application_id \\ Me.get().id, commands) do - request(:put, Constants.global_application_commands(application_id), commands) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.bulk_overwrite_global_commands(application_id, commands) end @doc """ @@ -3519,8 +3424,7 @@ defmodule Nostrum.Api do @spec get_guild_application_commands(Guild.id()) :: {:ok, [map()]} | error @spec get_guild_application_commands(User.id(), Guild.id()) :: {:ok, [map()]} | error def get_guild_application_commands(application_id \\ Me.get().id, guild_id) do - request(:get, Constants.guild_application_commands(application_id, guild_id)) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.get_guild_commands(application_id, guild_id) end @doc """ @@ -3550,8 +3454,7 @@ defmodule Nostrum.Api do guild_id, command ) do - request(:post, Constants.guild_application_commands(application_id, guild_id), command) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.create_guild_command(application_id, guild_id, command) end @doc """ @@ -3588,12 +3491,12 @@ defmodule Nostrum.Api do command_id, command ) do - request( - :patch, - Constants.guild_application_command(application_id, guild_id, command_id), + Nostrum.Api.ApplicationCommand.edit_guild_command( + application_id, + guild_id, + command_id, command ) - |> handle_request_with_decode end @doc """ @@ -3612,7 +3515,7 @@ defmodule Nostrum.Api do guild_id, command_id ) do - request(:delete, Constants.guild_application_command(application_id, guild_id, command_id)) + Nostrum.Api.ApplicationCommand.delete_guild_command(application_id, guild_id, command_id) end @doc """ @@ -3917,11 +3820,7 @@ defmodule Nostrum.Api do guild_id, command_id ) do - request( - :get, - Constants.guild_application_command_permissions(application_id, guild_id, command_id) - ) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.get_command_permissions(application_id, guild_id, command_id) end @doc """ @@ -3952,14 +3851,12 @@ defmodule Nostrum.Api do command_id, permissions ) do - request( - :put, - Constants.guild_application_command_permissions(application_id, guild_id, command_id), - %{ - permissions: permissions - } + Nostrum.Api.ApplicationCommand.edit_command_permissions( + application_id, + guild_id, + command_id, + permissions ) - |> handle_request_with_decode end @doc """ @@ -3995,12 +3892,11 @@ defmodule Nostrum.Api do guild_id, permissions ) do - request( - :put, - Constants.guild_application_command_permissions(application_id, guild_id), + Nostrum.Api.ApplicationCommand.batch_edit_permissions( + application_id, + guild_id, permissions ) - |> handle_request_with_decode end @type thread_with_message_params :: %{ @@ -4345,8 +4241,7 @@ defmodule Nostrum.Api do @spec get_guild_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: {:ok, AutoModerationRule.t()} | error def get_guild_auto_moderation_rule(guild_id, rule_id) do - request(:get, Constants.guild_auto_moderation_rule(guild_id, rule_id)) - |> handle_request_with_decode({:struct, AutoModerationRule}) + Nostrum.Api.AutoModeration.get_auto_moderation_rule(guild_id, rule_id) end @doc """ @@ -4373,8 +4268,7 @@ defmodule Nostrum.Api do do: create_guild_auto_moderation_rule(guild_id, Map.new(options)) def create_guild_auto_moderation_rule(guild_id, options) do - request(:post, Constants.guild_auto_moderation_rule(guild_id), options) - |> handle_request_with_decode({:struct, AutoModerationRule}) + Nostrum.Api.AutoModeration.create_auto_moderation_rule(guild_id, options) end @doc """ @@ -4389,8 +4283,7 @@ defmodule Nostrum.Api do do: modify_guild_auto_moderation_rule(guild_id, rule_id, Map.new(options)) def modify_guild_auto_moderation_rule(guild_id, rule_id, options) do - request(:patch, Constants.guild_auto_moderation_rule(guild_id, rule_id), options) - |> handle_request_with_decode({:struct, AutoModerationRule}) + Nostrum.Api.AutoModeration.modify_auto_moderation_rule(guild_id, rule_id, options) end @doc """ @@ -4399,20 +4292,20 @@ defmodule Nostrum.Api do @doc since: "0.7.0" @spec delete_guild_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: {:ok} | error def delete_guild_auto_moderation_rule(guild_id, rule_id) do - request(:delete, Constants.guild_auto_moderation_rule(guild_id, rule_id)) + Nostrum.Api.AutoModeration.delete_auto_moderation_rule(guild_id, rule_id) end @spec maybe_add_reason(String.t() | nil) :: list() - defp maybe_add_reason(reason) do + def maybe_add_reason(reason) do maybe_add_reason(reason, [{"content-type", "application/json"}]) end @spec maybe_add_reason(String.t() | nil, list()) :: list() - defp maybe_add_reason(nil, headers) do + def maybe_add_reason(nil, headers) do headers end - defp maybe_add_reason(reason, headers) do + def maybe_add_reason(reason, headers) do [{"x-audit-log-reason", reason} | headers] end @@ -4499,24 +4392,6 @@ defmodule Nostrum.Api do end end - defp handle_request_with_decode(response) - defp handle_request_with_decode({:ok, body}), do: {:ok, Jason.decode!(body, keys: :atoms)} - defp handle_request_with_decode({:error, _} = error), do: error - - defp handle_request_with_decode(response, type) - # add_guild_member/3 can return both a 201 and a 204 - defp handle_request_with_decode({:ok}, _type), do: {:ok} - defp handle_request_with_decode({:error, _} = error, _type), do: error - - defp handle_request_with_decode({:ok, body}, type) do - convert = - body - |> Jason.decode!(keys: :atoms) - |> Util.cast(type) - - {:ok, convert} - end - defp prepare_allowed_mentions(options) do with raw_options when raw_options != :all <- Map.get(options, :allowed_mentions, :all), allowed_mentions when is_map(allowed_mentions) <- parse_allowed_mentions(raw_options) do diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex new file mode 100644 index 000000000..231df3206 --- /dev/null +++ b/lib/nostrum/api/guild.ex @@ -0,0 +1,55 @@ +defmodule Nostrum.Api.Guild do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct + alias Nostrum.Struct.User + alias Nostrum.Struct.Guild.Member + + import Nostrum.Snowflake, only: [is_snowflake: 1] + + @doc ~S""" + Puts a user in a guild. + + This endpoint fires the `t:Nostrum.Consumer.guild_member_add/0` event. + It requires the `CREATE_INSTANT_INVITE` permission. Additionally, it + situationally requires the `MANAGE_NICKNAMES`, `MANAGE_ROLES`, + `MUTE_MEMBERS`, and `DEAFEN_MEMBERS` permissions. + + If successful, returns `{:ok, member}` or `{:ok}` if the user was already a member of the + guild. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:access_token` (string) - the user's oauth2 access token + * `:nick` (string) - value to set users nickname to + * `:roles` (list of `t:Nostrum.Struct.Guild.Role.id/0`) - array of role ids the member is assigned + * `:mute` (boolean) - if the user is muted + * `:deaf` (boolean) - if the user is deafened + + `:access_token` is always required. + + ## Examples + + ```elixir + Nostrum.Api.Guild.add_member( + 41771983423143937, + 18374719829378473, + access_token: "6qrZcUqja7812RVdnEKjpzOL4CvHBFG", + nick: "nostrum", + roles: [431849301, 913809431] + ) + ``` + """ + @spec add_member(Struct.Guild.id(), User.id(), Api.options()) :: + Api.error() | {:ok, Member.t()} | {:ok} + def add_member(guild_id, user_id, options) + + def add_member(guild_id, user_id, options) when is_list(options), + do: add_member(guild_id, user_id, Map.new(options)) + + def add_member(guild_id, user_id, %{} = options) + when is_snowflake(guild_id) and is_snowflake(user_id) do + Api.request(:put, Constants.guild_member(guild_id, user_id), options) + |> Api.handle_request_with_decode({:struct, Member}) + end +end From aeff17e40c1d06367acd63573e5b050249c3a957 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 08:17:36 -0700 Subject: [PATCH 12/42] Extract functions for Guild, Invite, MEssage, Poll, Role, ScheduledEvent, and Self --- lib/nostrum/api.ex | 490 ++++------------ lib/nostrum/api/application_command.ex | 26 +- lib/nostrum/api/auto_moderation.ex | 31 +- lib/nostrum/api/channel.ex | 274 ++++----- lib/nostrum/api/guild.ex | 752 ++++++++++++++++++++++++- lib/nostrum/api/invite.ex | 145 +++++ lib/nostrum/api/message.ex | 366 ++++++++++++ lib/nostrum/api/poll.ex | 47 ++ lib/nostrum/api/role.ex | 164 ++++++ lib/nostrum/api/scheduled_event.ex | 136 +++++ lib/nostrum/api/self.ex | 137 +++++ 11 files changed, 2021 insertions(+), 547 deletions(-) create mode 100644 lib/nostrum/api/invite.ex create mode 100644 lib/nostrum/api/message.ex create mode 100644 lib/nostrum/api/poll.ex create mode 100644 lib/nostrum/api/role.ex create mode 100644 lib/nostrum/api/scheduled_event.ex create mode 100644 lib/nostrum/api/self.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index db2c8d8de..cbd1ebe8a 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -43,6 +43,8 @@ defmodule Nostrum.Api do @crlf "\r\n" + require Logger + import Nostrum.Snowflake, only: [is_snowflake: 1] alias Nostrum.Api.Ratelimiter @@ -67,7 +69,6 @@ defmodule Nostrum.Api do } alias Nostrum.Struct.Guild.{AuditLog, AuditLogEntry, Member, Role, ScheduledEvent} - alias Nostrum.Shard.{Session, Supervisor} defguardp has_files(args) when is_map_key(args, :files) or is_map_key(args, :file) @@ -201,8 +202,7 @@ defmodule Nostrum.Api do """ @spec update_shard_status(pid, status, String.t(), integer, String.t() | nil) :: :ok def update_shard_status(pid, status, game, type \\ 0, stream \\ nil) do - Session.update_status(pid, to_string(status), game, stream, type) - :ok + Nostrum.Api.Self.update_shard_status(pid, status, game, type, stream) end @doc """ @@ -212,8 +212,7 @@ defmodule Nostrum.Api do """ @spec update_status(status, String.t(), integer, String.t() | nil) :: :ok def update_status(status, game, type \\ 0, stream \\ nil) do - _result = Supervisor.update_status(to_string(status), game, stream, type) - :ok + Nostrum.Api.Self.update_status(status, game, type, stream) end @doc """ @@ -227,7 +226,7 @@ defmodule Nostrum.Api do """ @spec update_voice_state(Guild.id(), Channel.id() | nil, boolean, boolean) :: no_return | :ok def update_voice_state(guild_id, channel_id, self_mute \\ false, self_deaf \\ false) do - Supervisor.update_voice_state(guild_id, channel_id, self_mute, self_deaf) + Nostrum.Api.Self.update_voice_state(guild_id, channel_id, self_mute, self_deaf) end @doc ~S""" @@ -305,22 +304,8 @@ defmodule Nostrum.Api do """ @spec create_message(Channel.id() | Message.t(), options | String.t()) :: error | {:ok, Message.t()} - def create_message(channel_id, options) - - def create_message(%Message{} = message, options), - do: create_message(message.channel_id, options) - - def create_message(channel_id, content) when is_binary(content), - do: create_message(channel_id, %{content: content}) - - def create_message(channel_id, options) when is_list(options), - do: create_message(channel_id, Map.new(options)) - - def create_message(channel_id, %{} = options) when is_snowflake(channel_id) do - options = prepare_allowed_mentions(options) |> combine_embeds() |> combine_files() - - request(:post, Constants.channel_messages(channel_id), options) - |> handle_request_with_decode({:struct, Message}) + def create_message(channel_id, options) do + Nostrum.Api.Message.create(channel_id, options) end @doc ~S""" @@ -374,20 +359,8 @@ defmodule Nostrum.Api do """ @spec edit_message(Channel.id(), Message.id(), options | String.t()) :: error | {:ok, Message.t()} - def edit_message(channel_id, message_id, options) - - def edit_message(channel_id, message_id, content) when is_binary(content), - do: edit_message(channel_id, message_id, %{content: content}) - - def edit_message(channel_id, message_id, options) when is_list(options), - do: edit_message(channel_id, message_id, Map.new(options)) - - def edit_message(channel_id, message_id, %{} = options) - when is_snowflake(channel_id) and is_snowflake(message_id) do - options = prepare_allowed_mentions(options) |> combine_embeds() |> combine_files() - - request(:patch, Constants.channel_message(channel_id, message_id), options) - |> handle_request_with_decode({:struct, Message}) + def edit_message(channel_id, message_id, options) do + Nostrum.Api.Message.edit(channel_id, message_id, options) end @doc ~S""" @@ -443,7 +416,7 @@ defmodule Nostrum.Api do @spec delete_message(Channel.id(), Message.id()) :: error | {:ok} def delete_message(channel_id, message_id) when is_snowflake(channel_id) and is_snowflake(message_id) do - request(:delete, Constants.channel_message(channel_id, message_id)) + Nostrum.Api.Message.delete(channel_id, message_id) end @doc ~S""" @@ -489,13 +462,8 @@ defmodule Nostrum.Api do For other emoji string examples, see `t:Nostrum.Struct.Emoji.api_name/0`. """ @spec create_reaction(Channel.id(), Message.id(), emoji) :: error | {:ok} - def create_reaction(channel_id, message_id, emoji) - - def create_reaction(channel_id, message_id, %Emoji{} = emoji), - do: create_reaction(channel_id, message_id, Emoji.api_name(emoji)) - - def create_reaction(channel_id, message_id, emoji_api_name) do - request(:put, Constants.channel_reaction_me(channel_id, message_id, emoji_api_name)) + def create_reaction(channel_id, message_id, emoji) do + Nostrum.Api.Message.react(channel_id, message_id, emoji) end @doc ~S""" @@ -524,7 +492,7 @@ defmodule Nostrum.Api do do: delete_own_reaction(channel_id, message_id, Emoji.api_name(emoji)) def delete_own_reaction(channel_id, message_id, emoji_api_name) do - request(:delete, Constants.channel_reaction_me(channel_id, message_id, emoji_api_name)) + Nostrum.Api.Message.unreact(channel_id, message_id, emoji_api_name) end @doc ~S""" @@ -553,7 +521,7 @@ defmodule Nostrum.Api do do: delete_user_reaction(channel_id, message_id, Emoji.api_name(emoji), user_id) def delete_user_reaction(channel_id, message_id, emoji_api_name, user_id) do - request(:delete, Constants.channel_reaction(channel_id, message_id, emoji_api_name, user_id)) + Nostrum.Api.Message.delete_user_reaction(channel_id, message_id, emoji_api_name, user_id) end @doc ~S""" @@ -581,10 +549,7 @@ defmodule Nostrum.Api do do: delete_reaction(channel_id, message_id, Emoji.api_name(emoji)) def delete_reaction(channel_id, message_id, emoji_api_name) do - request( - :delete, - Constants.channel_reactions_delete_emoji(channel_id, message_id, emoji_api_name) - ) + Nostrum.Api.Message.delete_emoji_reactions(channel_id, message_id, emoji_api_name) end @doc ~S""" @@ -615,13 +580,7 @@ defmodule Nostrum.Api do do: get_reactions(channel_id, message_id, Emoji.api_name(emoji), params) def get_reactions(channel_id, message_id, emoji_api_name, params) do - request( - :get, - Constants.channel_reactions_get(channel_id, message_id, emoji_api_name), - "", - params - ) - |> handle_request_with_decode({:list, {:struct, User}}) + Nostrum.Api.Message.reactions(channel_id, message_id, emoji_api_name, params) end @doc ~S""" @@ -643,7 +602,7 @@ defmodule Nostrum.Api do """ @spec delete_all_reactions(Channel.id(), Message.id()) :: error | {:ok} def delete_all_reactions(channel_id, message_id) do - request(:delete, Constants.channel_reactions_delete(channel_id, message_id)) + Nostrum.Api.Message.clear_reactions(channel_id, message_id) end @doc ~S""" @@ -667,14 +626,7 @@ defmodule Nostrum.Api do @spec get_poll_answer_voters(Channel.id(), Message.id(), Poll.Answer.answer_id()) :: error | {:ok, [User.t()]} def get_poll_answer_voters(channel_id, message_id, answer_id, params \\ []) do - result = - request(:get, Constants.poll_answer_voters(channel_id, message_id, answer_id), "", params) - |> handle_request_with_decode() - - case result do - {:ok, %{users: users}} -> {:ok, Util.cast(users, {:list, {:struct, User}})} - _ -> result - end + Nostrum.Api.Poll.answer_voters(channel_id, message_id, answer_id, params) end @doc ~S""" @@ -693,8 +645,7 @@ defmodule Nostrum.Api do """ @spec expire_poll(Channel.id(), Message.id()) :: error | {:ok, Message.t()} def expire_poll(channel_id, message_id) do - request(:post, Constants.poll_expire(channel_id, message_id)) - |> handle_request_with_decode({:struct, Message}) + Nostrum.Api.Poll.expire(channel_id, message_id) end @doc ~S""" @@ -838,7 +789,7 @@ defmodule Nostrum.Api do """ @spec get_channel_messages(Channel.id(), limit, locator) :: error | {:ok, [Message.t()]} def get_channel_messages(channel_id, limit, locator \\ {}) when is_snowflake(channel_id) do - Nostrum.Api.Channel.get_messages(channel_id, limit, locator) + Nostrum.Api.Channel.messages(channel_id, limit, locator) end @doc ~S""" @@ -866,8 +817,7 @@ defmodule Nostrum.Api do @spec get_channel_message(Channel.id(), Message.id()) :: error | {:ok, Message.t()} def get_channel_message(channel_id, message_id) when is_snowflake(channel_id) and is_snowflake(message_id) do - request(:get, Constants.channel_message(channel_id, message_id)) - |> handle_request_with_decode({:struct, Message}) + Nostrum.Api.Message.get(channel_id, message_id) end @doc ~S""" @@ -985,8 +935,7 @@ defmodule Nostrum.Api do """ @spec get_channel_invites(Channel.id()) :: error | {:ok, [Invite.detailed_invite()]} def get_channel_invites(channel_id) when is_snowflake(channel_id) do - request(:get, Constants.channel_invites(channel_id)) - |> handle_request_with_decode({:list, {:struct, Invite}}) + Nostrum.Api.Invite.channel_invites(channel_id) end @doc ~S""" @@ -1030,22 +979,8 @@ defmodule Nostrum.Api do """ @spec create_channel_invite(Channel.id(), options, AuditLogEntry.reason()) :: error | {:ok, Invite.detailed_invite()} - def create_channel_invite(channel_id, options \\ [], reason \\ nil) - - def create_channel_invite(channel_id, options, reason) when is_list(options), - do: create_channel_invite(channel_id, Map.new(options), reason) - - def create_channel_invite(channel_id, options, reason) - when is_snowflake(channel_id) and is_map(options) do - %{ - method: :post, - route: Constants.channel_invites(channel_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:struct, Invite}) + def create_channel_invite(channel_id, options \\ [], reason \\ nil) do + Nostrum.Api.Invite.create(channel_id, options, reason) end @doc ~S""" @@ -1095,7 +1030,7 @@ defmodule Nostrum.Api do """ @spec get_pinned_messages(Channel.id()) :: error | {:ok, [Message.t()]} def get_pinned_messages(channel_id) when is_snowflake(channel_id) do - Nostrum.Api.Channel.get_pinned_messages(channel_id) + Nostrum.Api.Channel.pinned_messages(channel_id) end @doc ~S""" @@ -1172,8 +1107,7 @@ defmodule Nostrum.Api do """ @spec list_guild_emojis(Guild.id()) :: error | {:ok, [Emoji.t()]} def list_guild_emojis(guild_id) do - request(:get, Constants.guild_emojis(guild_id)) - |> handle_request_with_decode({:list, {:struct, Emoji}}) + Nostrum.Api.Guild.emojis(guild_id) end @doc ~S""" @@ -1194,8 +1128,7 @@ defmodule Nostrum.Api do """ @spec get_guild_emoji(Guild.id(), Emoji.id()) :: error | {:ok, Emoji.t()} def get_guild_emoji(guild_id, emoji_id) do - request(:get, Constants.guild_emoji(guild_id, emoji_id)) - |> handle_request_with_decode({:struct, Emoji}) + Nostrum.Api.Guild.emoji(guild_id, emoji_id) end @doc ~S""" @@ -1236,21 +1169,8 @@ defmodule Nostrum.Api do """ @spec create_guild_emoji(Guild.id(), options, AuditLogEntry.reason()) :: error | {:ok, Emoji.t()} - def create_guild_emoji(guild_id, options, reason \\ nil) - - def create_guild_emoji(guild_id, options, reason) when is_list(options), - do: create_guild_emoji(guild_id, Map.new(options), reason) - - def create_guild_emoji(guild_id, %{} = options, reason) do - %{ - method: :post, - route: Constants.guild_emojis(guild_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:struct, Emoji}) + def create_guild_emoji(guild_id, options, reason \\ nil) do + Nostrum.Api.Guild.create_emoji(guild_id, options, reason) end @doc ~S""" @@ -1285,21 +1205,8 @@ defmodule Nostrum.Api do """ @spec modify_guild_emoji(Guild.id(), Emoji.id(), options, AuditLogEntry.reason()) :: error | {:ok, Emoji.t()} - def modify_guild_emoji(guild_id, emoji_id, options \\ %{}, reason \\ nil) - - def modify_guild_emoji(guild_id, emoji_id, options, reason) when is_list(options), - do: modify_guild_emoji(guild_id, emoji_id, Map.new(options), reason) - - def modify_guild_emoji(guild_id, emoji_id, %{} = options, reason) do - %{ - method: :patch, - route: Constants.guild_emoji(guild_id, emoji_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:struct, Emoji}) + def modify_guild_emoji(guild_id, emoji_id, options \\ %{}, reason \\ nil) do + Nostrum.Api.Guild.modify_emoji(guild_id, emoji_id, options, reason) end @doc ~S""" @@ -1323,15 +1230,9 @@ defmodule Nostrum.Api do If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. """ @spec delete_guild_emoji(Guild.id(), Emoji.id(), AuditLogEntry.reason()) :: error | {:ok} - def delete_guild_emoji(guild_id, emoji_id, reason \\ nil), - do: - request(%{ - method: :delete, - route: Constants.guild_emoji(guild_id, emoji_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + def delete_guild_emoji(guild_id, emoji_id, reason \\ nil) do + Nostrum.Api.Guild.delete_emoji(guild_id, emoji_id, reason) + end @doc ~S""" Same as `delete_guild_emoji/2`, but raises `Nostrum.Error.ApiError` in case of failure. @@ -1505,8 +1406,7 @@ defmodule Nostrum.Api do """ @spec get_guild_audit_log(Guild.id(), options) :: {:ok, AuditLog.t()} | error def get_guild_audit_log(guild_id, options \\ []) do - request(:get, Constants.guild_audit_logs(guild_id), "", options) - |> handle_request_with_decode({:struct, AuditLog}) + Nostrum.Api.Guild.audit_log(guild_id, options) end @doc ~S""" @@ -1523,8 +1423,7 @@ defmodule Nostrum.Api do """ @spec get_guild(Guild.id()) :: error | {:ok, Guild.rest_guild()} def get_guild(guild_id) when is_snowflake(guild_id) do - request(:get, Constants.guild(guild_id)) - |> handle_request_with_decode({:struct, Guild}) + Nostrum.Api.Guild.get(guild_id) end @doc """ @@ -1577,23 +1476,8 @@ defmodule Nostrum.Api do """ @spec modify_guild(Guild.id(), options, AuditLogEntry.reason()) :: error | {:ok, Guild.rest_guild()} - def modify_guild(guild_id, options \\ [], reason \\ nil) - - def modify_guild(guild_id, options, reason) when is_list(options), - do: modify_guild(guild_id, Map.new(options), reason) - - def modify_guild(guild_id, options, reason) when is_snowflake(guild_id) and is_map(options) do - options = Map.new(options) - - %{ - method: :patch, - route: Constants.guild(guild_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:struct, Guild}) + def modify_guild(guild_id, options \\ [], reason \\ nil) do + Nostrum.Api.Guild.modify(guild_id, options, reason) end @doc """ @@ -1622,7 +1506,7 @@ defmodule Nostrum.Api do """ @spec delete_guild(Guild.id()) :: error | {:ok} def delete_guild(guild_id) when is_snowflake(guild_id) do - request(:delete, Constants.guild(guild_id)) + Nostrum.Api.Guild.delete(guild_id) end @doc ~S""" @@ -1648,8 +1532,7 @@ defmodule Nostrum.Api do """ @spec get_guild_channels(Guild.id()) :: error | {:ok, [Channel.guild_channel()]} def get_guild_channels(guild_id) when is_snowflake(guild_id) do - request(:get, Constants.guild_channels(guild_id)) - |> handle_request_with_decode({:list, {:struct, Channel}}) + Nostrum.Api.Guild.channels(guild_id) end @doc ~S""" @@ -1725,7 +1608,7 @@ defmodule Nostrum.Api do error | {:ok} def modify_guild_channel_positions(guild_id, positions) when is_snowflake(guild_id) and is_list(positions) do - request(:patch, Constants.guild_channels(guild_id), positions) + Nostrum.Api.Guild.modify_channel_positions(guild_id, positions) end @doc ~S""" @@ -1751,8 +1634,7 @@ defmodule Nostrum.Api do """ @spec get_guild_member(Guild.id(), User.id()) :: error | {:ok, Member.t()} def get_guild_member(guild_id, user_id) when is_snowflake(guild_id) and is_snowflake(user_id) do - request(:get, Constants.guild_member(guild_id, user_id)) - |> handle_request_with_decode({:struct, Member}) + Nostrum.Api.Guild.member(guild_id, user_id) end @doc """ @@ -1781,14 +1663,8 @@ defmodule Nostrum.Api do ``` """ @spec list_guild_members(Guild.id(), options) :: error | {:ok, [Member.t()]} - def list_guild_members(guild_id, options \\ %{}) - - def list_guild_members(guild_id, options) when is_list(options), - do: list_guild_members(guild_id, Map.new(options)) - - def list_guild_members(guild_id, %{} = options) when is_snowflake(guild_id) do - request(:get, Constants.guild_members(guild_id), "", options) - |> handle_request_with_decode({:list, {:struct, Member}}) + def list_guild_members(guild_id, options \\ %{}) do + Nostrum.Api.Guild.members(guild_id, options) end @doc """ @@ -1876,25 +1752,8 @@ defmodule Nostrum.Api do """ @spec modify_guild_member(Guild.id(), User.id(), options, AuditLogEntry.reason()) :: error | {:ok, Member.t()} - def modify_guild_member(guild_id, user_id, options \\ %{}, reason \\ nil) - - def modify_guild_member(guild_id, user_id, options, reason) when is_list(options), - do: modify_guild_member(guild_id, user_id, Map.new(options), reason) - - def modify_guild_member(guild_id, user_id, %{} = options, reason) - when is_snowflake(guild_id) and is_snowflake(user_id) do - options = - options - |> maybe_convert_date_time(:communication_disabled_until) - - request(%{ - method: :patch, - route: Constants.guild_member(guild_id, user_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - }) - |> handle_request_with_decode({:struct, Member}) + def modify_guild_member(guild_id, user_id, options \\ %{}, reason \\ nil) do + Nostrum.Api.Guild.modify_member(guild_id, user_id, options, reason) end @doc """ @@ -1925,8 +1784,7 @@ defmodule Nostrum.Api do """ @spec modify_current_user_nick(Guild.id(), options) :: error | {:ok, %{nick: String.t()}} def modify_current_user_nick(guild_id, options \\ %{}) do - request(:patch, Constants.guild_me_nick(guild_id), options) - |> handle_request_with_decode() + Nostrum.Api.Guild.modify_self_nick(guild_id, options) end @doc """ @@ -1947,13 +1805,7 @@ defmodule Nostrum.Api do """ @spec add_guild_member_role(integer, integer, integer, AuditLogEntry.reason()) :: error | {:ok} def add_guild_member_role(guild_id, user_id, role_id, reason \\ nil) do - request(%{ - method: :put, - route: Constants.guild_member_role(guild_id, user_id, role_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Role.add_member(guild_id, user_id, role_id, reason) end @doc """ @@ -1966,13 +1818,7 @@ defmodule Nostrum.Api do @spec remove_guild_member_role(integer, integer, integer, AuditLogEntry.reason()) :: error | {:ok} def remove_guild_member_role(guild_id, user_id, role_id, reason \\ nil) do - request(%{ - method: :delete, - route: Constants.guild_member_role(guild_id, user_id, role_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Role.remove_member(guild_id, user_id, role_id, reason) end @doc """ @@ -1995,13 +1841,7 @@ defmodule Nostrum.Api do @spec remove_guild_member(Guild.id(), User.id(), AuditLogEntry.reason()) :: error | {:ok} def remove_guild_member(guild_id, user_id, reason \\ nil) when is_snowflake(guild_id) and is_snowflake(user_id) do - request(%{ - method: :delete, - route: Constants.guild_member(guild_id, user_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Guild.kick_member(guild_id, user_id, reason) end @doc """ @@ -2019,8 +1859,7 @@ defmodule Nostrum.Api do @doc since: "0.5.0" @spec get_guild_ban(integer, integer) :: error | {:ok, Guild.Ban.t()} def get_guild_ban(guild_id, user_id) do - request(:get, Constants.guild_ban(guild_id, user_id)) - |> handle_request_with_decode({:struct, Guild.Ban}) + Nostrum.Api.Guild.ban(guild_id, user_id) end @doc """ @@ -2030,8 +1869,7 @@ defmodule Nostrum.Api do """ @spec get_guild_bans(integer) :: error | {:ok, [Nostrum.Struct.User.t()]} def get_guild_bans(guild_id) do - request(:get, Constants.guild_bans(guild_id)) - |> handle_request_with_decode + Nostrum.Api.Guild.bans(guild_id) end @doc """ @@ -2042,13 +1880,7 @@ defmodule Nostrum.Api do """ @spec create_guild_ban(integer, integer, integer, AuditLogEntry.reason()) :: error | {:ok} def create_guild_ban(guild_id, user_id, days_to_delete, reason \\ nil) do - request(%{ - method: :put, - route: Constants.guild_ban(guild_id, user_id), - body: %{delete_message_days: days_to_delete}, - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Guild.ban_member(guild_id, user_id, days_to_delete, reason) end @doc """ @@ -2059,13 +1891,7 @@ defmodule Nostrum.Api do """ @spec remove_guild_ban(integer, integer, AuditLogEntry.reason()) :: error | {:ok} def remove_guild_ban(guild_id, user_id, reason \\ nil) do - request(%{ - method: :delete, - route: Constants.guild_ban(guild_id, user_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Guild.unban_member(guild_id, user_id, reason) end @doc ~S""" @@ -2121,21 +1947,8 @@ defmodule Nostrum.Api do ``` """ @spec create_guild_role(Guild.id(), options, AuditLogEntry.reason()) :: error | {:ok, Role.t()} - def create_guild_role(guild_id, options, reason \\ nil) - - def create_guild_role(guild_id, options, reason) when is_list(options), - do: create_guild_role(guild_id, Map.new(options), reason) - - def create_guild_role(guild_id, %{} = options, reason) when is_snowflake(guild_id) do - %{ - method: :post, - route: Constants.guild_roles(guild_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:struct, Role}) + def create_guild_role(guild_id, options, reason \\ nil) do + Nostrum.Api.Role.create(guild_id, options, reason) end @doc ~S""" @@ -2170,15 +1983,7 @@ defmodule Nostrum.Api do ) :: error | {:ok, [Role.t()]} def modify_guild_role_positions(guild_id, positions, reason \\ nil) when is_snowflake(guild_id) and is_list(positions) do - %{ - method: :patch, - route: Constants.guild_roles(guild_id), - body: positions, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:list, {:struct, Role}}) + Nostrum.Api.Guild.modify_role_positions(guild_id, positions, reason) end @doc ~S""" @@ -2267,13 +2072,7 @@ defmodule Nostrum.Api do @spec delete_guild_role(Guild.id(), Role.id(), AuditLogEntry.reason()) :: error | {:ok} def delete_guild_role(guild_id, role_id, reason \\ nil) when is_snowflake(guild_id) and is_snowflake(role_id) do - request(%{ - method: :delete, - route: Constants.guild_role(guild_id, role_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Role.delete(guild_id, role_id, reason) end @doc ~S""" @@ -2335,15 +2134,7 @@ defmodule Nostrum.Api do error | {:ok, %{pruned: integer}} def begin_guild_prune(guild_id, days, reason \\ nil) when is_snowflake(guild_id) and days in 1..30 do - %{ - method: :post, - route: Constants.guild_prune(guild_id), - body: "", - params: [days: days], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode + Nostrum.Api.Guild.begin_prune(guild_id, days, reason) end @doc ~S""" @@ -2383,8 +2174,7 @@ defmodule Nostrum.Api do """ @spec get_guild_invites(Guild.id()) :: error | {:ok, [Invite.detailed_invite()]} def get_guild_invites(guild_id) when is_snowflake(guild_id) do - request(:get, Constants.guild_invites(guild_id)) - |> handle_request_with_decode({:list, {:struct, Invite}}) + Nostrum.Api.Invite.guild_invites(guild_id) end @doc ~S""" @@ -2422,7 +2212,7 @@ defmodule Nostrum.Api do id: integer }) :: error | {:ok} def create_guild_integrations(guild_id, options) do - request(:post, Constants.guild_integrations(guild_id), options) + Nostrum.Api.Guild.create_integration(guild_id, options) end @doc """ @@ -2441,7 +2231,7 @@ defmodule Nostrum.Api do enable_emoticons: boolean }) :: error | {:ok} def modify_guild_integrations(guild_id, integration_id, options) do - request(:patch, Constants.guild_integration(guild_id, integration_id), options) + Nostrum.Api.Guild.modify_integration(guild_id, integration_id, options) end @doc """ @@ -2451,7 +2241,7 @@ defmodule Nostrum.Api do """ @spec delete_guild_integrations(integer, integer) :: error | {:ok} def delete_guild_integrations(guild_id, integration_id) do - request(:delete, Constants.guild_integration(guild_id, integration_id)) + Nostrum.Api.Guild.delete_integration(guild_id, integration_id) end @doc """ @@ -2461,7 +2251,7 @@ defmodule Nostrum.Api do """ @spec sync_guild_integrations(integer, integer) :: error | {:ok} def sync_guild_integrations(guild_id, integration_id) do - request(:post, Constants.guild_integration_sync(guild_id, integration_id)) + Nostrum.Api.Guild.sync_integration(guild_id, integration_id) end @doc """ @@ -2469,8 +2259,7 @@ defmodule Nostrum.Api do """ @spec get_guild_widget(integer) :: error | {:ok, map} def get_guild_widget(guild_id) do - request(:get, Constants.guild_widget(guild_id)) - |> handle_request_with_decode + Nostrum.Api.Guild.widget(guild_id) end @doc """ @@ -2478,8 +2267,7 @@ defmodule Nostrum.Api do """ @spec modify_guild_widget(integer, map) :: error | {:ok, map} def modify_guild_widget(guild_id, options) do - request(:patch, Constants.guild_widget(guild_id), options) - |> handle_request_with_decode + Nostrum.Api.Guild.modify_widget(guild_id, options) end @doc """ @@ -2506,25 +2294,8 @@ defmodule Nostrum.Api do @doc since: "0.5.0" @spec create_guild_scheduled_event(Guild.id(), reason :: AuditLogEntry.reason(), options) :: {:ok, ScheduledEvent.t()} | error - def create_guild_scheduled_event(guild_id, reason \\ nil, options) - - def create_guild_scheduled_event(guild_id, reason, options) when is_list(options), - do: create_guild_scheduled_event(guild_id, reason, Map.new(options)) - - def create_guild_scheduled_event(guild_id, reason, %{} = options) do - options = - options - |> maybe_convert_date_time(:scheduled_start_time) - |> maybe_convert_date_time(:scheduled_end_time) - - request(%{ - method: :post, - route: Constants.guild_scheduled_events(guild_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - }) - |> handle_request_with_decode({:struct, ScheduledEvent}) + def create_guild_scheduled_event(guild_id, reason \\ nil, options) do + Nostrum.Api.ScheduledEvent.create(guild_id, reason, options) end @doc """ @@ -2544,8 +2315,7 @@ defmodule Nostrum.Api do @spec get_guild_scheduled_event(Guild.id(), ScheduledEvent.id()) :: error | {:ok, ScheduledEvent.t()} def get_guild_scheduled_event(guild_id, event_id) do - request(:get, Constants.guild_scheduled_event(guild_id, event_id)) - |> handle_request_with_decode({:struct, ScheduledEvent}) + Nostrum.Api.ScheduledEvent.get(guild_id, event_id) end @doc """ @@ -2555,7 +2325,7 @@ defmodule Nostrum.Api do @spec delete_guild_scheduled_event(Guild.id(), ScheduledEvent.id()) :: error | {:ok} def delete_guild_scheduled_event(guild_id, event_id) do - request(:delete, Constants.guild_scheduled_event(guild_id, event_id)) + Nostrum.Api.ScheduledEvent.delete(guild_id, event_id) end @doc """ @@ -2582,25 +2352,8 @@ defmodule Nostrum.Api do reason :: AuditLogEntry.reason(), options ) :: error | {:ok, ScheduledEvent.t()} - def modify_guild_scheduled_event(guild_id, event_id, reason \\ nil, options) - - def modify_guild_scheduled_event(guild_id, event_id, reason, options) when is_list(options), - do: modify_guild_scheduled_event(guild_id, event_id, reason, Map.new(options)) - - def modify_guild_scheduled_event(guild_id, event_id, reason, %{} = options) do - options = - options - |> maybe_convert_date_time(:scheduled_start_time) - |> maybe_convert_date_time(:scheduled_end_time) - - request(%{ - method: :patch, - route: Constants.guild_scheduled_event(guild_id, event_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - }) - |> handle_request_with_decode({:struct, ScheduledEvent}) + def modify_guild_scheduled_event(guild_id, event_id, reason \\ nil, options) do + Nostrum.Api.ScheduledEvent.modify(guild_id, event_id, reason, options) end @doc """ @@ -2617,8 +2370,7 @@ defmodule Nostrum.Api do @spec get_guild_scheduled_event_users(Guild.id(), ScheduledEvent.id(), options) :: error | {:ok, [ScheduledEvent.User.t()]} def get_guild_scheduled_event_users(guild_id, event_id, params \\ []) do - request(:get, Constants.guild_scheduled_event_users(guild_id, event_id), "", params) - |> handle_request_with_decode({:list, {:struct, ScheduledEvent.User}}) + Nostrum.Api.ScheduledEvent.users(guild_id, event_id, params) end @doc ~S""" @@ -2641,8 +2393,7 @@ defmodule Nostrum.Api do """ @spec get_invite(Invite.code(), options) :: error | {:ok, Invite.simple_invite()} def get_invite(invite_code, options \\ []) when is_binary(invite_code) do - request(:get, Constants.invite(invite_code), "", options) - |> handle_request_with_decode({:struct, Invite}) + Nostrum.Api.Invite.get(invite_code, options) end @doc ~S""" @@ -2670,8 +2421,7 @@ defmodule Nostrum.Api do """ @spec delete_invite(Invite.code()) :: error | {:ok, Invite.simple_invite()} def delete_invite(invite_code) when is_binary(invite_code) do - request(:delete, Constants.invite(invite_code)) - |> handle_request_with_decode({:struct, Invite}) + Nostrum.Api.Invite.delete(invite_code) end @doc ~S""" @@ -2716,8 +2466,7 @@ defmodule Nostrum.Api do """ @spec get_current_user() :: error | {:ok, User.t()} def get_current_user do - request(:get, Constants.me()) - |> handle_request_with_decode({:struct, User}) + Nostrum.Api.Self.get() end @doc """ @@ -2744,14 +2493,8 @@ defmodule Nostrum.Api do ``` """ @spec modify_current_user(options) :: error | {:ok, User.t()} - def modify_current_user(options) - - def modify_current_user(options) when is_list(options), - do: modify_current_user(Map.new(options)) - - def modify_current_user(%{} = options) do - request(:patch, Constants.me(), options) - |> handle_request_with_decode({:struct, User}) + def modify_current_user(options) do + Nostrum.Api.Self.modify(options) end @doc """ @@ -2812,13 +2555,7 @@ defmodule Nostrum.Api do """ @spec leave_guild(integer) :: error | {:ok} def leave_guild(guild_id) do - request(%{ - method: :delete, - route: Constants.me_guild(guild_id), - body: "", - params: [], - headers: [] - }) + Nostrum.Api.Guild.leave(guild_id) end @doc """ @@ -2835,8 +2572,7 @@ defmodule Nostrum.Api do """ @spec get_user_dms() :: error | {:ok, [Channel.dm_channel()]} def get_user_dms do - request(:get, Constants.me_channels()) - |> handle_request_with_decode({:list, {:struct, Channel}}) + Nostrum.Api.Self.dms() end @doc ~S""" @@ -2921,8 +2657,7 @@ defmodule Nostrum.Api do """ @spec list_voice_regions() :: error | {:ok, [Nostrum.Struct.VoiceRegion.t()]} def list_voice_regions do - request(:get, Constants.regions()) - |> handle_request_with_decode + Nostrum.Api.Guild.voice_regions() end @doc """ @@ -2974,7 +2709,7 @@ defmodule Nostrum.Api do """ @spec get_channel_webhooks(Channel.id()) :: error | {:ok, [Nostrum.Struct.Webhook.t()]} def get_channel_webhooks(channel_id) do - Nostrum.Api.Channel.get_webhooks(channel_id) + Nostrum.Api.Channel.webhooks(channel_id) end @doc """ @@ -2985,8 +2720,7 @@ defmodule Nostrum.Api do """ @spec get_guild_webhooks(Guild.id()) :: error | {:ok, [Nostrum.Struct.Webhook.t()]} def get_guild_webhooks(guild_id) do - request(:get, Constants.webhooks_guild(guild_id)) - |> handle_request_with_decode + Nostrum.Api.Guild.webhooks(guild_id) end @doc """ @@ -3263,8 +2997,7 @@ defmodule Nostrum.Api do """ @spec get_application_information() :: error | {:ok, map()} def get_application_information do - request(:get, Constants.application_information()) - |> handle_request_with_decode + Nostrum.Api.Self.application_information() end @doc """ @@ -3296,7 +3029,7 @@ defmodule Nostrum.Api do @spec get_global_application_commands() :: {:ok, [map()]} | error @spec get_global_application_commands(User.id()) :: {:ok, [map()]} | error def get_global_application_commands(application_id \\ Me.get().id) do - Nostrum.Api.ApplicationCommand.get_global_commands(application_id) + Nostrum.Api.ApplicationCommand.global_commands(application_id) end @doc """ @@ -3424,7 +3157,7 @@ defmodule Nostrum.Api do @spec get_guild_application_commands(Guild.id()) :: {:ok, [map()]} | error @spec get_guild_application_commands(User.id(), Guild.id()) :: {:ok, [map()]} | error def get_guild_application_commands(application_id \\ Me.get().id, guild_id) do - Nostrum.Api.ApplicationCommand.get_guild_commands(application_id, guild_id) + Nostrum.Api.ApplicationCommand.guild_commands(application_id, guild_id) end @doc """ @@ -3794,8 +3527,7 @@ defmodule Nostrum.Api do application_id \\ Me.get().id, guild_id ) do - request(:get, Constants.guild_application_command_permissions(application_id, guild_id)) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.guild_permissions(application_id, guild_id) end @doc """ @@ -3820,7 +3552,7 @@ defmodule Nostrum.Api do guild_id, command_id ) do - Nostrum.Api.ApplicationCommand.get_command_permissions(application_id, guild_id, command_id) + Nostrum.Api.ApplicationCommand.permissions(application_id, guild_id, command_id) end @doc """ @@ -4045,8 +3777,7 @@ defmodule Nostrum.Api do @doc since: "0.5.1" @spec get_thread_member(Channel.id(), User.id()) :: {:ok, ThreadMember.t()} | error def get_thread_member(thread_id, user_id) do - request(:get, Constants.thread_member(thread_id, user_id)) - |> handle_request_with_decode({:struct, ThreadMember}) + Nostrum.Api.Thread.member(thread_id, user_id) end @doc """ @@ -4057,8 +3788,7 @@ defmodule Nostrum.Api do @doc since: "0.5.1" @spec get_thread_members(Channel.id()) :: {:ok, [ThreadMember.t()]} | error def get_thread_members(thread_id) do - request(:get, Constants.thread_members(thread_id)) - |> handle_request_with_decode({:list, {:struct, ThreadMember}}) + Nostrum.Api.Thread.members(thread_id) end @doc """ @@ -4201,7 +3931,7 @@ defmodule Nostrum.Api do """ @doc since: "0.5.1" def add_thread_member(thread_id, user_id) do - request(:put, Constants.thread_member(thread_id, user_id)) + Nostrum.Api.Thread.add_member(thread_id, user_id) end @doc """ @@ -4230,8 +3960,7 @@ defmodule Nostrum.Api do @doc since: "0.7.0" @spec get_guild_auto_moderation_rules(Guild.id()) :: {:ok, [AutoModerationRule.t()]} | error def get_guild_auto_moderation_rules(guild_id) do - request(:get, Constants.guild_auto_moderation_rule(guild_id)) - |> handle_request_with_decode({:list, {:struct, AutoModerationRule}}) + Nostrum.Api.AutoModeration.rules(guild_id) end @doc """ @@ -4241,7 +3970,7 @@ defmodule Nostrum.Api do @spec get_guild_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: {:ok, AutoModerationRule.t()} | error def get_guild_auto_moderation_rule(guild_id, rule_id) do - Nostrum.Api.AutoModeration.get_auto_moderation_rule(guild_id, rule_id) + Nostrum.Api.AutoModeration.rule(guild_id, rule_id) end @doc """ @@ -4268,7 +3997,7 @@ defmodule Nostrum.Api do do: create_guild_auto_moderation_rule(guild_id, Map.new(options)) def create_guild_auto_moderation_rule(guild_id, options) do - Nostrum.Api.AutoModeration.create_auto_moderation_rule(guild_id, options) + Nostrum.Api.AutoModeration.create_rule(guild_id, options) end @doc """ @@ -4283,7 +4012,7 @@ defmodule Nostrum.Api do do: modify_guild_auto_moderation_rule(guild_id, rule_id, Map.new(options)) def modify_guild_auto_moderation_rule(guild_id, rule_id, options) do - Nostrum.Api.AutoModeration.modify_auto_moderation_rule(guild_id, rule_id, options) + Nostrum.Api.AutoModeration.modify_rule(guild_id, rule_id, options) end @doc """ @@ -4292,7 +4021,7 @@ defmodule Nostrum.Api do @doc since: "0.7.0" @spec delete_guild_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: {:ok} | error def delete_guild_auto_moderation_rule(guild_id, rule_id) do - Nostrum.Api.AutoModeration.delete_auto_moderation_rule(guild_id, rule_id) + Nostrum.Api.AutoModeration.delete_rule(guild_id, rule_id) end @spec maybe_add_reason(String.t() | nil) :: list() @@ -4355,20 +4084,20 @@ defmodule Nostrum.Api do end # If `:embed` is present, prepend to `:embeds` for compatibility - defp combine_embeds(%{embed: embed} = args), + def combine_embeds(%{embed: embed} = args), do: Map.delete(args, :embed) |> Map.put(:embeds, [embed | args[:embeds] || []]) - defp combine_embeds(%{data: data} = args), do: %{args | data: combine_embeds(data)} - defp combine_embeds(%{message: data} = args), do: %{args | message: combine_embeds(data)} - defp combine_embeds(args), do: args + def combine_embeds(%{data: data} = args), do: %{args | data: combine_embeds(data)} + def combine_embeds(%{message: data} = args), do: %{args | message: combine_embeds(data)} + def combine_embeds(args), do: args # If `:file` is present, prepend to `:files` for compatibility - defp combine_files(%{file: file} = args), + def combine_files(%{file: file} = args), do: Map.delete(args, :file) |> Map.put(:files, [file | args[:files] || []]) - defp combine_files(%{data: data} = args), do: %{args | data: combine_files(data)} - defp combine_files(%{message: data} = args), do: %{args | message: combine_files(data)} - defp combine_files(args), do: args + def combine_files(%{data: data} = args), do: %{args | data: combine_files(data)} + def combine_files(%{message: data} = args), do: %{args | message: combine_files(data)} + def combine_files(args), do: args defp pop_files(%{data: data} = args), do: {data.files, %{args | data: Map.delete(data, :files)}} @@ -4380,6 +4109,11 @@ defmodule Nostrum.Api do @doc false def bangify(to_bang) do + Logger.warning(""" + Using the bangified version of the Api call is deprecated and will be removed in the next major release. + Please use the non-bangified version and handle the error case yourself. + """) + case to_bang do {:error, error} -> raise(error) @@ -4392,7 +4126,7 @@ defmodule Nostrum.Api do end end - defp prepare_allowed_mentions(options) do + def prepare_allowed_mentions(options) do with raw_options when raw_options != :all <- Map.get(options, :allowed_mentions, :all), allowed_mentions when is_map(allowed_mentions) <- parse_allowed_mentions(raw_options) do Map.put(options, :allowed_mentions, allowed_mentions) @@ -4489,7 +4223,7 @@ defmodule Nostrum.Api do defp parse_allowed_mentions(options), do: options @spec maybe_convert_date_time(options(), atom()) :: options() - defp maybe_convert_date_time(options, key) when is_map(options) do + def maybe_convert_date_time(options, key) when is_map(options) do case options do %{^key => %DateTime{} = date_time} -> timestamp = DateTime.to_iso8601(date_time) @@ -4500,7 +4234,7 @@ defmodule Nostrum.Api do end end - defp maybe_convert_date_time(options, key) when is_list(options) do + def maybe_convert_date_time(options, key) when is_list(options) do case Keyword.get(options, key) do %DateTime{} = date_time -> timestamp = DateTime.to_iso8601(date_time) diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex index 91283af06..d815edf82 100644 --- a/lib/nostrum/api/application_command.ex +++ b/lib/nostrum/api/application_command.ex @@ -312,11 +312,11 @@ defmodule Nostrum.Api.ApplicationCommand do This method returns a single guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). """ @doc since: "0.5.0" - @spec get_command_permissions(Guild.id(), Snowflake.t()) :: + @spec permissions(Guild.id(), Snowflake.t()) :: {:ok, map()} | Api.error() - @spec get_command_permissions(User.id(), Guild.id(), Snowflake.t()) :: + @spec permissions(User.id(), Guild.id(), Snowflake.t()) :: {:ok, map()} | Api.error() - def get_command_permissions( + def permissions( application_id \\ Me.get().id, guild_id, command_id @@ -342,7 +342,7 @@ defmodule Nostrum.Api.ApplicationCommand do ## Example ```elixir - iex> Nostrum.Api.get_global_commands + iex> Nostrum.Api.global_commands {:ok, [ %{ @@ -354,9 +354,9 @@ defmodule Nostrum.Api.ApplicationCommand do ]} ``` """ - @spec get_global_commands() :: {:ok, [map()]} | Api.error() - @spec get_global_commands(User.id()) :: {:ok, [map()]} | Api.error() - def get_global_commands(application_id \\ Me.get().id) do + @spec global_commands() :: {:ok, [map()]} | Api.error() + @spec global_commands(User.id()) :: {:ok, [map()]} | Api.error() + def global_commands(application_id \\ Me.get().id) do Api.request(:get, Constants.global_application_commands(application_id)) |> Api.handle_request_with_decode() end @@ -373,10 +373,10 @@ defmodule Nostrum.Api.ApplicationCommand do This method returns a list of guild application command permission objects, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). """ @doc since: "0.5.0" - @spec get_guild_command_permissions(Guild.id()) :: {:ok, [map()]} | Api.error() - @spec get_guild_command_permissions(User.id(), Guild.id()) :: + @spec guild_permissions(Guild.id()) :: {:ok, [map()]} | Api.error() + @spec guild_permissions(User.id(), Guild.id()) :: {:ok, [map()]} | Api.error() - def get_guild_command_permissions( + def guild_permissions( application_id \\ Me.get().id, guild_id ) do @@ -397,9 +397,9 @@ defmodule Nostrum.Api.ApplicationCommand do A list of ``ApplicationCommand``s on success. See the official reference: https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure """ - @spec get_guild_commands(Guild.id()) :: {:ok, [map()]} | Api.error() - @spec get_guild_commands(User.id(), Guild.id()) :: {:ok, [map()]} | Api.error() - def get_guild_commands(application_id \\ Me.get().id, guild_id) do + @spec guild_commands(Guild.id()) :: {:ok, [map()]} | Api.error() + @spec guild_commands(User.id(), Guild.id()) :: {:ok, [map()]} | Api.error() + def guild_commands(application_id \\ Me.get().id, guild_id) do Api.request(:get, Constants.guild_application_commands(application_id, guild_id)) |> Api.handle_request_with_decode() end diff --git a/lib/nostrum/api/auto_moderation.ex b/lib/nostrum/api/auto_moderation.ex index 8ad63cef2..a43f1539c 100644 --- a/lib/nostrum/api/auto_moderation.ex +++ b/lib/nostrum/api/auto_moderation.ex @@ -22,12 +22,12 @@ defmodule Nostrum.Api.AutoModeration do - optional, defaults to `[]`, maximum of 50. """ @doc since: "0.7.0" - @spec create_auto_moderation_rule(Guild.id(), Api.options()) :: + @spec create_rule(Guild.id(), Api.options()) :: {:ok, AutoModerationRule.t()} | Api.error() - def create_auto_moderation_rule(guild_id, options) when is_list(options), - do: create_auto_moderation_rule(guild_id, Map.new(options)) + def create_rule(guild_id, options) when is_list(options), + do: create_rule(guild_id, Map.new(options)) - def create_auto_moderation_rule(guild_id, options) do + def create_rule(guild_id, options) do Api.request(:post, Constants.guild_auto_moderation_rule(guild_id), options) |> Api.handle_request_with_decode({:struct, AutoModerationRule}) end @@ -36,9 +36,9 @@ defmodule Nostrum.Api.AutoModeration do Delete an auto-moderation rule for a guild. """ @doc since: "0.7.0" - @spec delete_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: + @spec delete_rule(Guild.id(), AutoModerationRule.id()) :: {:ok} | Api.error() - def delete_auto_moderation_rule(guild_id, rule_id) do + def delete_rule(guild_id, rule_id) do Api.request(:delete, Constants.guild_auto_moderation_rule(guild_id, rule_id)) end @@ -46,9 +46,8 @@ defmodule Nostrum.Api.AutoModeration do Get a list of all auto-moderation rules for a guild. """ @doc since: "0.7.0" - @spec get_auto_moderation_rules(Guild.id()) :: - {:ok, [AutoModerationRule.t()]} | Api.error() - def get_auto_moderation_rules(guild_id) do + @spec rules(Guild.id()) :: {:ok, [AutoModerationRule.t()]} | Api.error() + def rules(guild_id) do Api.request(:get, Constants.guild_auto_moderation_rule(guild_id)) |> Api.handle_request_with_decode({:list, {:struct, AutoModerationRule}}) end @@ -57,9 +56,9 @@ defmodule Nostrum.Api.AutoModeration do Get a single auto-moderation rule for a guild. """ @doc since: "0.7.0" - @spec get_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: + @spec rule(Guild.id(), AutoModerationRule.id()) :: {:ok, AutoModerationRule.t()} | Api.error() - def get_auto_moderation_rule(guild_id, rule_id) do + def rule(guild_id, rule_id) do Api.request(:get, Constants.guild_auto_moderation_rule(guild_id, rule_id)) |> Api.handle_request_with_decode({:struct, AutoModerationRule}) end @@ -67,15 +66,15 @@ defmodule Nostrum.Api.AutoModeration do @doc """ Modify an auto-moderation rule for a guild. - Takes the same options as `create_auto_moderation_rule/2`, however all fields are optional. + Takes the same options as `create_rule/2`, however all fields are optional. """ @doc since: "0.7.0" - @spec modify_auto_moderation_rule(Guild.id(), AutoModerationRule.id(), Api.options()) :: + @spec modify_rule(Guild.id(), AutoModerationRule.id(), Api.options()) :: {:ok, AutoModerationRule.t()} | Api.error() - def modify_auto_moderation_rule(guild_id, rule_id, options) when is_list(options), - do: modify_auto_moderation_rule(guild_id, rule_id, Map.new(options)) + def modify_rule(guild_id, rule_id, options) when is_list(options), + do: modify_rule(guild_id, rule_id, Map.new(options)) - def modify_auto_moderation_rule(guild_id, rule_id, options) do + def modify_rule(guild_id, rule_id, options) do Api.request(:patch, Constants.guild_auto_moderation_rule(guild_id, rule_id), options) |> Api.handle_request_with_decode({:struct, AutoModerationRule}) end diff --git a/lib/nostrum/api/channel.ex b/lib/nostrum/api/channel.ex index 9fbcfcbf8..880e64fc7 100644 --- a/lib/nostrum/api/channel.ex +++ b/lib/nostrum/api/channel.ex @@ -32,41 +32,6 @@ defmodule Nostrum.Api.Channel do Api.request(:put, Constants.channel_pin(channel_id, message_id)) end - @doc ~S""" - Retrieves all pinned messages from a channel. - - This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions. - - If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.Channel.get_pinned_messages(43189401384091) - ``` - """ - @spec get_pinned_messages(Channel.id()) :: Api.error() | {:ok, [Message.t()]} - def get_pinned_messages(channel_id) when is_snowflake(channel_id) do - Api.request(:get, Constants.channel_pins(channel_id)) - |> Api.handle_request_with_decode({:list, {:struct, Message}}) - end - - @doc """ - Unpins a message in a channel. - - This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and - 'MANAGE_MESSAGES' permissions. It fires the - `t:Nostrum.Consumer.message_update/0` and - `t:Nostrum.Consumer.channel_pins_update/0` events. - - Returns `{:ok}` if successful. `error` otherwise. - """ - @spec unpin_message(Channel.id(), Message.id()) :: Api.error() | {:ok} - def unpin_message(channel_id, message_id) - when is_snowflake(channel_id) and is_snowflake(message_id) do - Api.request(:delete, Constants.channel_pin(channel_id, message_id)) - end - @doc """ Deletes multiple messages from a channel. @@ -158,83 +123,6 @@ defmodule Nostrum.Api.Channel do |> Api.handle_request_with_decode({:struct, Channel}) end - @doc ~S""" - Gets a channel. - - If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.Channel.get(381889573426429952) - {:ok, %Nostrum.Struct.Channel{id: 381889573426429952}} - ``` - """ - @spec get(Channel.id()) :: Api.error() | {:ok, Channel.t()} - def get(channel_id) when is_snowflake(channel_id) do - Api.request(:get, Constants.channel(channel_id)) - |> Api.handle_request_with_decode({:struct, Channel}) - end - - @doc ~S""" - Modifies a channel's settings. - - An optional `reason` can be given for the guild audit log. - - If a `t:Nostrum.Struct.Channel.guild_channel/0` is being modified, this - endpoint requires the `MANAGE_CHANNEL` permission. It fires a - `t:Nostrum.Consumer.channel_update/0` event. If a - `t:Nostrum.Struct.Channel.guild_category_channel/0` is being modified, then this - endpoint fires multiple `t:Nostrum.Consumer.channel_update/0` events. - - If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - 2-100 character channel name - * `:position` (integer) - the position of the channel in the left-hand listing - * `:topic` (string) (`t:Nostrum.Struct.Channel.text_channel/0` only) - - 0-1024 character channel topic - * `:nsfw` (boolean) (`t:Nostrum.Struct.Channel.text_channel/0` only) - - if the channel is nsfw - * `:bitrate` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - - the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers) - * `:user_limit` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - - the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit - * `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) - - channel or category-specific permissions - * `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) (`t:Nostrum.Struct.Channel.guild_channel/0` only) - - id of the new parent category for a channel - - ## Examples - - ```elixir - Nostrum.Api.Channel.modify(41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion") - {:ok, %Nostrum.Struct.Channel{id: 41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion"}} - - Nostrum.Api.Channel.modify(41771983423143933) - {:ok, %Nostrum.Struct.Channel{id: 41771983423143933}} - ``` - """ - @spec modify(Channel.id(), Api.options(), AuditLogEntry.reason()) :: - Api.error() | {:ok, Channel.t()} - def modify(channel_id, options, reason \\ nil) - - def modify(channel_id, options, reason) when is_list(options), - do: modify(channel_id, Map.new(options), reason) - - def modify(channel_id, %{} = options, reason) when is_snowflake(channel_id) do - %{ - method: :patch, - route: Constants.channel(channel_id), - body: options, - params: [], - headers: Api.maybe_add_reason(reason) - } - |> Api.request() - |> Api.handle_request_with_decode({:struct, Channel}) - end - @doc ~S""" Deletes a channel. @@ -268,6 +156,39 @@ defmodule Nostrum.Api.Channel do |> Api.handle_request_with_decode({:struct, Channel}) end + @doc """ + Delete a channel permission for a user or role. + + Role or user overwrite to delete is specified by `channel_id` and `overwrite_id`. + An optional `reason` can be given for the audit log. + """ + @spec delete_permissions(Channel.id(), integer, AuditLogEntry.reason()) :: Api.error() | {:ok} + def delete_permissions(channel_id, overwrite_id, reason \\ nil) do + Api.request(%{ + method: :delete, + route: Constants.channel_permission(channel_id, overwrite_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc """ + Unpins a message in a channel. + + This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and + 'MANAGE_MESSAGES' permissions. It fires the + `t:Nostrum.Consumer.message_update/0` and + `t:Nostrum.Consumer.channel_pins_update/0` events. + + Returns `{:ok}` if successful. `error` otherwise. + """ + @spec unpin_message(Channel.id(), Message.id()) :: Api.error() | {:ok} + def unpin_message(channel_id, message_id) + when is_snowflake(channel_id) and is_snowflake(message_id) do + Api.request(:delete, Constants.channel_pin(channel_id, message_id)) + end + @doc """ Edit the permission overwrites for a user or role. @@ -305,21 +226,22 @@ defmodule Nostrum.Api.Channel do }) end - @doc """ - Delete a channel permission for a user or role. + @doc ~S""" + Gets a channel. - Role or user overwrite to delete is specified by `channel_id` and `overwrite_id`. - An optional `reason` can be given for the audit log. + If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Channel.get(381889573426429952) + {:ok, %Nostrum.Struct.Channel{id: 381889573426429952}} + ``` """ - @spec delete_permissions(Channel.id(), integer, AuditLogEntry.reason()) :: Api.error() | {:ok} - def delete_permissions(channel_id, overwrite_id, reason \\ nil) do - Api.request(%{ - method: :delete, - route: Constants.channel_permission(channel_id, overwrite_id), - body: "", - params: [], - headers: Api.maybe_add_reason(reason) - }) + @spec get(Channel.id()) :: Api.error() | {:ok, Channel.t()} + def get(channel_id) when is_snowflake(channel_id) do + Api.request(:get, Constants.channel(channel_id)) + |> Api.handle_request_with_decode({:struct, Channel}) end @doc ~S""" @@ -334,24 +256,24 @@ defmodule Nostrum.Api.Channel do ## Examples ```elixir - Nostrum.Api.Channel.get_messages(43189401384091, 5, {:before, 130230401384}) + Nostrum.Api.Channel.messages(43189401384091, 5, {:before, 130230401384}) ``` """ - @spec get_messages(Channel.id(), Api.limit(), Api.locator()) :: + @spec messages(Channel.id(), Api.limit(), Api.locator()) :: Api.error() | {:ok, [Message.t()]} - def get_messages(channel_id, limit, locator \\ {}) when is_snowflake(channel_id) do - get_messages_sync(channel_id, limit, [], locator) + def messages(channel_id, limit, locator \\ {}) when is_snowflake(channel_id) do + messages_sync(channel_id, limit, [], locator) end - defp get_messages_sync(channel_id, limit, messages, locator) when limit <= 100 do - case get_messages_call(channel_id, limit, locator) do + defp messages_sync(channel_id, limit, messages, locator) when limit <= 100 do + case messages_call(channel_id, limit, locator) do {:ok, new_messages} -> {:ok, messages ++ new_messages} other -> other end end - defp get_messages_sync(channel_id, limit, messages, locator) do - case get_messages_call(channel_id, 100, locator) do + defp messages_sync(channel_id, limit, messages, locator) do + case messages_call(channel_id, 100, locator) do {:error, message} -> {:error, message} @@ -361,7 +283,7 @@ defmodule Nostrum.Api.Channel do {:ok, new_messages} -> new_limit = get_new_limit(limit, length(new_messages)) new_locator = get_new_locator(locator, List.last(new_messages)) - get_messages_sync(channel_id, new_limit, messages ++ new_messages, new_locator) + messages_sync(channel_id, new_limit, messages ++ new_messages, new_locator) end end @@ -373,7 +295,7 @@ defmodule Nostrum.Api.Channel do # We're decoding the response at each call to catch any errors @doc false - def get_messages_call(channel_id, limit, locator) do + def messages_call(channel_id, limit, locator) do qs_params = case locator do {} -> [{:limit, limit}] @@ -390,12 +312,90 @@ defmodule Nostrum.Api.Channel do ## Parameters - `channel_id` - Channel to get webhooks for. """ - @spec get_webhooks(Channel.id()) :: Api.error() | {:ok, [Webhook.t()]} - def get_webhooks(channel_id) do + @spec webhooks(Channel.id()) :: Api.error() | {:ok, [Webhook.t()]} + def webhooks(channel_id) do Api.request(:get, Constants.webhooks_channel(channel_id)) |> Api.handle_request_with_decode() end + @doc ~S""" + Retrieves all pinned messages from a channel. + + This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions. + + If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Channel.pinned_messages(43189401384091) + ``` + """ + @spec pinned_messages(Channel.id()) :: Api.error() | {:ok, [Message.t()]} + def pinned_messages(channel_id) when is_snowflake(channel_id) do + Api.request(:get, Constants.channel_pins(channel_id)) + |> Api.handle_request_with_decode({:list, {:struct, Message}}) + end + + @doc ~S""" + Modifies a channel's settings. + + An optional `reason` can be given for the guild audit log. + + If a `t:Nostrum.Struct.Channel.guild_channel/0` is being modified, this + endpoint requires the `MANAGE_CHANNEL` permission. It fires a + `t:Nostrum.Consumer.channel_update/0` event. If a + `t:Nostrum.Struct.Channel.guild_category_channel/0` is being modified, then this + endpoint fires multiple `t:Nostrum.Consumer.channel_update/0` events. + + If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - 2-100 character channel name + * `:position` (integer) - the position of the channel in the left-hand listing + * `:topic` (string) (`t:Nostrum.Struct.Channel.text_channel/0` only) - + 0-1024 character channel topic + * `:nsfw` (boolean) (`t:Nostrum.Struct.Channel.text_channel/0` only) - + if the channel is nsfw + * `:bitrate` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - + the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers) + * `:user_limit` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - + the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit + * `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) - + channel or category-specific permissions + * `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) (`t:Nostrum.Struct.Channel.guild_channel/0` only) - + id of the new parent category for a channel + + ## Examples + + ```elixir + Nostrum.Api.Channel.modify(41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion") + {:ok, %Nostrum.Struct.Channel{id: 41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion"}} + + Nostrum.Api.Channel.modify(41771983423143933) + {:ok, %Nostrum.Struct.Channel{id: 41771983423143933}} + ``` + """ + @spec modify(Channel.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Channel.t()} + def modify(channel_id, options, reason \\ nil) + + def modify(channel_id, options, reason) when is_list(options), + do: modify(channel_id, Map.new(options), reason) + + def modify(channel_id, %{} = options, reason) when is_snowflake(channel_id) do + %{ + method: :patch, + route: Constants.channel(channel_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Channel}) + end + @doc """ Triggers the typing indicator. diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex index 231df3206..1993e6b40 100644 --- a/lib/nostrum/api/guild.ex +++ b/lib/nostrum/api/guild.ex @@ -1,9 +1,19 @@ defmodule Nostrum.Api.Guild do alias Nostrum.Api alias Nostrum.Constants - alias Nostrum.Struct - alias Nostrum.Struct.User + alias Nostrum.Struct.Channel + alias Nostrum.Struct.Emoji + alias Nostrum.Struct.Guild + alias Nostrum.Struct.Guild.AuditLog + alias Nostrum.Struct.Guild.AuditLogEntry + alias Nostrum.Struct.Guild.Ban + alias Nostrum.Struct.Guild.Integration alias Nostrum.Struct.Guild.Member + alias Nostrum.Struct.Guild.Role + alias Nostrum.Struct.Guild.ScheduledEvent + alias Nostrum.Struct.User + alias Nostrum.Struct.VoiceRegion + alias Nostrum.Struct.Webhook import Nostrum.Snowflake, only: [is_snowflake: 1] @@ -40,7 +50,7 @@ defmodule Nostrum.Api.Guild do ) ``` """ - @spec add_member(Struct.Guild.id(), User.id(), Api.options()) :: + @spec add_member(Guild.id(), User.id(), Api.options()) :: Api.error() | {:ok, Member.t()} | {:ok} def add_member(guild_id, user_id, options) @@ -52,4 +62,740 @@ defmodule Nostrum.Api.Guild do Api.request(:put, Constants.guild_member(guild_id, user_id), options) |> Api.handle_request_with_decode({:struct, Member}) end + + @doc """ + Begins a guild prune to prune members within `days`. + + An optional `reason` can be provided for the guild audit log. + + This endpoint requires the `KICK_MEMBERS` permission. It fires multiple + `t:Nostrum.Consumer.guild_member_remove/0` events. + + If successful, returns `{:ok, %{pruned: pruned}}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.begin_prune(81384788765712384, 1) + {:ok, %{pruned: 0}} + ``` + """ + @spec begin_prune(Guild.id(), 1..30, AuditLogEntry.reason()) :: + Api.error() | {:ok, %{pruned: integer}} + def begin_prune(guild_id, days, reason \\ nil) + when is_snowflake(guild_id) and days in 1..30 do + %{ + method: :post, + route: Constants.guild_prune(guild_id), + body: "", + params: [days: days], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode() + end + + @doc """ + Bans a user from a guild. + + User to delete is specified by `guild_id` and `user_id`. + An optional `reason` can be specified for the audit log. + """ + @spec ban_member(Guild.id(), User.id(), integer, AuditLogEntry.reason()) :: + Api.error() | {:ok} + def ban_member(guild_id, user_id, days_to_delete, reason \\ nil) do + Api.request(%{ + method: :put, + route: Constants.guild_ban(guild_id, user_id), + body: %{delete_message_days: days_to_delete}, + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc ~S""" + Creates a new emoji for the given guild. + + This endpoint requires the `MANAGE_EMOJIS` permission. It fires a + `t:Nostrum.Consumer.guild_emojis_update/0` event. + + An optional `reason` can be provided for the audit log. + + If successful, returns `{:ok, emoji}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - name of the emoji + * `:image` (base64 data URI) - the 128x128 emoji image. Maximum size of 256kb + * `:roles` (list of `t:Nostrum.Snowflake.t/0`) - roles for which this emoji will be whitelisted + (default: []) + + `:name` and `:image` are always required. + + ## Examples + + ```elixir + image = "data:image/png;base64,YXl5IGJieSB1IGx1a2luIDQgc3VtIGZ1az8=" + + Nostrum.Api.Guild.create_emoji(43189401384091, name: "nostrum", image: image, roles: []) + ``` + """ + @spec create_emoji(Guild.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Emoji.t()} + def create_emoji(guild_id, options, reason \\ nil) + + def create_emoji(guild_id, options, reason) when is_list(options), + do: create_emoji(guild_id, Map.new(options), reason) + + def create_emoji(guild_id, %{} = options, reason) do + %{ + method: :post, + route: Constants.guild_emojis(guild_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Emoji}) + end + + @doc """ + Creates a new guild integeration. + + Guild to create integration with is specified by `guild_id`. + + `options` is a map with the following requires keys: + * `type` - Integration type. + * `id` - Integeration id. + """ + @spec create_integration(integer, %{ + type: String.t(), + id: integer + }) :: Api.error() | {:ok} + def create_integration(guild_id, options) do + Api.request(:post, Constants.guild_integrations(guild_id), options) + end + + @doc ~S""" + Deletes a guild. + + This endpoint requires that the current user is the owner of the guild. + It fires the `t:Nostrum.Consumer.guild_delete/0` event. + + If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.delete(81384788765712384) + {:ok} + ``` + """ + @spec delete(Guild.id()) :: Api.error() | {:ok} + def delete(guild_id) when is_snowflake(guild_id) do + Api.request(:delete, Constants.guild(guild_id)) + end + + @doc ~S""" + Deletes the given emoji. + + An optional `reason` can be provided for the audit log. + + This endpoint requires the `MANAGE_EMOJIS` permission. It fires a + `t:Nostrum.Consumer.guild_emojis_update/0` event. + + If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. + """ + @spec delete_emoji(Guild.id(), Emoji.id(), AuditLogEntry.reason()) :: + Api.error() | {:ok} + def delete_emoji(guild_id, emoji_id, reason \\ nil) do + Api.request(%{ + method: :delete, + route: Constants.guild_emoji(guild_id, emoji_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc """ + Deletes a guild integeration. + + Integration to delete is specified by `guild_id` and `integeration_id`. + """ + @spec delete_integration(Guild.id(), integer) :: Api.error() | {:ok} + def delete_integration(guild_id, integration_id) do + Api.request(:delete, Constants.guild_integration(guild_id, integration_id)) + end + + @doc ~S""" + Gets a guild. + + If successful, returns `{:ok, guild}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.get(81384788765712384) + {:ok, %Nostrum.Struct.Guild{id: 81384788765712384}} + ``` + """ + @spec get(Guild.id()) :: Api.error() | {:ok, Guild.rest_guild()} + def get(guild_id) when is_snowflake(guild_id) do + Api.request(:get, Constants.guild(guild_id)) + |> Api.handle_request_with_decode({:struct, Guild}) + end + + @doc ~S""" + Get the `t:Nostrum.Struct.Guild.AuditLog.t/0` for the given `guild_id`. + + ## Options + + * `:user_id` (`t:Nostrum.Struct.User.id/0`) - filter the log for a user ID + * `:action_type` (`t:integer/0`) - filter the log by audit log type, see [Audit Log Events](https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events) + * `:before` (`t:Nostrum.Struct.Snowflake.t/0`) - filter the log before a certain entry ID + * `:limit` (`t:pos_integer/0`) - how many entries are returned (default 50, minimum 1, maximum 100) + """ + @spec audit_log(Guild.id(), Api.options()) :: {:ok, AuditLog.t()} | Api.error() + def audit_log(guild_id, options \\ []) do + Api.request(:get, Constants.guild_audit_logs(guild_id), "", options) + |> Api.handle_request_with_decode({:struct, AuditLog}) + end + + @doc """ + Gets a ban object for the given user from a guild. + """ + @doc since: "0.5.0" + @spec ban(Guild.id(), User.id()) :: Api.error() | {:ok, Ban.t()} + def ban(guild_id, user_id) do + Api.request(:get, Constants.guild_ban(guild_id, user_id)) + |> Api.handle_request_with_decode({:struct, Ban}) + end + + @doc """ + Gets a list of users banned from a guild. + + Guild to get bans for is specified by `guild_id`. + """ + @spec bans(Guild.id()) :: Api.error() | {:ok, [User.t()]} + def bans(guild_id) do + Api.request(:get, Constants.guild_bans(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, User}}) + end + + @doc ~S""" + Gets a list of guild channels. + + If successful, returns `{:ok, channels}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.channels(81384788765712384) + {:ok, [%Nostrum.Struct.Channel{guild_id: 81384788765712384} | _]} + ``` + """ + @spec channels(Guild.id()) :: Api.error() | {:ok, [Channel.guild_channel()]} + def channels(guild_id) when is_snowflake(guild_id) do + Api.request(:get, Constants.guild_channels(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, Channel}}) + end + + @doc ~S""" + Gets an emoji for the given guild and emoji ids. + + This endpoint requires the `MANAGE_EMOJIS` permission. + + If successful, returns `{:ok, emoji}`. Otherwise, returns `t:Nostrum.Api.error/0`. + """ + @spec emoji(Guild.id(), Emoji.id()) :: Api.error() | {:ok, Emoji.t()} + def emoji(guild_id, emoji_id) do + Api.request(:get, Constants.guild_emoji(guild_id, emoji_id)) + |> Api.handle_request_with_decode({:struct, Emoji}) + end + + @doc """ + Gets a list of guild integerations. + + Guild to get integrations for is specified by `guild_id`. + """ + @spec integrations(Guild.id()) :: + Api.error() | {:ok, [Integration.t()]} + def integrations(guild_id) do + Api.request(:get, Constants.guild_integrations(guild_id)) + |> Api.handle_request_with_decode() + end + + @doc """ + Gets a guild member. + + If successful, returns `{:ok, member}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.member(4019283754613, 184937267485) + ``` + """ + @spec member(Guild.id(), User.id()) :: Api.error() | {:ok, Member.t()} + def member(guild_id, user_id) when is_snowflake(guild_id) and is_snowflake(user_id) do + Api.request(:get, Constants.guild_member(guild_id, user_id)) + |> Api.handle_request_with_decode({:struct, Member}) + end + + @doc """ + Gets the number of members that would be removed in a prune given `days`. + + This endpoint requires the `KICK_MEMBERS` permission. + + If successful, returns `{:ok, %{pruned: pruned}}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.estimate_prune_count(81384788765712384, 1) + {:ok, %{pruned: 0}} + ``` + """ + @spec estimate_prune_count(Guild.id(), 1..30) :: Api.error() | {:ok, %{pruned: integer}} + def estimate_prune_count(guild_id, days) when is_snowflake(guild_id) and days in 1..30 do + Api.request(:get, Constants.guild_prune(guild_id), "", days: days) + |> Api.handle_request_with_decode() + end + + @doc ~S""" + Gets a guild's roles. + + If successful, returns `{:ok, roles}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.roles(147362948571673) + ``` + """ + @spec roles(Guild.id()) :: Api.error() | {:ok, [Role.t()]} + def roles(guild_id) when is_snowflake(guild_id) do + Api.request(:get, Constants.guild_roles(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, Role}}) + end + + @doc """ + Get a list of scheduled events for a guild. + """ + @doc since: "0.5.0" + @spec scheduled_events(Guild.id()) :: Api.error() | {:ok, [ScheduledEvent.t()]} + def scheduled_events(guild_id) do + Api.request(:get, Constants.guild_scheduled_events(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, ScheduledEvent}}) + end + + @doc """ + Gets a list of webhooks for a guild. + + ## Parameters + - `guild_id` - Guild to get webhooks for. + """ + @spec webhooks(Guild.id()) :: Api.error() | {:ok, [Webhook.t()]} + def webhooks(guild_id) do + Api.request(:get, Constants.webhooks_guild(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, Webhook}}) + end + + @doc """ + Gets a guild embed. + """ + @spec widget(Guild.id()) :: Api.error() | {:ok, map} + def widget(guild_id) do + Api.request(:get, Constants.guild_widget(guild_id)) + |> Api.handle_request_with_decode() + end + + @doc """ + Gets a list of voice regions for the guild. + + Guild to get voice regions for is specified by `guild_id`. + """ + @spec voice_region(Guild.id()) :: Api.error() | {:ok, [VoiceRegion.t()]} + def voice_region(guild_id) do + Api.request(:get, Constants.guild_voice_regions(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, VoiceRegion}}) + end + + @doc """ + Leaves a guild. + + Guild to leave is specified by `guild_id`. + """ + @spec leave(Guild.id()) :: Api.error() | {:ok} + def leave(guild_id) do + Api.request(%{ + method: :delete, + route: Constants.me_guild(guild_id), + body: "", + params: [], + headers: [] + }) + end + + @doc ~S""" + Gets a list of emojis for a given guild. + + This endpoint requires the `MANAGE_EMOJIS` permission. + + If successful, returns `{:ok, emojis}`. Otherwise, returns `t:Nostrum.Api.error/0`. + """ + @spec emojis(Guild.id()) :: Api.error() | {:ok, [Emoji.t()]} + def emojis(guild_id) do + Api.request(:get, Constants.guild_emojis(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, Emoji}}) + end + + @doc """ + Gets a list of a guild's members. + + If successful, returns `{:ok, members}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:limit` (integer) - max number of members to return (1-1000) (default: 1) + * `:after` (`t:Nostrum.Struct.User.id/0`) - the highest user id in the previous page (default: 0) + + ## Examples + + ```elixir + Nostrum.Api.Guild.members(41771983423143937, limit: 1) + ``` + """ + @spec members(Guild.id(), Api.options()) :: Api.error() | {:ok, [Member.t()]} + def members(guild_id, options \\ %{}) + + def members(guild_id, options) when is_list(options), + do: members(guild_id, Map.new(options)) + + def members(guild_id, %{} = options) when is_snowflake(guild_id) do + Api.request(:get, Constants.guild_members(guild_id), "", options) + |> Api.handle_request_with_decode({:list, {:struct, Member}}) + end + + @doc """ + Gets a list of voice regions. + """ + @spec voice_regions() :: Api.error() | {:ok, [VoiceRegion.t()]} + def voice_regions do + Api.request(:get, Constants.regions()) + |> Api.handle_request_with_decode({:list, {:struct, VoiceRegion}}) + end + + @doc """ + Modifies the nickname of the current user in a guild. + + If successful, returns `{:ok, %{nick: nick}}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:nick` (string) - value to set users nickname to + + ## Examples + + ```elixir + Nostrum.Api.Guild.modify_self_nick(41771983423143937, nick: "Nostrum") + {:ok, %{nick: "Nostrum"}} + ``` + """ + @spec modify_self_nick(Guild.id(), Api.options()) :: Api.error() | {:ok, %{nick: String.t()}} + def modify_self_nick(guild_id, options \\ %{}) do + Api.request(:patch, Constants.guild_me_nick(guild_id), options) + |> Api.handle_request_with_decode() + end + + @doc """ + Modifies a guild's settings. + + This endpoint requires the `MANAGE_GUILD` permission. It fires the + `t:Nostrum.Consumer.guild_update/0` event. + + An optional `reason` can be provided for the audit log. + + If successful, returns `{:ok, guild}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - guild name + * `:region` (string) - guild voice region id + * `:verification_level` (integer) - verification level + * `:default_message_notifications` (integer) - default message + notification level + * `:explicit_content_filter` (integer) - explicit content filter level + * `:afk_channel_id` (`t:Nostrum.Snowflake.t/0`) - id for afk channel + * `:afk_timeout` (integer) - afk timeout in seconds + * `:icon` (base64 data URI) - 128x128 jpeg image for the guild icon + * `:owner_id` (`t:Nostrum.Snowflake.t/0`) - user id to transfer + guild ownership to (must be owner) + * `:splash` (base64 data URI) - 128x128 jpeg image for the guild splash + (VIP only) + * `:system_channel_id` (`t:Nostrum.Snowflake.t/0`) - the id of the + channel to which system messages are sent + * `:rules_channel_id` (`t:Nostrum.Snowflake.t/0`) - the id of the channel that + is used for rules in public guilds + * `:public_updates_channel_id` (`t:Nostrum.Snowflake.t/0`) - the id of the channel + where admins and moderators receive notices from Discord in public guilds + + ## Examples + + ```elixir + Nostrum.Api.Guild.modify(451824027976073216, name: "Nose Drum") + {:ok, %Nostrum.Struct.Guild{id: 451824027976073216, name: "Nose Drum", ...}} + ``` + """ + @spec modify(Guild.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Guild.rest_guild()} + def modify(guild_id, options \\ [], reason \\ nil) + + def modify(guild_id, options, reason) when is_list(options), + do: modify(guild_id, Map.new(options), reason) + + def modify(guild_id, options, reason) when is_snowflake(guild_id) and is_map(options) do + %{ + method: :patch, + route: Constants.guild(guild_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Guild}) + end + + @doc """ + Reorders a guild's channels. + + This endpoint requires the `MANAGE_CHANNELS` permission. It fires multiple + `t:Nostrum.Consumer.channel_update/0` events. + + If successful, returns `{:ok, channels}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + `positions` is a list of maps that each map a channel id with a position. + + ## Examples + + ```elixir + Nostrum.Api.Guild.modify_channel_positions(279093381723062272, [%{id: 351500354581692420, position: 2}]) + {:ok} + ``` + """ + @spec modify_channel_positions(Guild.id(), [%{id: Channel.id(), position: integer}]) :: + Api.error() | {:ok} + def modify_channel_positions(guild_id, positions) + when is_snowflake(guild_id) and is_list(positions) do + Api.request(:patch, Constants.guild_channels(guild_id), positions) + end + + @doc ~S""" + Modify the given emoji. + + This endpoint requires the `MANAGE_EMOJIS` permission. It fires a + `t:Nostrum.Consumer.guild_emojis_update/0` event. + + An optional `reason` can be provided for the audit log. + + If successful, returns `{:ok, emoji}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - name of the emoji + * `:roles` (list of `t:Nostrum.Snowflake.t/0`) - roles to which this emoji will be whitelisted + + ## Examples + + ```elixir + Nostrum.Api.Guild.modify_emoji(43189401384091, 4314301984301, name: "elixir", roles: []) + ``` + """ + @spec modify_emoji(Guild.id(), Emoji.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Emoji.t()} + def modify_emoji(guild_id, emoji_id, options \\ %{}, reason \\ nil) + + def modify_emoji(guild_id, emoji_id, options, reason) when is_list(options), + do: modify_emoji(guild_id, emoji_id, Map.new(options), reason) + + def modify_emoji(guild_id, emoji_id, options, reason) when is_map(options) do + %{ + method: :patch, + route: Constants.guild_emoji(guild_id, emoji_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Emoji}) + end + + @doc """ + Changes the settings and behaviours for a guild integeration. + + Integration to modify is specified by `guild_id` and `integeration_id`. + + `options` is a map with the following keys: + * `expire_behavior` - Expiry behavior. + * `expire_grace_period` - Period where the integration will ignore elapsed subs. + * `enable_emoticons` - Whether emoticons should be synced. + """ + @spec modify_integration(Guild.id(), Integration.id(), %{ + expire_behaviour: integer, + expire_grace_period: integer, + enable_emoticons: boolean + }) :: Api.error() | {:ok} + def modify_integration(guild_id, integration_id, options) do + Api.request(:patch, Constants.guild_integration(guild_id, integration_id), options) + end + + @doc ~S""" + Modifies a guild member's attributes. + + This endpoint fires the `t:Nostrum.Consumer.guild_member_update/0` event. + It situationally requires the `MANAGE_NICKNAMES`, `MANAGE_ROLES`, + `MUTE_MEMBERS`, `DEAFEN_MEMBERS`, and `MOVE_MEMBERS` permissions. + + If successful, returns `{:ok, member}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + An optional `reason` argument can be given for the audit log. + + ## Options + + * `:nick` (string) - value to set users nickname to + * `:roles` (list of `t:Nostrum.Snowflake.t/0`) - array of role ids the member is assigned + * `:mute` (boolean) - if the user is muted + * `:deaf` (boolean) - if the user is deafened + * `:channel_id` (`t:Nostrum.Snowflake.t/0`) - id of channel to move user to (if they are connected to voice) + * `:communication_disabled_until` (`t:DateTime.t/0` or `nil`) - datetime to disable user communication (timeout) until, or `nil` to remove timeout. + + ## Examples + + ```elixir + Nostrum.Api.Guild.modify_member(41771983423143937, 637162356451, nick: "Nostrum") + {:ok, %Nostrum.Struct.Member{}} + ``` + """ + @spec modify_member(Guild.id(), User.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Member.t()} + def modify_member(guild_id, user_id, options \\ %{}, reason \\ nil) + + def modify_member(guild_id, user_id, options, reason) when is_list(options), + do: modify_member(guild_id, user_id, Map.new(options), reason) + + def modify_member(guild_id, user_id, %{} = options, reason) + when is_snowflake(guild_id) and is_snowflake(user_id) do + options = + options + |> Api.maybe_convert_date_time(:communication_disabled_until) + + Api.request(%{ + method: :patch, + route: Constants.guild_member(guild_id, user_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + }) + |> Api.handle_request_with_decode({:struct, Member}) + end + + @doc ~S""" + Reorders a guild's roles. + + This endpoint requires the `MANAGE_ROLES` permission. It fires multiple + `t:Nostrum.Consumer.guild_role_update/0` events. + + If successful, returns `{:ok, roles}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + `positions` is a list of maps that each map a role id with a position. + + ## Examples + + ```elixir + Nostrum.Api.Guild.modify_role_positions(41771983423143937, [%{id: 41771983423143936, position: 2}]) + ``` + """ + @spec modify_role_positions( + Guild.id(), + [%{id: Role.id(), position: integer}], + AuditLogEntry.reason() + ) :: Api.error() | {:ok, [Role.t()]} + def modify_role_positions(guild_id, positions, reason \\ nil) + when is_snowflake(guild_id) and is_list(positions) do + %{ + method: :patch, + route: Constants.guild_roles(guild_id), + body: positions, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:list, {:struct, Role}}) + end + + @doc """ + Modifies a guild embed. + """ + @spec modify_widget(Guild.id(), map) :: Api.error() | {:ok, map} + def modify_widget(guild_id, options) do + Api.request(:patch, Constants.guild_widget(guild_id), options) + |> Api.handle_request_with_decode() + end + + @doc """ + Removes a ban for a user. + + User to unban is specified by `guild_id` and `user_id`. + An optional `reason` can be specified for the audit log. + """ + @spec unban_member(Guild.id(), User.id(), AuditLogEntry.reason()) :: Api.error() | {:ok} + def unban_member(guild_id, user_id, reason \\ nil) do + Api.request(%{ + method: :delete, + route: Constants.guild_ban(guild_id, user_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc """ + Removes a member from a guild. + + This event requires the `KICK_MEMBERS` permission. It fires a + `t:Nostrum.Consumer.guild_member_remove/0` event. + + An optional reason can be provided for the audit log with `reason`. + + If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + iex> Nostrum.Api.Guild.kick_member(1453827904102291, 18739485766253) + {:ok} + ``` + """ + @spec kick_member(Guild.id(), User.id(), AuditLogEntry.reason()) :: Api.error() | {:ok} + def kick_member(guild_id, user_id, reason \\ nil) + when is_snowflake(guild_id) and is_snowflake(user_id) do + Api.request(%{ + method: :delete, + route: Constants.guild_member(guild_id, user_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc """ + Syncs a guild integration. + + Integration to sync is specified by `guild_id` and `integeration_id`. + """ + @spec sync_integration(Guild.id(), Integration.id()) :: Api.error() | {:ok} + def sync_integration(guild_id, integration_id) do + Api.request(:post, Constants.guild_integration_sync(guild_id, integration_id)) + end end diff --git a/lib/nostrum/api/invite.ex b/lib/nostrum/api/invite.ex new file mode 100644 index 000000000..5e056ff0b --- /dev/null +++ b/lib/nostrum/api/invite.ex @@ -0,0 +1,145 @@ +defmodule Nostrum.Api.Invite do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.Invite + alias Nostrum.Struct.Guild + alias Nostrum.Struct.Channel + alias Nostrum.Struct.Guild.AuditLogEntry + + import Nostrum.Snowflake, only: [is_snowflake: 1] + + @doc ~S""" + Gets an invite by its `invite_code`. + + If successful, returns `{:ok, invite}`. Otherwise, returns a + `t:Nostrum.Api.error/0`. + + ## Options + + * `:with_counts` (boolean) - whether to include member count fields + + ## Examples + + ```elixir + Nostrum.Api.Invite.get("zsjUsC") + + Nostrum.Api.Invite.get("zsjUsC", with_counts: true) + ``` + """ + @spec get(Invite.code(), Api.options()) :: Api.error() | {:ok, Invite.simple_invite()} + def get(invite_code, options \\ []) when is_binary(invite_code) do + Api.request(:get, Constants.invite(invite_code), "", options) + |> Api.handle_request_with_decode({:struct, Invite}) + end + + @doc ~S""" + Deletes an invite by its `invite_code`. + + This endpoint requires the `MANAGE_CHANNELS` permission. + + If successful, returns `{:ok, invite}`. Otherwise, returns a + `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Invite.delete("zsjUsC") + ``` + """ + @spec delete(Invite.code()) :: Api.error() | {:ok, Invite.simple_invite()} + def delete(invite_code) when is_binary(invite_code) do + Api.request(:delete, Constants.invite(invite_code)) + |> Api.handle_request_with_decode({:struct, Invite}) + end + + @doc ~S""" + Gets a list of invites for a guild. + + This endpoint requires the `MANAGE_GUILD` permission. + + If successful, returns `{:ok, invites}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Invite.guild_invites(81384788765712384) + {:ok, [%Nostrum.Struct.Invite{} | _]} + ``` + """ + @spec guild_invites(Guild.id()) :: Api.error() | {:ok, [Invite.detailed_invite()]} + def guild_invites(guild_id) when is_snowflake(guild_id) do + Api.request(:get, Constants.guild_invites(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, Invite}}) + end + + @doc ~S""" + Creates an invite for a guild channel. + + An optional `reason` can be provided for the audit log. + + This endpoint requires the `CREATE_INSTANT_INVITE` permission. + + If successful, returns `{:ok, invite}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:max_age` (integer) - duration of invite in seconds before expiry, or 0 for never. + (default: `86400`) + * `:max_uses` (integer) - max number of uses or 0 for unlimited. + (default: `0`) + * `:temporary` (boolean) - Whether the invite should grant temporary + membership. (default: `false`) + * `:unique` (boolean) - used when creating unique one time use invites. + (default: `false`) + + ## Examples + + ```elixir + Nostrum.Api.Invite.create(41771983423143933) + {:ok, Nostrum.Struct.Invite{}} + + Nostrum.Api.Invite.create(41771983423143933, max_uses: 20) + {:ok, %Nostrum.Struct.Invite{}} + ``` + """ + @spec create(Channel.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Invite.detailed_invite()} + def create(channel_id, options \\ [], reason \\ nil) + + def create(channel_id, options, reason) when is_list(options), + do: create(channel_id, Map.new(options), reason) + + def create(channel_id, options, reason) + when is_snowflake(channel_id) and is_map(options) do + %{ + method: :post, + route: Constants.channel_invites(channel_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Invite}) + end + + @doc ~S""" + Gets a list of invites for a channel. + + This endpoint requires the 'VIEW_CHANNEL' and 'MANAGE_CHANNELS' permissions. + + If successful, returns `{:ok, invite}`. Otherwise, returns a + `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + iex> Nostrum.Api.Invite.channel_invites(43189401384091) + {:ok, [%Nostrum.Struct.Invite{} | _]} + ``` + """ + @spec channel_invites(Channel.id()) :: Api.error() | {:ok, [Invite.detailed_invite()]} + def channel_invites(channel_id) when is_snowflake(channel_id) do + Api.request(:get, Constants.channel_invites(channel_id)) + |> Api.handle_request_with_decode({:list, {:struct, Invite}}) + end +end diff --git a/lib/nostrum/api/message.ex b/lib/nostrum/api/message.ex new file mode 100644 index 000000000..ebcb64752 --- /dev/null +++ b/lib/nostrum/api/message.ex @@ -0,0 +1,366 @@ +defmodule Nostrum.Api.Message do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.Message + alias Nostrum.Struct.Channel + alias Nostrum.Struct.Emoji + alias Nostrum.Struct.User + + import Nostrum.Snowflake, only: [is_snowflake: 1] + + @doc ~S""" + Posts a message to a guild text or DM channel. + + This endpoint requires the `VIEW_CHANNEL` and `SEND_MESSAGES` permissions. It + may situationally need the `SEND_MESSAGES_TTS` permission. It fires the + `t:Nostrum.Consumer.message_create/0` event. + + If `options` is a string, `options` will be used as the message's content. + + If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:content` (string) - the message contents (up to 2000 characters) + * `:nonce` (`t:Nostrum.Snowflake.t/0`) - a nonce that can be used for + optimistic message sending + * `:tts` (boolean) - true if this is a TTS message + * `:file` (`t:Path.t/0` | map) - the path of the file being sent, or a map with the following keys + if sending a binary from memory + * `:name` (string) - the name of the file + * `:body` (string) - binary you wish to send + * `:files` - a list of files where each element is the same format as the `:file` option. If both + `:file` and `:files` are specified, `:file` will be prepended to the `:files` list. + * `:embeds` (`t:Nostrum.Struct.Embed.t/0`) - a list of embedded rich content + * `:allowed_mentions` (`t:allowed_mentions/0`) - see the allowed mentions type documentation + * `:message_reference` (`map`) - See "Message references" below + * `:poll` (`t:Nostrum.Struct.Message.Poll.t/0`) - A poll object to send with the message + + At least one of the following is required: `:content`, `:file`, `:embeds`, `:poll`. + + ### Message reference + + You can create a reply to another message on guilds using this option, given + that you have the ``VIEW_MESSAGE_HISTORY`` permission. To do so, include the + ``message_reference`` field in your call. The complete structure + documentation can be found [on the Discord Developer + Portal](https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure), + but simply passing ``message_id`` will suffice: + + ```elixir + def my_command(msg) do + # Reply to the author - ``msg`` is a ``Nostrum.Struct.Message`` + Nostrum.Api.Message.create( + msg.channel_id, + content: "Hello", + message_reference: %{message_id: msg.id} + ) + end + ``` + + Passing a list will merge the settings provided + + ## Examples + + ```elixir + Nostrum.Api.Message.create(43189401384091, content: "hello world!") + + Nostrum.Api.Message.create(43189401384091, "hello world!") + + import Nostrum.Struct.Embed + embed = + %Nostrum.Struct.Embed{} + |> put_title("embed") + |> put_description("new desc") + Nostrum.Api.Message.create(43189401384091, embeds: [embed]) + + Nostrum.Api.Message.Message.create(43189401384091, file: "/path/to/file.txt") + + Nostrum.Api.Message.create(43189401384091, content: "hello world!", embeds: [embed], file: "/path/to/file.txt") + + Nostrum.Api.Message.create(43189401384091, content: "Hello @everyone", allowed_mentions: :none) + ``` + """ + @spec create(Channel.id() | Message.t(), Api.options() | String.t()) :: + Api.error() | {:ok, Message.t()} + def create(channel_id, options) + + def create(%Message{} = message, options), + do: create(message.channel_id, options) + + def create(channel_id, content) when is_binary(content), + do: create(channel_id, %{content: content}) + + def create(channel_id, options) when is_list(options), + do: create(channel_id, Map.new(options)) + + def create(channel_id, options) when is_snowflake(channel_id) and is_map(options) do + prepared_options = + Api.prepare_allowed_mentions(options) + |> Api.combine_embeds() + |> Api.combine_files() + + Api.request(:post, Constants.channel_messages(channel_id), prepared_options) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc ~S""" + Creates a reaction for a message. + + This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY` + permissions. Additionally, if nobody else has reacted to the message with + the `emoji`, this endpoint requires the `ADD_REACTIONS` permission. It + fires a `t:Nostrum.Consumer.message_reaction_add/0` event. + + If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + # Using a Nostrum.Struct.Emoji. + emoji = %Nostrum.Struct.Emoji{id: 43819043108, name: "foxbot"} + Nostrum.Api.Message.react(123123123123, 321321321321, emoji) + + # Using a base 16 emoji string. + Nostrum.Api.Message.react(123123123123, 321321321321, "\xF0\x9F\x98\x81") + + ``` + + For other emoji string examples, see `t:Nostrum.Struct.Emoji.api_name/0`. + """ + @spec react(Channel.id(), Message.id(), Api.emoji()) :: Api.error() | {:ok} + def react(channel_id, message_id, emoji) + + def react(channel_id, message_id, %Emoji{} = emoji), + do: react(channel_id, message_id, Emoji.api_name(emoji)) + + def react(channel_id, message_id, emoji_api_name) do + Api.request(:put, Constants.channel_reaction_me(channel_id, message_id, emoji_api_name)) + end + + @doc ~S""" + Deletes all reactions from a message. + + This endpoint requires the `VIEW_CHANNEL`, `READ_MESSAGE_HISTORY`, and + `MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove_all/0` event. + + If successful, returns `{:ok}`. Otherwise, return `t:Nostrum.Api.error/0`. + """ + @spec clear_reactions(Channel.id(), Message.id()) :: Api.error() | {:ok} + def clear_reactions(channel_id, message_id) do + Api.request(:delete, Constants.channel_reactions_delete(channel_id, message_id)) + end + + @doc ~S""" + Same as `delete/2`, but takes a `Nostrum.Struct.Message` instead of a + `channel_id` and `message_id`. + """ + @spec delete(Message.t()) :: Api.error() | {:ok} + def delete(%Message{id: id, channel_id: c_id}) do + delete(c_id, id) + end + + @doc ~S""" + Deletes a message. + + This endpoint requires the 'VIEW_CHANNEL' and 'MANAGE_MESSAGES' permission. It + fires the `MESSAGE_DELETE` event. + + If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Message.delete(43189401384091, 43189401384091) + ``` + """ + @spec delete(Channel.id(), Message.id()) :: Api.error() | {:ok} + def delete(channel_id, message_id) + when is_snowflake(channel_id) and is_snowflake(message_id) do + Api.request(:delete, Constants.channel_message(channel_id, message_id)) + end + + @doc ~S""" + Deletes a reaction the current user has made for the message. + + This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY` + permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove/0` event. + + If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + See `react/3` for similar examples. + """ + @spec unreact(Channel.id(), Message.id(), Api.emoji()) :: Api.error() | {:ok} + def unreact(channel_id, message_id, emoji) + + def unreact(channel_id, message_id, %Emoji{} = emoji), + do: unreact(channel_id, message_id, Emoji.api_name(emoji)) + + def unreact(channel_id, message_id, emoji_api_name) do + Api.request(:delete, Constants.channel_reaction_me(channel_id, message_id, emoji_api_name)) + end + + @doc ~S""" + Deletes all reactions of a given emoji from a message. + + This endpoint requires the `MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove_emoji/0` event. + + If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + See `react/3` for similar examples. + """ + @spec delete_emoji_reactions(Channel.id(), Message.id(), Api.emoji()) :: Api.error() | {:ok} + def delete_emoji_reactions(channel_id, message_id, emoji) + + def delete_emoji_reactions(channel_id, message_id, %Emoji{} = emoji), + do: delete_emoji_reactions(channel_id, message_id, Emoji.api_name(emoji)) + + def delete_emoji_reactions(channel_id, message_id, emoji_api_name) do + Api.request( + :delete, + Constants.channel_reactions_delete_emoji(channel_id, message_id, emoji_api_name) + ) + end + + @doc ~S""" + Deletes another user's reaction from a message. + + This endpoint requires the `VIEW_CHANNEL`, `READ_MESSAGE_HISTORY`, and + `MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove/0` event. + + If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + See `react/3` for similar examples. + """ + @spec delete_user_reaction(Channel.id(), Message.id(), Api.emoji(), User.id()) :: + Api.error() | {:ok} + def delete_user_reaction(channel_id, message_id, emoji, user_id) + + def delete_user_reaction(channel_id, message_id, %Emoji{} = emoji, user_id), + do: delete_user_reaction(channel_id, message_id, Emoji.api_name(emoji), user_id) + + def delete_user_reaction(channel_id, message_id, emoji_api_name, user_id) do + Api.request( + :delete, + Constants.channel_reaction(channel_id, message_id, emoji_api_name, user_id) + ) + end + + @doc ~S""" + Edits a previously sent message in a channel. + + This endpoint requires the `VIEW_CHANNEL` permission. It fires the + `t:Nostrum.Consumer.message_update/0` event. + + If `options` is a string, `options` will be used as the message's content. + + If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:content` (string) - the message contents (up to 2000 characters) + * `:embeds` (`t:Nostrum.Struct.Embed.t/0`) - a list of embedded rich content + * `:files` - a list of files where each element is the same format as the + `:file` option. If both `:file` and `:files` are specified, `:file` will be + prepended to the `:files` list. See `create_message/2` for more information. + + Note that if you edit a message with attachments, all attachments that should + be present after edit **must** be included in your request body. This + includes attachments that were sent in the original request. + + ## Examples + + ```elixir + Nostrum.Api.Message.edit(43189401384091, 1894013840914098, content: "hello world!") + + Nostrum.Api.Message.edit(43189401384091, 1894013840914098, "hello world!") + + import Nostrum.Struct.Embed + embed = + %Nostrum.Struct.Embed{} + |> put_title("embed") + |> put_description("new desc") + Nostrum.Api.Message.edit(43189401384091, 1894013840914098, embeds: [embed]) + + Nostrum.Api.Message.edit(43189401384091, 1894013840914098, content: "hello world!", embeds: [embed]) + ``` + """ + @spec edit(Channel.id(), Message.id(), Api.options() | String.t()) :: + Api.error() | {:ok, Message.t()} + def edit(channel_id, message_id, options) + + def edit(channel_id, message_id, content) when is_binary(content), + do: edit(channel_id, message_id, %{content: content}) + + def edit(channel_id, message_id, options) when is_list(options), + do: edit(channel_id, message_id, Map.new(options)) + + def edit(channel_id, message_id, %{} = options) + when is_snowflake(channel_id) and is_snowflake(message_id) do + prepared_options = + Api.prepare_allowed_mentions(options) + |> Api.combine_embeds() + |> Api.combine_files() + + Api.request(:patch, Constants.channel_message(channel_id, message_id), prepared_options) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc ~S""" + Same as `edit/3`, but takes a `Nostrum.Struct.Message` instead of a + `channel_id` and `message_id`. + """ + @spec edit(Message.t(), Api.options()) :: Api.error() | {:ok, Message.t()} + def edit(%Message{id: id, channel_id: c_id}, options) do + edit(c_id, id, options) + end + + @doc ~S""" + Retrieves a message from a channel. + + This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions. + + If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Message.get(43189401384091, 198238475613443) + ``` + """ + @spec get(Channel.id(), Message.id()) :: Api.error() | {:ok, Message.t()} + def get(channel_id, message_id) + when is_snowflake(channel_id) and is_snowflake(message_id) do + Api.request(:get, Constants.channel_message(channel_id, message_id)) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc ~S""" + Gets all users who reacted with an emoji. + + This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY` permissions. + + If successful, returns `{:ok, users}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + The optional `params` are `after`, the user ID to query after, absent by default, + and `limit`, the max number of users to return, 1-100, 25 by default. + + See `react/3` for similar examples. + """ + @spec reactions(Channel.id(), Message.id(), Api.emoji(), keyword()) :: + Api.error() | {:ok, [User.t()]} + def reactions(channel_id, message_id, emoji, params \\ []) + + def reactions(channel_id, message_id, %Emoji{} = emoji, params), + do: reactions(channel_id, message_id, Emoji.api_name(emoji), params) + + def reactions(channel_id, message_id, emoji_api_name, params) do + Api.request( + :get, + Constants.channel_reactions_get(channel_id, message_id, emoji_api_name), + "", + params + ) + |> Api.handle_request_with_decode({:list, {:struct, User}}) + end +end diff --git a/lib/nostrum/api/poll.ex b/lib/nostrum/api/poll.ex new file mode 100644 index 000000000..2ae2a5c79 --- /dev/null +++ b/lib/nostrum/api/poll.ex @@ -0,0 +1,47 @@ +defmodule Nostrum.Api.Poll do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Util + alias Nostrum.Struct.Message + alias Nostrum.Struct.Channel + alias Nostrum.Struct.Message.Poll.Answer + alias Nostrum.Struct.User + + @doc ~S""" + Expire (close voting on) a poll before the scheduled end time. + + Returns the original message containing the poll. + """ + @spec expire(Channel.id(), Message.id()) :: Api.error() | {:ok, Message.t()} + def expire(channel_id, message_id) do + Api.request(:post, Constants.poll_expire(channel_id, message_id)) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc ~S""" + Get voters for the provided answer on the poll attached to the provided message. + + If successful, returns `{:ok, users}`. Otherwise, returns `t:Nostrum.Api.error/0`. + + The optional `params` are `after`, the user ID to query after, absent by default, + and `limit`, the max number of users to return, 1-100, 25 by default. Results are + sorted by Discord user snowflake (ID) in ascending order. + """ + @spec answer_voters(Channel.id(), Message.id(), Answer.answer_id()) :: + Api.error() | {:ok, [User.t()]} + def answer_voters(channel_id, message_id, answer_id, params \\ []) do + result = + Api.request( + :get, + Constants.poll_answer_voters(channel_id, message_id, answer_id), + "", + params + ) + |> Api.handle_request_with_decode() + + case result do + {:ok, %{users: users}} -> {:ok, Util.cast(users, {:list, {:struct, User}})} + _ -> result + end + end +end diff --git a/lib/nostrum/api/role.ex b/lib/nostrum/api/role.ex new file mode 100644 index 000000000..fece5bee9 --- /dev/null +++ b/lib/nostrum/api/role.ex @@ -0,0 +1,164 @@ +defmodule Nostrum.Api.Role do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.Guild.AuditLogEntry + alias Nostrum.Struct.Guild.Role + alias Nostrum.Struct.Guild + alias Nostrum.Struct.User + + import Nostrum.Snowflake, only: [is_snowflake: 1] + + @doc """ + Adds a role to a member. + + Role to add is specified by `role_id`. + User to add role to is specified by `guild_id` and `user_id`. + An optional `reason` can be given for the audit log. + """ + @spec add_member(Guild.id(), User.id(), Role.id(), AuditLogEntry.reason()) :: + Api.error() | {:ok} + def add_member(guild_id, user_id, role_id, reason \\ nil) do + Api.request(%{ + method: :put, + route: Constants.guild_member_role(guild_id, user_id, role_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc ~S""" + Creates a guild role. + + An optional reason for the audit log can be provided via `reason`. + + This endpoint requires the `MANAGE_ROLES` permission. It fires a + `t:Nostrum.Consumer.guild_role_create/0` event. + + If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - name of the role (default: "new role") + * `:permissions` (integer) - bitwise of the enabled/disabled permissions (default: @everyone perms) + * `:color` (integer) - RGB color value (default: 0) + * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar (default: false) + * `:mentionable` (boolean) - whether the role should be mentionable (default: false) + * `:icon` (string) - URL role icon (default: `nil`) + * `:unicode_emoji` (string) - standard unicode character emoji role icon (default: `nil`) + + ## Examples + + ```elixir + Nostrum.Api.Role.create(41771983423143937, name: "nostrum-club", hoist: true) + ``` + """ + @spec create(Guild.id(), Api.options(), AuditLogEntry.reason()) :: Api.error() | {:ok, Role.t()} + def create(guild_id, options, reason \\ nil) + + def create(guild_id, options, reason) when is_list(options), + do: create(guild_id, Map.new(options), reason) + + def create(guild_id, %{} = options, reason) when is_snowflake(guild_id) do + %{ + method: :post, + route: Constants.guild_roles(guild_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Role}) + end + + @doc ~S""" + Deletes a role from a guild. + + An optional `reason` can be specified for the audit log. + + This endpoint requires the `MANAGE_ROLES` permission. It fires a + `t:Nostrum.Consumer.guild_role_delete/0` event. + + If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Role.delete(41771983423143937, 392817238471936) + ``` + """ + @spec delete(Guild.id(), Role.id(), AuditLogEntry.reason()) :: Api.error() | {:ok} + def delete(guild_id, role_id, reason \\ nil) + when is_snowflake(guild_id) and is_snowflake(role_id) do + Api.request(%{ + method: :delete, + route: Constants.guild_role(guild_id, role_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc ~S""" + Modifies a guild role. + + This endpoint requires the `MANAGE_ROLES` permission. It fires a + `t:Nostrum.Consumer.guild_role_update/0` event. + + An optional `reason` can be specified for the audit log. + + If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - name of the role + * `:permissions` (integer) - bitwise of the enabled/disabled permissions + * `:color` (integer) - RGB color value (default: 0) + * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar + * `:mentionable` (boolean) - whether the role should be mentionable + + ## Examples + + ```elixir + Nostrum.Api.Role.modify(41771983423143937, 392817238471936, hoist: false, name: "foo-bar") + ``` + """ + @spec modify(Guild.id(), Role.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Role.t()} + def modify(guild_id, role_id, options, reason \\ nil) + + def modify(guild_id, role_id, options, reason) when is_list(options), + do: modify(guild_id, role_id, Map.new(options), reason) + + def modify(guild_id, role_id, %{} = options, reason) + when is_snowflake(guild_id) and is_snowflake(role_id) do + %{ + method: :patch, + route: Constants.guild_role(guild_id, role_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Role}) + end + + @doc """ + Removes a role from a member. + + Role to remove is specified by `role_id`. + User to remove role from is specified by `guild_id` and `user_id`. + An optional `reason` can be given for the audit log. + """ + @spec remove_member(Guild.id(), User.id(), Role.id(), AuditLogEntry.reason()) :: + Api.error() | {:ok} + def remove_member(guild_id, user_id, role_id, reason \\ nil) do + Api.request(%{ + method: :delete, + route: Constants.guild_member_role(guild_id, user_id, role_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end +end diff --git a/lib/nostrum/api/scheduled_event.ex b/lib/nostrum/api/scheduled_event.ex new file mode 100644 index 000000000..581c82767 --- /dev/null +++ b/lib/nostrum/api/scheduled_event.ex @@ -0,0 +1,136 @@ +defmodule Nostrum.Api.ScheduledEvent do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.Guild + alias Nostrum.Struct.Guild.AuditLogEntry + alias Nostrum.Struct.Guild.ScheduledEvent + + @doc """ + Creates a new scheduled event for the guild. + + ## Options + * `:channel_id` - (`t:Nostrum.Snowflake.t/0`) optional channel id for the event + * `:entity_metadata` - (`t:Nostrum.Struct.Guild.ScheduledEvent.EntityMetadata.t/0`) metadata for the event + * `:name` - (string) required name for the event + * `:privacy_level` - (integer) at the time of writing, this must always be 2 for `GUILD_ONLY` + * `:scheduled_start_time` - required time for the event to start as a `DateTime` or (ISO8601 timestamp)[`DateTime.to_iso8601/3`] + * `:scheduled_end_time` - optional time for the event to end as a `DateTime` or (ISO8601 timestamp)[`DateTime.to_iso8601/3`] + * `:description` - (string) optional description for the event + * `:entity_type` - (integer) an integer representing the type of entity the event is for + * `1` - `STAGE_INSTANCE` + * `2` - `VOICE` + * `3` - `EXTERNAL` + + See the (official documentation)[https://discord.com/developers/docs/resources/guild-scheduled-event] for more information. + + + An optional `reason` can be specified for the audit log. + """ + @doc since: "0.5.0" + @spec create(Guild.id(), AuditLogEntry.reason(), Api.options()) :: + {:ok, ScheduledEvent.t()} | Api.error() + def create(guild_id, reason \\ nil, options) + + def create(guild_id, reason, options) when is_list(options), + do: create(guild_id, reason, Map.new(options)) + + def create(guild_id, reason, %{} = options) do + options = + options + |> Api.maybe_convert_date_time(:scheduled_start_time) + |> Api.maybe_convert_date_time(:scheduled_end_time) + + Api.request(%{ + method: :post, + route: Constants.guild_scheduled_events(guild_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + }) + |> Api.handle_request_with_decode({:struct, ScheduledEvent}) + end + + @doc """ + Delete a scheduled event for a guild. + """ + @doc since: "0.5.0" + @spec delete(Guild.id(), ScheduledEvent.id()) :: + Api.error() | {:ok} + def delete(guild_id, event_id) do + Api.request(:delete, Constants.guild_scheduled_event(guild_id, event_id)) + end + + @doc """ + Get a scheduled event for a guild. + """ + @doc since: "0.5.0" + @spec get(Guild.id(), ScheduledEvent.id()) :: + Api.error() | {:ok, ScheduledEvent.t()} + def get(guild_id, event_id) do + Api.request(:get, Constants.guild_scheduled_event(guild_id, event_id)) + |> Api.handle_request_with_decode({:struct, ScheduledEvent}) + end + + @doc """ + Get a list of users who have subscribed to an event. + + ## Options + All are optional, with their default values listed. + * `:limit` (integer) maximum number of users to return, defaults to `100` + * `:with_member` (boolean) whether to include the member object for each user, defaults to `false` + * `:before` (`t:Nostrum.Snowflake.t/0`) return only users before this user id, defaults to `nil` + * `:after` (`t:Nostrum.Snowflake.t/0`) return only users after this user id, defaults to `nil` + """ + @doc since: "0.5.0" + @spec users(Guild.id(), ScheduledEvent.id(), Api.options()) :: + Api.error() | {:ok, [ScheduledEvent.User.t()]} + def users(guild_id, event_id, params \\ []) do + Api.request(:get, Constants.guild_scheduled_event_users(guild_id, event_id), "", params) + |> Api.handle_request_with_decode({:list, {:struct, ScheduledEvent.User}}) + end + + @doc """ + Modify a scheduled event for a guild. + + Options are the same as for `create_guild_scheduled_event/2` except all fields are optional, + with the additional optional integer field `:status` which can be one of: + + * `1` - `SCHEDULED` + * `2` - `ACTIVE` + * `3` - `COMPLETED` + * `4` - `CANCELLED` + + Copied from the official documentation: + * If updating entity_type to `EXTERNAL`: + * `channel_id` is required and must be set to null + * `entity_metadata` with a `location` field must be provided + * `scheduled_end_time` must be provided + """ + @doc since: "0.5.0" + @spec modify( + Guild.id(), + ScheduledEvent.id(), + AuditLogEntry.reason(), + Api.options() + ) :: Api.error() | {:ok, ScheduledEvent.t()} + def modify(guild_id, event_id, reason \\ nil, options) + + def modify(guild_id, event_id, reason, options) when is_list(options), + do: modify(guild_id, event_id, reason, Map.new(options)) + + def modify(guild_id, event_id, reason, options) when is_map(options) do + prepared_options = + options + |> Api.maybe_convert_date_time(:scheduled_start_time) + |> Api.maybe_convert_date_time(:scheduled_end_time) + + Api.request(%{ + method: :patch, + route: Constants.guild_scheduled_event(guild_id, event_id), + body: prepared_options, + params: [], + headers: Api.maybe_add_reason(reason) + }) + |> Api.handle_request_with_decode({:struct, ScheduledEvent}) + end +end diff --git a/lib/nostrum/api/self.ex b/lib/nostrum/api/self.ex new file mode 100644 index 000000000..3a20c1165 --- /dev/null +++ b/lib/nostrum/api/self.ex @@ -0,0 +1,137 @@ +defmodule Nostrum.Api.Self do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.User + alias Nostrum.Struct.Channel + alias Nostrum.Shard.Session + alias Nostrum.Shard.Supervisor + + @doc """ + Gets the bot's OAuth2 application info. + + ## Example + ```elixir + Nostrum.Api.Self.application_information + {:ok, + %{ + bot_public: false, + bot_require_code_grant: false, + description: "Test", + icon: nil, + id: "172150183260323840", + name: "Baba O-Riley", + owner: %{ + avatar: nil, + discriminator: "0042", + id: "172150183260323840", + username: "i own a bot" + }, + }} + ``` + """ + @spec application_information() :: Api.error() | {:ok, map()} + def application_information do + Api.request(:get, Constants.application_information()) + |> Api.handle_request_with_decode() + end + + @doc """ + Gets info on the current user. + + If nostrum's caching is enabled, it is recommended to use `Me.get/0` + instead of this function. This is because sending out an API request is much slower + than pulling from our cache. + + If the request is successful, this function returns `{:ok, user}`, where + `user` is nostrum's `Nostrum.Struct.User`. Otherwise, returns `{:error, reason}`. + """ + @spec get() :: Api.error() | {:ok, User.t()} + def get do + Api.request(:get, Constants.me()) + |> Api.handle_request_with_decode({:struct, User}) + end + + @doc """ + Gets a list of our user's DM channels. + + If successful, returns `{:ok, dm_channels}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Self.dms() + {:ok, [%Nostrum.Struct.Channel{type: 1} | _]} + ``` + """ + @spec dms() :: Api.error() | {:ok, [Channel.dm_channel()]} + def dms do + Api.request(:get, Constants.me_channels()) + |> Api.handle_request_with_decode({:list, {:struct, Channel}}) + end + + @doc ~S""" + Changes the username or avatar of the current user. + + ## Options + + * `:username` (string) - new username + * `:avatar` (string) - the user's avatar as [avatar data](https://discord.com/developers/docs/resources/user#avatar-data) + + ## Examples + + ```elixir + Nostrum.Api.Self.modify(avatar: "data:image/jpeg;base64,YXl5IGJieSB1IGx1a2luIDQgc3VtIGZ1az8=") + ``` + """ + @spec modify(Api.options()) :: Api.error() | {:ok, User.t()} + def modify(options) + + def modify(options) when is_list(options), + do: modify(Map.new(options)) + + def modify(options) when is_map(options) do + Api.request(:patch, Constants.me(), options) + |> Api.handle_request_with_decode({:struct, User}) + end + + @doc """ + Updates the status of the bot for a certain shard. + + ## Parameters + - `pid` - Pid of the shard. + - `status` - Status of the bot. + - `game` - The 'playing' text of the bot. Empty will clear. + - `type` - The type of status to show. 0 (Playing) | 1 (Streaming) | 2 (Listening) | 3 (Watching) + - `stream` - URL of twitch.tv stream + """ + @spec update_shard_status(pid, Api.status(), String.t(), integer, String.t() | nil) :: :ok + def update_shard_status(pid, status, game, type \\ 0, stream \\ nil) do + Session.update_status(pid, to_string(status), game, stream, type) + :ok + end + + @doc """ + Updates the status of the bot for all shards. + + See `update_shard_status/5` for usage. + """ + @spec update_status(Api.status(), String.t(), integer, String.t() | nil) :: :ok + def update_status(status, game, type \\ 0, stream \\ nil) do + _result = Supervisor.update_status(to_string(status), game, stream, type) + :ok + end + + @doc """ + Joins, moves, or disconnects the bot from a voice channel. + + The correct shard to send the update to will be inferred from the + `guild_id`. If a corresponding `guild_id` is not found a cache error will be + raised. + + To disconnect from a channel, `channel_id` should be set to `nil`. + """ + @spec update_voice_state(Guild.id(), Channel.id() | nil, boolean, boolean) :: no_return | :ok + def update_voice_state(guild_id, channel_id, self_mute \\ false, self_deaf \\ false) do + Supervisor.update_voice_state(guild_id, channel_id, self_mute, self_deaf) + end +end From a40b47fc6b6855a573a495721f1343dab346fa05 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 10:21:03 -0700 Subject: [PATCH 13/42] Extract functions for Nostrum.Api.Thread --- lib/nostrum/api.ex | 152 +++-------------- lib/nostrum/api/thread.ex | 339 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 358 insertions(+), 133 deletions(-) create mode 100644 lib/nostrum/api/thread.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index cbd1ebe8a..bdee9c314 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -70,7 +70,7 @@ defmodule Nostrum.Api do alias Nostrum.Struct.Guild.{AuditLog, AuditLogEntry, Member, Role, ScheduledEvent} - defguardp has_files(args) when is_map_key(args, :files) or is_map_key(args, :file) + defguard has_files(args) when is_map_key(args, :files) or is_map_key(args, :file) def handle_request_with_decode(response) def handle_request_with_decode({:ok, body}), do: {:ok, Jason.decode!(body, keys: :atoms)} @@ -3661,50 +3661,7 @@ defmodule Nostrum.Api do ) :: {:ok, Channel.t()} | error def start_thread_with_message(channel_id, message_id, options, reason \\ nil) do - request(%{ - method: :post, - route: Constants.thread_with_message(channel_id, message_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - }) - |> handle_request_with_decode({:struct, Channel}) - end - - @type thread_without_message_params :: %{ - required(:name) => String.t(), - required(:type) => non_neg_integer(), - optional(:auto_archive_duration) => 60 | 1440 | 4320 | 10_080, - optional(:invitable) => boolean(), - optional(:rate_limit_per_user) => 0..21_600 - } - - @doc """ - Create a thread on a channel without an associated message. - - If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`. - - An optional `reason` argument can be given for the audit log. - - ## Options - - `name`: Name of the thread, max 100 characters. - - `type`: Type of thread, can be either 11 (`GUILD_PUBLIC_THREAD`) or 12 (`GUILD_PRIVATE_THREAD`). - - `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080. - - `invitable`: whether non-moderators can add other non-moderators to a thread; only available when creating a private thread defaults to `false`. - - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. - """ - @doc since: "0.5.1" - @spec start_thread(Channel.id(), thread_without_message_params, AuditLogEntry.reason()) :: - {:ok, Channel.t()} | error - def start_thread(channel_id, options, reason \\ nil) do - request(%{ - method: :post, - route: Constants.thread_without_message(channel_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - }) - |> handle_request_with_decode({:struct, Channel}) + Nostrum.Api.Thread.create_with_message(channel_id, message_id, options, reason) end @doc """ @@ -3735,40 +3692,8 @@ defmodule Nostrum.Api do @doc since: "0.7.0" @spec start_thread_in_forum_channel(Channel.id(), map(), AuditLogEntry.reason()) :: {:ok, Channel.t()} | error - def start_thread_in_forum_channel(channel_id, options, reason \\ nil) - - def start_thread_in_forum_channel(channel_id, %{message: data} = body, reason) - when has_files(data) do - # done this way to avoid breaking changes to support audit log reasons in multipart requests - boundary = generate_boundary() - {files, json} = combine_files(body) |> pop_files() - body = Jason.encode_to_iodata!(json) - - headers = - maybe_add_reason(reason, [ - {"content-type", "multipart/form-data; boundary=#{boundary}"} - ]) - - %{ - method: :post, - route: Constants.thread_without_message(channel_id), - body: {:multipart, create_multipart(files, body, boundary)}, - params: [], - headers: headers - } - |> request() - |> handle_request_with_decode({:struct, Channel}) - end - - def start_thread_in_forum_channel(channel_id, options, reason) do - request(%{ - method: :post, - route: Constants.thread_without_message(channel_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - }) - |> handle_request_with_decode({:struct, Channel}) + def start_thread_in_forum_channel(channel_id, options, reason \\ nil) do + Nostrum.Api.Thread.create_in_forum(channel_id, options, reason) end @doc """ @@ -3802,22 +3727,7 @@ defmodule Nostrum.Api do @spec list_guild_threads(Guild.id()) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()]}} | error def list_guild_threads(guild_id) do - res = - request(:get, Constants.guild_active_threads(guild_id)) - |> handle_request_with_decode - - case res do - {:ok, %{threads: channels, members: thread_members}} -> - map = %{ - threads: Util.cast(channels, {:list, {:struct, Channel}}), - members: Util.cast(thread_members, {:list, {:struct, ThreadMember}}) - } - - {:ok, map} - - {:error, e} -> - {:error, e} - end + Nostrum.Api.Thread.list(guild_id) end @doc """ @@ -3839,16 +3749,8 @@ defmodule Nostrum.Api do @spec list_public_archived_threads(Channel.id(), options) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} | error - def list_public_archived_threads(channel_id, options \\ []) - - def list_public_archived_threads(channel_id, options) when is_map(options) do - Constants.public_archived_threads(channel_id) - |> list_archived_threads(Map.to_list(options)) - end - - def list_public_archived_threads(channel_id, options) when is_list(options) do - Constants.public_archived_threads(channel_id) - |> list_archived_threads(options) + def list_public_archived_threads(channel_id, options \\ []) do + Nostrum.Api.Thread.public_archived_threads(channel_id, options) end @doc """ @@ -3858,16 +3760,8 @@ defmodule Nostrum.Api do @spec list_private_archived_threads(Channel.id(), options) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} | error - def list_private_archived_threads(channel_id, options \\ []) - - def list_private_archived_threads(channel_id, options) when is_map(options) do - Constants.private_archived_threads(channel_id) - |> list_archived_threads(Map.to_list(options)) - end - - def list_private_archived_threads(channel_id, options) when is_list(options) do - Constants.private_archived_threads(channel_id) - |> list_archived_threads(options) + def list_private_archived_threads(channel_id, options \\ []) do + Nostrum.Api.Thread.private_archived_threads(channel_id, options) end @doc """ @@ -3877,16 +3771,8 @@ defmodule Nostrum.Api do @spec list_joined_private_archived_threads(Channel.id(), options) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} | error - def list_joined_private_archived_threads(channel_id, options \\ []) - - def list_joined_private_archived_threads(channel_id, options) when is_map(options) do - Constants.private_joined_archived_threads(channel_id) - |> list_archived_threads(Map.to_list(options)) - end - - def list_joined_private_archived_threads(channel_id, options) when is_list(options) do - Constants.private_joined_archived_threads(channel_id) - |> list_archived_threads(options) + def list_joined_private_archived_threads(channel_id, options \\ []) do + Nostrum.Api.Thread.joined_private_archived_threads(channel_id, options) end defp list_archived_threads(route, options) do @@ -3923,7 +3809,7 @@ defmodule Nostrum.Api do @doc since: "0.5.1" @spec join_thread(Channel.id()) :: {:ok} | error def join_thread(thread_id) do - request(:put, Constants.thread_member_me(thread_id)) + Nostrum.Api.Thread.join(thread_id) end @doc """ @@ -3940,7 +3826,7 @@ defmodule Nostrum.Api do @doc since: "0.5.1" @spec leave_thread(Channel.id()) :: {:ok} | error def leave_thread(thread_id) do - request(:delete, Constants.thread_member_me(thread_id)) + Nostrum.Api.Thread.leave(thread_id) end @doc """ @@ -3951,7 +3837,7 @@ defmodule Nostrum.Api do @doc since: "0.5.1" @spec remove_thread_member(Channel.id(), User.id()) :: {:ok} | error def remove_thread_member(thread_id, user_id) do - request(:delete, Constants.thread_member(thread_id, user_id)) + Nostrum.Api.Thread.remove_member(thread_id, user_id) end @doc """ @@ -4099,13 +3985,13 @@ defmodule Nostrum.Api do def combine_files(%{message: data} = args), do: %{args | message: combine_files(data)} def combine_files(args), do: args - defp pop_files(%{data: data} = args), + def pop_files(%{data: data} = args), do: {data.files, %{args | data: Map.delete(data, :files)}} - defp pop_files(%{message: data} = args), + def pop_files(%{message: data} = args), do: {data.files, %{args | message: Map.delete(data, :files)}} - defp pop_files(args), do: Map.pop!(args, :files) + def pop_files(args), do: Map.pop!(args, :files) @doc false def bangify(to_bang) do @@ -4136,7 +4022,7 @@ defmodule Nostrum.Api do end end - defp create_multipart(files, json, boundary) do + def create_multipart(files, json, boundary) do json_mime = MIME.type("json") json_size = :erlang.iolist_size(json) @@ -4186,7 +4072,7 @@ defmodule Nostrum.Api do defp get_file_contents(%{body: body, name: name}), do: {body, name} - defp generate_boundary do + def generate_boundary do String.duplicate("-", 20) <> "KraigieNostrumCat_" <> Base.encode16(:crypto.strong_rand_bytes(10)) diff --git a/lib/nostrum/api/thread.ex b/lib/nostrum/api/thread.ex new file mode 100644 index 000000000..691bd9509 --- /dev/null +++ b/lib/nostrum/api/thread.ex @@ -0,0 +1,339 @@ +defmodule Nostrum.Api.Thread do + alias Nostrum.Api + alias Nostrum.Util + alias Nostrum.Constants + alias Nostrum.Struct.Channel + alias Nostrum.Struct.Guild + alias Nostrum.Struct.Guild.AuditLogEntry + alias Nostrum.Struct.User + alias Nostrum.Struct.ThreadMember + alias Nostrum.Struct.Message + + import Api, only: [has_files: 1] + + @doc """ + Add a user to a thread, requires the ability to send messages in the thread. + """ + @doc since: "0.5.1" + @spec add_member(Channel.id(), User.id()) :: {:ok} | Api.error() + def add_member(thread_id, user_id) do + Api.request(:put, Constants.thread_member(thread_id, user_id)) + end + + @doc """ + Returns a thread member object for the specified user if they are a member of the thread + """ + @doc since: "0.5.1" + @spec member(Channel.id(), User.id()) :: {:ok, ThreadMember.t()} | Api.error() + def member(thread_id, user_id) do + Api.request(:get, Constants.thread_member(thread_id, user_id)) + |> Api.handle_request_with_decode({:struct, ThreadMember}) + end + + @doc """ + Returns a list of thread members for the specified thread. + + This endpoint is restricted according to whether the `GUILD_MEMBERS` privileged intent is enabled. + """ + @doc since: "0.5.1" + @spec members(Channel.id()) :: {:ok, [ThreadMember.t()]} | Api.error() + def members(thread_id) do + Api.request(:get, Constants.thread_members(thread_id)) + |> Api.handle_request_with_decode({:list, {:struct, ThreadMember}}) + end + + @doc """ + Join an existing thread, requires that the thread is not archived. + """ + @doc since: "0.5.1" + @spec join(Channel.id()) :: {:ok} | Api.error() + def join(thread_id) do + Api.request(:put, Constants.thread_member_me(thread_id)) + end + + @doc """ + Leave a thread, requires that the thread is not archived. + """ + @doc since: "0.5.1" + @spec leave(Channel.id()) :: {:ok} | Api.error() + def leave(thread_id) do + Api.request(:delete, Constants.thread_member_me(thread_id)) + end + + @doc """ + Return all active threads for the current guild. + + Response body is a map with the following keys: + - `threads`: A list of channel objects. + - `members`: A list of `ThreadMember` objects, one for each returned thread the current user has joined. + """ + @doc since: "0.5.1" + @spec list(Guild.id()) :: + {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()]}} | Api.error() + def list(guild_id) do + res = + Api.request(:get, Constants.guild_active_threads(guild_id)) + |> Api.handle_request_with_decode() + + case res do + {:ok, %{threads: channels, members: thread_members}} -> + map = %{ + threads: Util.cast(channels, {:list, {:struct, Channel}}), + members: Util.cast(thread_members, {:list, {:struct, ThreadMember}}) + } + + {:ok, map} + + {:error, e} -> + {:error, e} + end + end + + @doc """ + Same as `public_archived_threads/2`, but only returns private threads that the current user has joined. + """ + @doc since: "0.5.1" + @spec joined_private_archived_threads(Channel.id(), Api.options()) :: + {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} + | Api.error() + def joined_private_archived_threads(channel_id, options \\ []) + + def joined_private_archived_threads(channel_id, options) when is_map(options) do + Constants.private_joined_archived_threads(channel_id) + |> list_archived_threads(Map.to_list(options)) + end + + def joined_private_archived_threads(channel_id, options) when is_list(options) do + Constants.private_joined_archived_threads(channel_id) + |> list_archived_threads(options) + end + + defp list_archived_threads(route, options) do + options = options |> Api.maybe_convert_date_time(:before) + + res = + Api.request(%{ + method: :get, + route: route, + body: "", + params: options, + headers: [] + }) + |> Api.handle_request_with_decode() + + case res do + {:ok, %{threads: channels, members: thread_members, has_more: has_more}} -> + map = %{ + threads: Util.cast(channels, {:list, {:struct, Channel}}), + members: Util.cast(thread_members, {:list, {:struct, ThreadMember}}), + has_more: has_more + } + + {:ok, map} + + {:error, e} -> + {:error, e} + end + end + + @doc """ + Same as `public_archived_threads/2`, but for private threads instead of public. + """ + @doc since: "0.5.1" + @spec private_archived_threads(Channel.id(), Api.options()) :: + {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} + | Api.error() + def private_archived_threads(channel_id, options \\ []) + + def private_archived_threads(channel_id, options) when is_map(options) do + Constants.private_archived_threads(channel_id) + |> list_archived_threads(Map.to_list(options)) + end + + def private_archived_threads(channel_id, options) when is_list(options) do + Constants.private_archived_threads(channel_id) + |> list_archived_threads(options) + end + + @doc """ + Returns a list of archived threads for a given channel. + + Threads are sorted by the `archive_timestamp` field, in descending order. + + ## Response body + Response body is a map with the following keys: + - `threads`: A list of channel objects. + - `members`: A list of `ThreadMember` objects, one for each returned thread the current user has joined. + - `has_more`: A boolean indicating whether there are more archived threads that can be fetched. + + ## Options + - `before`: Returns threads before this timestamp, can be either a `DateTime` or [ISO8601 timestamp](`DateTime.to_iso8601/3`). + - `limit`: Optional maximum number of threads to return. + """ + @doc since: "0.5.1" + @spec public_archived_threads(Channel.id(), Api.options()) :: + {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} + | Api.error() + def public_archived_threads(channel_id, options \\ []) + + def public_archived_threads(channel_id, options) when is_map(options) do + Constants.public_archived_threads(channel_id) + |> list_archived_threads(Map.to_list(options)) + end + + def public_archived_threads(channel_id, options) when is_list(options) do + Constants.public_archived_threads(channel_id) + |> list_archived_threads(options) + end + + @doc """ + Removes another user from a thread, requires that the thread is not archived. + + Also requires the `MANAGE_THREADS` permission, or the creator of the thread if the thread is private. + """ + @doc since: "0.5.1" + @spec remove_member(Channel.id(), User.id()) :: {:ok} | Api.error() + def remove_member(thread_id, user_id) do + Api.request(:delete, Constants.thread_member(thread_id, user_id)) + end + + @type thread_without_message_params :: %{ + required(:name) => String.t(), + required(:type) => non_neg_integer(), + optional(:auto_archive_duration) => 60 | 1440 | 4320 | 10_080, + optional(:invitable) => boolean(), + optional(:rate_limit_per_user) => 0..21_600 + } + + @doc """ + Create a thread on a channel without an associated message. + + If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`. + + An optional `reason` argument can be given for the audit log. + + ## Options + - `name`: Name of the thread, max 100 characters. + - `type`: Type of thread, can be either 11 (`GUILD_PUBLIC_THREAD`) or 12 (`GUILD_PRIVATE_THREAD`). + - `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080. + - `invitable`: whether non-moderators can add other non-moderators to a thread; only available when creating a private thread defaults to `false`. + - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. + """ + @doc since: "0.5.1" + @spec create(Channel.id(), thread_without_message_params, AuditLogEntry.reason()) :: + {:ok, Channel.t()} | Api.error() + def create(channel_id, options, reason \\ nil) do + Api.request(%{ + method: :post, + route: Constants.thread_without_message(channel_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + }) + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @doc """ + Create a new thread in a forum channel. + + If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`. + + An optional `reason` argument can be given for the audit log. + + ## Options + - `name`: Name of the thread, max 100 characters. + - `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080. + - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. + - `applied_tags`: An array of tag ids to apply to the thread. + - `message`: The first message in the created thread. + + ### Thread Message Options + - `content`: The content of the message. + - `embeds`: A list of embeds. + - `allowed_mentions`: Allowed mentions object. + - `components`: A list of components. + - `sticker_ids`: A list of sticker ids. + - `:files` - a list of files where each element is the same format as the `:file` option. If both + `:file` and `:files` are specified, `:file` will be prepended to the `:files` list. + + At least one of `content`, `embeds`, `sticker_ids`, or `files` must be specified. + """ + @doc since: "0.7.0" + @spec create_in_forum(Channel.id(), map(), AuditLogEntry.reason()) :: + {:ok, Channel.t()} | Api.error() + def create_in_forum(channel_id, options, reason \\ nil) + + def create_in_forum(channel_id, %{message: data} = body, reason) + when has_files(data) do + # done this way to avoid breaking changes to support audit log reasons in multipart requests + boundary = Api.generate_boundary() + {files, json} = Api.combine_files(body) |> Api.pop_files() + body = Jason.encode_to_iodata!(json) + + headers = + Api.maybe_add_reason(reason, [ + {"content-type", "multipart/form-data; boundary=#{boundary}"} + ]) + + %{ + method: :post, + route: Constants.thread_without_message(channel_id), + body: {:multipart, Api.create_multipart(files, body, boundary)}, + params: [], + headers: headers + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Channel}) + end + + def create_in_forum(channel_id, options, reason) do + Api.request(%{ + method: :post, + route: Constants.thread_without_message(channel_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + }) + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @type thread_with_message_params :: %{ + required(:name) => String.t(), + optional(:auto_archive_duration) => 60 | 1440 | 4320 | 10_080, + optional(:rate_limit_per_user) => 0..21_600 + } + + @doc """ + Create a thread on a channel message. + + The `thread_id` will be the same as the id of the message, as such no message can have more than one thread. + + If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`. + + An optional `reason` argument can be given for the audit log. + + ## Options + - `name`: Name of the thread, max 100 characters. + - `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080. + - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. + + """ + @doc since: "0.5.1" + @spec create_with_message( + Channel.id(), + Message.id(), + thread_with_message_params, + AuditLogEntry.reason() + ) :: + {:ok, Channel.t()} | Api.error() + def create_with_message(channel_id, message_id, options, reason \\ nil) do + Api.request(%{ + method: :post, + route: Constants.thread_with_message(channel_id, message_id), + body: options, + params: [], + headers: Api.maybe_add_reason(reason) + }) + |> Api.handle_request_with_decode({:struct, Channel}) + end +end From b5280490f3647d10d4fe6d8b23e4f84b2165658d Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 10:21:19 -0700 Subject: [PATCH 14/42] Extract functions for Nostrum.Api.User --- lib/nostrum/api.ex | 22 +++++---------- lib/nostrum/api/self.ex | 43 +++++++++++++++++++++++++++++ lib/nostrum/api/user.ex | 60 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 lib/nostrum/api/user.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index bdee9c314..635e262e3 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -2441,8 +2441,7 @@ defmodule Nostrum.Api do """ @spec get_user(User.id()) :: error | {:ok, User.t()} def get_user(user_id) do - request(:get, Constants.user(user_id)) - |> handle_request_with_decode({:struct, User}) + Nostrum.Api.User.get(user_id) end @doc """ @@ -2529,14 +2528,8 @@ defmodule Nostrum.Api do ``` """ @spec get_current_user_guilds(options) :: error | {:ok, [Guild.user_guild()]} - def get_current_user_guilds(options \\ []) - - def get_current_user_guilds(options) when is_list(options), - do: get_current_user_guilds(Map.new(options)) - - def get_current_user_guilds(options) when is_map(options) do - request(:get, Constants.me_guilds(), "", options) - |> handle_request_with_decode({:list, {:struct, Guild}}) + def get_current_user_guilds(options \\ []) do + Nostrum.Api.Self.guilds(options) end @doc ~S""" @@ -2598,8 +2591,7 @@ defmodule Nostrum.Api do """ @spec create_dm(User.id()) :: error | {:ok, Channel.dm_channel()} def create_dm(user_id) when is_snowflake(user_id) do - request(:post, Constants.me_channels(), %{recipient_id: user_id}) - |> handle_request_with_decode({:struct, Channel}) + Nostrum.Api.User.create_dm(user_id) end @doc ~S""" @@ -2629,8 +2621,7 @@ defmodule Nostrum.Api do @spec create_group_dm([String.t()], %{optional(User.id()) => String.t()}) :: error | {:ok, Channel.group_dm_channel()} def create_group_dm(access_tokens, nicks) when is_list(access_tokens) and is_map(nicks) do - request(:post, Constants.me_channels(), %{access_tokens: access_tokens, nicks: nicks}) - |> handle_request_with_decode({:struct, Channel}) + Nostrum.Api.User.create_group_dm(access_tokens, nicks) end @doc ~S""" @@ -2648,8 +2639,7 @@ defmodule Nostrum.Api do """ @spec get_user_connections() :: error | {:ok, list()} def get_user_connections do - request(:get, Constants.me_connections()) - |> handle_request_with_decode + Nostrum.Api.Self.connections() end @doc """ diff --git a/lib/nostrum/api/self.ex b/lib/nostrum/api/self.ex index 3a20c1165..10df1054a 100644 --- a/lib/nostrum/api/self.ex +++ b/lib/nostrum/api/self.ex @@ -5,6 +5,7 @@ defmodule Nostrum.Api.Self do alias Nostrum.Struct.Channel alias Nostrum.Shard.Session alias Nostrum.Shard.Supervisor + alias Nostrum.Struct.Guild @doc """ Gets the bot's OAuth2 application info. @@ -134,4 +135,46 @@ defmodule Nostrum.Api.Self do def update_voice_state(guild_id, channel_id, self_mute \\ false, self_deaf \\ false) do Supervisor.update_voice_state(guild_id, channel_id, self_mute, self_deaf) end + + @doc """ + Gets a list of guilds the user is currently in. + + This endpoint requires the `guilds` OAuth2 scope. + + If successful, returns `{:ok, guilds}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:before` (`t:Nostrum.Snowflake.t/0`) - get guilds before this + guild ID + * `:after` (`t:Nostrum.Snowflake.t/0`) - get guilds after this guild + ID + * `:limit` (integer) - max number of guilds to return (1-100) + + ## Examples + + ```elixir + iex> Nostrum.Api.Self.guilds(limit: 1) + {:ok, [%Nostrum.Struct.Guild{}]} + ``` + """ + @spec guilds(Api.options()) :: Api.error() | {:ok, [Guild.user_guild()]} + def guilds(options \\ []) + + def guilds(options) when is_list(options), + do: guilds(Map.new(options)) + + def guilds(options) when is_map(options) do + Api.request(:get, Constants.me_guilds(), "", options) + |> Api.handle_request_with_decode({:list, {:struct, Guild}}) + end + + @doc """ + Gets a list of user connections. + """ + @spec connections() :: Api.error() | {:ok, list()} + def connections do + Api.request(:get, Constants.me_connections()) + |> Api.handle_request_with_decode() + end end diff --git a/lib/nostrum/api/user.ex b/lib/nostrum/api/user.ex new file mode 100644 index 000000000..d3bdd4d61 --- /dev/null +++ b/lib/nostrum/api/user.ex @@ -0,0 +1,60 @@ +defmodule Nostrum.Api.User do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.User + alias Nostrum.Struct.Channel + + import Nostrum.Snowflake, only: [is_snowflake: 1] + + @doc ~S""" + Create a new DM channel with a user. + + If successful, returns `{:ok, dm_channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.create_dm(150061853001777154) + {:ok, %Nostrum.Struct.Channel{type: 1}} + ``` + """ + @spec create_dm(User.id()) :: Api.error() | {:ok, Channel.dm_channel()} + def create_dm(user_id) when is_snowflake(user_id) do + Api.request(:post, Constants.me_channels(), %{recipient_id: user_id}) + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @doc """ + Creates a new group DM channel. + + If successful, returns `{:ok, group_dm_channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + `access_tokens` are user oauth2 tokens. `nicks` is a map that maps a user id + to a nickname. + + ## Examples + + ```elixir + Nostrum.Api.create_group_dm(["6qrZcUqja7812RVdnEKjpzOL4CvHBFG"], %{41771983423143937 => "My Nickname"}) + {:ok, %Nostrum.Struct.Channel{type: 3}} + ``` + """ + @spec create_group_dm([String.t()], %{optional(User.id()) => String.t()}) :: + Api.error() | {:ok, Channel.group_dm_channel()} + def create_group_dm(access_tokens, nicks) when is_list(access_tokens) and is_map(nicks) do + Api.request(:post, Constants.me_channels(), %{access_tokens: access_tokens, nicks: nicks}) + |> Api.handle_request_with_decode({:struct, Channel}) + end + + @doc """ + Gets a user by its `t:Nostrum.Struct.User.id/0`. + + If the request is successful, this function returns `{:ok, user}`, where + `user` is a `Nostrum.Struct.User`. Otherwise, returns `{:error, reason}`. + """ + @spec get(User.id()) :: Api.error() | {:ok, User.t()} + def get(user_id) do + Api.request(:get, Constants.user(user_id)) + |> Api.handle_request_with_decode({:struct, User}) + end +end From dc5fd117fa4a65be09adf608223a3884dd087b33 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 10:42:27 -0700 Subject: [PATCH 15/42] Extract functions for Nostrum.Api.Webhook Also adjust missed prior delegations --- lib/nostrum/api.ex | 116 ++--------- lib/nostrum/api/application_command.ex | 65 +++++-- lib/nostrum/api/webhook.ex | 258 +++++++++++++++++++++++++ 3 files changed, 326 insertions(+), 113 deletions(-) create mode 100644 lib/nostrum/api/webhook.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 635e262e3..be44f3522 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -2304,8 +2304,7 @@ defmodule Nostrum.Api do @doc since: "0.5.0" @spec get_guild_scheduled_events(Guild.id()) :: error | {:ok, [ScheduledEvent.t()]} def get_guild_scheduled_events(guild_id) do - request(:get, Constants.guild_scheduled_events(guild_id)) - |> handle_request_with_decode({:list, {:struct, ScheduledEvent}}) + Nostrum.Api.Guild.scheduled_events(guild_id) end @doc """ @@ -2669,15 +2668,7 @@ defmodule Nostrum.Api do AuditLogEntry.reason() ) :: error | {:ok, Nostrum.Struct.Webhook.t()} def create_webhook(channel_id, args, reason \\ nil) do - %{ - method: :post, - route: Constants.webhooks_channel(channel_id), - body: args, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode + Nostrum.Api.Webhook.create(channel_id, args, reason) end @doc """ @@ -2687,8 +2678,7 @@ defmodule Nostrum.Api do @spec get_webhook_message(Webhook.t(), Message.id()) :: error | {:ok, Message.t()} def get_webhook_message(webhook, message_id) do - request(:get, Constants.webhook_message(webhook.id, webhook.token, message_id)) - |> handle_request_with_decode({:struct, Message}) + Nostrum.Api.Webhook.get_message(webhook, message_id) end @doc """ @@ -2721,8 +2711,7 @@ defmodule Nostrum.Api do """ @spec get_webhook(Webhook.id()) :: error | {:ok, Nostrum.Struct.Webhook.t()} def get_webhook(webhook_id) do - request(:get, Constants.webhook(webhook_id)) - |> handle_request_with_decode + Nostrum.Api.Webhook.get(webhook_id) end @doc """ @@ -2738,8 +2727,7 @@ defmodule Nostrum.Api do @spec get_webhook_with_token(Webhook.id(), Webhook.token()) :: error | {:ok, Nostrum.Struct.Webhook.t()} def get_webhook_with_token(webhook_id, webhook_token) do - request(:get, Constants.webhook_token(webhook_id, webhook_token)) - |> handle_request_with_decode + Nostrum.Api.Webhook.get_with_token(webhook_id, webhook_token) end @doc """ @@ -2761,15 +2749,7 @@ defmodule Nostrum.Api do AuditLogEntry.reason() ) :: error | {:ok, Nostrum.Struct.Webhook.t()} def modify_webhook(webhook_id, args, reason \\ nil) do - %{ - method: :patch, - route: Constants.webhook(webhook_id), - body: args, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode + Nostrum.Api.Webhook.modify(webhook_id, args, reason) end @doc """ @@ -2796,15 +2776,7 @@ defmodule Nostrum.Api do AuditLogEntry.reason() ) :: error | {:ok, Nostrum.Struct.Webhook.t()} def modify_webhook_with_token(webhook_id, webhook_token, args, reason \\ nil) do - %{ - method: :patch, - route: Constants.webhook_token(webhook_id, webhook_token), - body: args, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode + Nostrum.Api.Webhook.modify_with_token(webhook_id, webhook_token, args, reason) end @doc """ @@ -2816,13 +2788,7 @@ defmodule Nostrum.Api do """ @spec delete_webhook(Webhook.id(), AuditLogEntry.reason()) :: error | {:ok} def delete_webhook(webhook_id, reason \\ nil) do - request(%{ - method: :delete, - route: Constants.webhook(webhook_id), - body: "", - params: [], - headers: maybe_add_reason(reason) - }) + Nostrum.Api.Webhook.delete(webhook_id, reason) end @typep m1 :: %{ @@ -2896,24 +2862,8 @@ defmodule Nostrum.Api do At least one of `content`, `files` or `embeds` should be supplied in the `args` parameter. """ - def execute_webhook(webhook_id, webhook_token, args, wait \\ false) - - def execute_webhook(webhook_id, webhook_token, args, wait) do - {thread_id, args} = Map.pop(args, :thread_id) - args = prepare_allowed_mentions(args) - - params = - if is_nil(thread_id), - do: [wait: wait], - else: [wait: wait, thread_id: thread_id] - - request( - :post, - Constants.webhook_token(webhook_id, webhook_token), - combine_embeds(args) |> combine_files(), - params - ) - |> handle_request_with_decode({:struct, Message}) + def execute_webhook(webhook_id, webhook_token, args, wait \\ false) do + Nostrum.Api.Webhook.execute(webhook_id, webhook_token, args, wait) end @doc """ @@ -2930,12 +2880,7 @@ defmodule Nostrum.Api do ) :: error | {:ok, Message.t()} def edit_webhook_message(webhook_id, webhook_token, message_id, args) do - request( - :patch, - Constants.webhook_message_edit(webhook_id, webhook_token, message_id), - combine_embeds(args) |> combine_files() - ) - |> handle_request_with_decode({:struct, Message}) + Nostrum.Api.Webhook.edit_message(webhook_id, webhook_token, message_id, args) end @doc """ @@ -2947,7 +2892,7 @@ defmodule Nostrum.Api do """ @spec execute_slack_webhook(Webhook.id(), Webhook.token(), boolean) :: error | {:ok} def execute_slack_webhook(webhook_id, webhook_token, wait \\ false) do - request(:post, Constants.webhook_slack(webhook_id, webhook_token), wait: wait) + Nostrum.Api.Webhook.execute_slack(webhook_id, webhook_token, wait) end @doc """ @@ -2959,7 +2904,7 @@ defmodule Nostrum.Api do """ @spec execute_git_webhook(Webhook.id(), Webhook.token(), boolean) :: error | {:ok} def execute_git_webhook(webhook_id, webhook_token, wait \\ false) do - request(:post, Constants.webhook_git(webhook_id, webhook_token), wait: wait) + Nostrum.Api.Webhook.execute_git(webhook_id, webhook_token, wait) end @doc """ @@ -3272,8 +3217,11 @@ defmodule Nostrum.Api do guild_id, commands ) do - request(:put, Constants.guild_application_commands(application_id, guild_id), commands) - |> handle_request_with_decode + Nostrum.Api.ApplicationCommand.bulk_overwrite_guild_commands( + application_id, + guild_id, + commands + ) end # Why the two separate functions here? @@ -3765,34 +3713,6 @@ defmodule Nostrum.Api do Nostrum.Api.Thread.joined_private_archived_threads(channel_id, options) end - defp list_archived_threads(route, options) do - options = options |> maybe_convert_date_time(:before) - - res = - request(%{ - method: :get, - route: route, - body: "", - params: options, - headers: [] - }) - |> handle_request_with_decode - - case res do - {:ok, %{threads: channels, members: thread_members, has_more: has_more}} -> - map = %{ - threads: Util.cast(channels, {:list, {:struct, Channel}}), - members: Util.cast(thread_members, {:list, {:struct, ThreadMember}}), - has_more: has_more - } - - {:ok, map} - - {:error, e} -> - {:error, e} - end - end - @doc """ Join an existing thread, requires that the thread is not archived. """ diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex index d815edf82..51cf5a567 100644 --- a/lib/nostrum/api/application_command.ex +++ b/lib/nostrum/api/application_command.ex @@ -1,11 +1,11 @@ defmodule Nostrum.Api.ApplicationCommand do alias Nostrum.Api - alias Nostrum.Struct alias Nostrum.Constants alias Nostrum.Snowflake alias Nostrum.Cache.Me alias Nostrum.Struct.User alias Nostrum.Struct.Guild + alias Nostrum.Struct.ApplicationCommand @doc """ Edits command permissions for a specific command for your application in a guild. You can only add up to 10 permission overwrites for a command. @@ -24,14 +24,14 @@ defmodule Nostrum.Api.ApplicationCommand do @spec batch_edit_permissions(Guild.id(), [ %{ id: Snowflake.t(), - permissions: [Struct.ApplicationCommand.application_command_permissions()] + permissions: [ApplicationCommand.application_command_permissions()] } ]) :: {:ok, map()} | Api.error() @spec batch_edit_permissions(User.id(), Guild.id(), [ %{ id: Snowflake.t(), - permissions: [Struct.ApplicationCommand.application_command_permissions()] + permissions: [ApplicationCommand.application_command_permissions()] } ]) :: {:ok, map()} | Api.error() @@ -70,17 +70,52 @@ defmodule Nostrum.Api.ApplicationCommand do """ @doc since: "0.5.0" @spec bulk_overwrite_global_commands([ - Struct.ApplicationCommand.application_command_map() + ApplicationCommand.application_command_map() ]) :: {:ok, [map()]} | Api.error() @spec bulk_overwrite_global_commands(User.id(), [ - Struct.ApplicationCommand.application_command_map() + ApplicationCommand.application_command_map() ]) :: {:ok, [map()]} | Api.error() def bulk_overwrite_global_commands(application_id \\ Me.get().id, commands) do Api.request(:put, Constants.global_application_commands(application_id), commands) |> Api.handle_request_with_decode() end + @doc """ + Overwrite the existing guild application commands on the specified guild. + + This action will: + - Create any command that was provided and did not already exist + - Update any command that was provided and already existed if its configuration changed + - Delete any command that was not provided but existed on Discord's end + + ## Parameters + - `application_id`: Application ID for which to overwrite the commands. + If not given, this will be fetched from `Me`. + - `guild_id`: Guild on which to overwrite the commands. + - `commands`: List of command configurations, see the linked API documentation for reference. + + ## Return value + Updated list of guild application commands. See the official reference: + https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands + """ + @doc since: "0.5.0" + @spec bulk_overwrite_guild_commands(Guild.id(), [ + ApplicationCommand.application_command_map() + ]) :: {:ok, [map()]} | Api.error() + @spec bulk_overwrite_guild_commands(User.id(), Guild.id(), [ + ApplicationCommand.application_command_map() + ]) :: + {:ok, [map()]} | Api.error() + def bulk_overwrite_guild_commands( + application_id \\ Me.get().id, + guild_id, + commands + ) do + Api.request(:put, Constants.guild_application_commands(application_id, guild_id), commands) + |> Api.handle_request_with_decode() + end + @doc """ Create a new global application command. @@ -106,11 +141,11 @@ defmodule Nostrum.Api.ApplicationCommand do ) ``` """ - @spec create_global_command(Struct.ApplicationCommand.application_command_map()) :: + @spec create_global_command(ApplicationCommand.application_command_map()) :: {:ok, map()} | Api.error() @spec create_global_command( User.id(), - Struct.ApplicationCommand.application_command_map() + ApplicationCommand.application_command_map() ) :: {:ok, map()} | Api.error() def create_global_command(application_id \\ Me.get().id, command) do @@ -133,12 +168,12 @@ defmodule Nostrum.Api.ApplicationCommand do The created command. See the official reference: https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command """ - @spec create_guild_command(Guild.id(), Struct.ApplicationCommand.application_command_map()) :: + @spec create_guild_command(Guild.id(), ApplicationCommand.application_command_map()) :: {:ok, map()} | Api.error() @spec create_guild_command( User.id(), Guild.id(), - Struct.ApplicationCommand.application_command_map() + ApplicationCommand.application_command_map() ) :: {:ok, map()} | Api.error() def create_guild_command( application_id \\ Me.get().id, @@ -201,11 +236,11 @@ defmodule Nostrum.Api.ApplicationCommand do """ @doc since: "0.5.0" @spec edit_command_permissions(Guild.id(), Snowflake.t(), [ - Struct.ApplicationCommand.application_command_permissions() + ApplicationCommand.application_command_permissions() ]) :: {:ok, map()} | Api.error() @spec edit_command_permissions(User.id(), Guild.id(), Snowflake.t(), [ - Struct.ApplicationCommand.application_command_permissions() + ApplicationCommand.application_command_permissions() ]) :: {:ok, map()} | Api.error() def edit_command_permissions( @@ -241,12 +276,12 @@ defmodule Nostrum.Api.ApplicationCommand do """ @spec edit_global_command( Snowflake.t(), - Struct.ApplicationCommand.application_command_edit_map() + ApplicationCommand.application_command_edit_map() ) :: {:ok, map()} | Api.error() @spec edit_global_command( User.id(), Snowflake.t(), - Struct.ApplicationCommand.application_command_edit_map() + ApplicationCommand.application_command_edit_map() ) :: {:ok, map()} | Api.error() def edit_global_command( application_id \\ Me.get().id, @@ -276,13 +311,13 @@ defmodule Nostrum.Api.ApplicationCommand do @spec edit_guild_command( Guild.id(), Snowflake.t(), - Struct.ApplicationCommand.application_command_edit_map() + ApplicationCommand.application_command_edit_map() ) :: {:ok, map()} | Api.error() @spec edit_guild_command( User.id(), Guild.id(), Snowflake.t(), - Struct.ApplicationCommand.application_command_edit_map() + ApplicationCommand.application_command_edit_map() ) :: {:ok, map()} | Api.error() def edit_guild_command( diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex new file mode 100644 index 000000000..febd82617 --- /dev/null +++ b/lib/nostrum/api/webhook.ex @@ -0,0 +1,258 @@ +defmodule Nostrum.Api.Webhook do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Struct.Channel + alias Nostrum.Struct.Webhook + alias Nostrum.Struct.Guild.AuditLogEntry + alias Nostrum.Struct.Message + alias Nostrum.Struct.Interaction + alias Nostrum.Struct.User + + @doc """ + Creates a webhook. + + ## Parameters + - `channel_id` - Id of the channel to send the message to. + - `args` - Map with the following **required** keys: + - `name` - Name of the webhook. + - `avatar` - Base64 128x128 jpeg image for the default avatar. + - `reason` - An optional reason for the guild audit log. + """ + @spec create( + Channel.id(), + %{ + name: String.t(), + avatar: String.t() + }, + AuditLogEntry.reason() + ) :: Api.error() | {:ok, Webhook.t()} + def create(channel_id, args, reason \\ nil) do + %{ + method: :post, + route: Constants.webhooks_channel(channel_id), + body: args, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode() + end + + @doc """ + Deletes a webhook. + + ## Parameters + - `webhook_id` - Id of webhook to delete. + - `reason` - An optional reason for the guild audit log. + """ + @spec delete(Webhook.id(), AuditLogEntry.reason()) :: Api.error() | {:ok} + def delete(webhook_id, reason \\ nil) do + Api.request(%{ + method: :delete, + route: Constants.webhook(webhook_id), + body: "", + params: [], + headers: Api.maybe_add_reason(reason) + }) + end + + @doc """ + Edits a message previously created by the same webhook, + args are the same as `execute/3`, + however all fields are optional. + """ + @doc since: "0.5.0" + @spec edit_message( + Webhook.id(), + Webhook.token(), + Message.id(), + map() + ) :: + Api.error() | {:ok, Message.t()} + def edit_message(webhook_id, webhook_token, message_id, args) do + Api.request( + :patch, + Constants.webhook_message_edit(webhook_id, webhook_token, message_id), + Api.combine_embeds(args) |> Api.combine_files() + ) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc """ + Executes a git webhook. + + ## Parameters + - `webhook_id` - Id of the webhook to execute. + - `webhook_token` - Token of the webhook to execute. + """ + @spec execute_git(Webhook.id(), Webhook.token(), boolean) :: Api.error() | {:ok} + def execute_git(webhook_id, webhook_token, wait \\ false) do + Api.request(:post, Constants.webhook_git(webhook_id, webhook_token), wait: wait) + end + + @doc """ + Executes a slack webhook. + + ## Parameters + - `webhook_id` - Id of the webhook to execute. + - `webhook_token` - Token of the webhook to execute. + """ + @spec execute_slack(Webhook.id(), Webhook.token(), boolean) :: Api.error() | {:ok} + def execute_slack(webhook_id, webhook_token, wait \\ false) do + Api.request(:post, Constants.webhook_slack(webhook_id, webhook_token), wait: wait) + end + + @doc """ + Executes a webhook. + + ## Parameters + - `webhook_id` - Id of the webhook to execute. + - `webhook_token` - Token of the webhook to execute. + - `args` - Map with the following allowed keys: + - `content` - Message content. + - `files` - List of Files to send. + - `embeds` - List of embeds to send. + - `username` - Overrides the default name of the webhook. + - `avatar_url` - Overrides the default avatar of the webhook. + - `tts` - Whether the message should be read over text to speech. + - `flags` - Bitwise flags. + - `thread_id` - Send a message to the specified thread within the webhook's channel. + - `allowed_mentions` - Mentions to allow in the webhook message + - `wait` - Whether to return an error or not. Defaults to `false`. + + **Note**: If `wait` is `true`, this method will return a `Message.t()` on success. + + At least one of `content`, `files` or `embeds` should be supplied in the `args` parameter. + """ + @spec execute( + Webhook.id() | User.id(), + Webhook.token() | Interaction.token(), + Api.matrix(), + boolean + ) :: + Api.error() | {:ok} | {:ok, Message.t()} + def execute(webhook_id, webhook_token, args, wait \\ false) + + def execute(webhook_id, webhook_token, args, wait) do + {thread_id, args} = Map.pop(args, :thread_id) + args = Api.prepare_allowed_mentions(args) + + params = + if is_nil(thread_id), + do: [wait: wait], + else: [wait: wait, thread_id: thread_id] + + Api.request( + :post, + Constants.webhook_token(webhook_id, webhook_token), + Api.combine_embeds(args) |> Api.combine_files(), + params + ) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc """ + Gets a webhook by id. + + ## Parameters + - `webhook_id` - Id of the webhook to get. + """ + @spec get(Webhook.id()) :: Api.error() | {:ok, Webhook.t()} + def get(webhook_id) do + Api.request(:get, Constants.webhook(webhook_id)) + |> Api.handle_request_with_decode({:struct, Webhook}) + end + + @doc """ + Retrieves the original message of a webhook. + """ + @doc since: "0.7.0" + @spec get_message(Webhook.t(), Message.id()) :: + Api.error() | {:ok, Message.t()} + def get_message(webhook, message_id) do + Api.request(:get, Constants.webhook_message(webhook.id, webhook.token, message_id)) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc """ + Gets a webhook by id and token. + + This method is exactly like `get/1` but does not require + authentication. + + ## Parameters + - `webhook_id` - Id of the webhook to get. + - `webhook_token` - Token of the webhook to get. + """ + @spec get_with_token(Webhook.id(), Webhook.token()) :: + Api.error() | {:ok, Webhook.t()} + def get_with_token(webhook_id, webhook_token) do + Api.request(:get, Constants.webhook_token(webhook_id, webhook_token)) + |> Api.handle_request_with_decode({:struct, Webhook}) + end + + @doc """ + Modifies a webhook. + + ## Parameters + - `webhook_id` - Id of the webhook to modify. + - `args` - Map with the following *optional* keys: + - `name` - Name of the webhook. + - `avatar` - Base64 128x128 jpeg image for the default avatar. + - `reason` - An optional reason for the guild audit log. + """ + @spec modify( + Webhook.id(), + %{ + name: String.t(), + avatar: String.t() + }, + AuditLogEntry.reason() + ) :: Api.error() | {:ok, Webhook.t()} + def modify(webhook_id, args, reason \\ nil) do + %{ + method: :patch, + route: Constants.webhook(webhook_id), + body: args, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Webhook}) + end + + @doc """ + Modifies a webhook with a token. + + This method is exactly like `modify_webhook/1` but does not require + authentication. + + ## Parameters + - `webhook_id` - Id of the webhook to modify. + - `webhook_token` - Token of the webhook to get. + - `args` - Map with the following *optional* keys: + - `name` - Name of the webhook. + - `avatar` - Base64 128x128 jpeg image for the default avatar. + - `reason` - An optional reason for the guild audit log. + """ + @spec modify_with_token( + Webhook.id(), + Webhook.token(), + %{ + name: String.t(), + avatar: String.t() + }, + AuditLogEntry.reason() + ) :: Api.error() | {:ok, Webhook.t()} + def modify_with_token(webhook_id, webhook_token, args, reason \\ nil) do + %{ + method: :patch, + route: Constants.webhook_token(webhook_id, webhook_token), + body: args, + params: [], + headers: Api.maybe_add_reason(reason) + } + |> Api.request() + |> Api.handle_request_with_decode() + end +end From 508e00aeb5ddd9c236fa57efb0a735aa969e8486 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 12:39:16 -0700 Subject: [PATCH 16/42] Extract functions for Interaction and Sticker Also adds the get sticker pack route --- lib/nostrum/api.ex | 112 ++++----------------- lib/nostrum/api/interaction.ex | 159 ++++++++++++++++++++++++++++++ lib/nostrum/api/sticker.ex | 172 +++++++++++++++++++++++++++++++++ lib/nostrum/constants.ex | 1 + 4 files changed, 352 insertions(+), 92 deletions(-) create mode 100644 lib/nostrum/api/interaction.ex create mode 100644 lib/nostrum/api/sticker.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index be44f3522..79f19e014 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -1251,8 +1251,7 @@ defmodule Nostrum.Api do @doc since: "0.10.0" @spec get_sticker(Snowflake.t()) :: {:ok, Sticker.t()} | error def get_sticker(sticker_id) do - request(:get, Constants.sticker(sticker_id)) - |> handle_request_with_decode({:struct, Sticker}) + Nostrum.Api.Sticker.get(sticker_id) end @doc ~S""" @@ -1263,8 +1262,7 @@ defmodule Nostrum.Api do @doc since: "0.10.0" @spec list_guild_stickers(Guild.id()) :: {:ok, [Sticker.t()]} | error def list_guild_stickers(guild_id) do - request(:get, Constants.guild_stickers(guild_id)) - |> handle_request_with_decode({:list, {:struct, Sticker}}) + Nostrum.Api.Sticker.list(guild_id) end @doc ~S""" @@ -1275,8 +1273,7 @@ defmodule Nostrum.Api do @doc since: "0.10.0" @spec get_guild_sticker(Guild.id(), Sticker.id()) :: Sticker.t() | error def get_guild_sticker(guild_id, sticker_id) do - request(:get, Constants.guild_sticker(guild_id, sticker_id)) - |> handle_request_with_decode({:struct, Sticker}) + Nostrum.Api.Sticker.get(guild_id, sticker_id) end @doc ~S""" @@ -1313,38 +1310,7 @@ defmodule Nostrum.Api do String.t() | nil ) :: {:ok, Sticker.t()} | error def create_guild_sticker(guild_id, name, description, tags, file, reason \\ nil) do - opts = %{ - name: name, - description: description, - tags: tags - } - - boundary = generate_boundary() - - multipart = create_multipart([], Jason.encode_to_iodata!(opts), boundary) - - headers = - maybe_add_reason(reason, [ - {"content-type", "multipart/form-data; boundary=#{boundary}"} - ]) - - file = create_file_part_for_multipart(file, nil, boundary, "file") - - %{ - method: :post, - route: Constants.guild_stickers(guild_id), - body: - {:multipart, - [ - ~s|--#{boundary}#{@crlf}|, - file - | multipart - ]}, - params: [], - headers: headers - } - |> request() - |> handle_request_with_decode({:struct, Sticker}) + Nostrum.Api.Sticker.create(guild_id, name, description, tags, file, reason) end @doc ~S""" @@ -1365,8 +1331,7 @@ defmodule Nostrum.Api do tags: Sticker.tags() | nil }) :: {:ok, Sticker.t()} | error def modify_guild_sticker(guild_id, sticker_id, options) do - request(:patch, Constants.guild_sticker(guild_id, sticker_id), options) - |> handle_request_with_decode({:struct, Sticker}) + Nostrum.Api.Sticker.modify(guild_id, sticker_id, options) end @doc ~S""" @@ -1375,7 +1340,7 @@ defmodule Nostrum.Api do @doc since: "0.10.0" @spec delete_guild_sticker(Guild.id(), Sticker.id()) :: {:ok} | error def delete_guild_sticker(guild_id, sticker_id) do - request(:delete, Constants.guild_sticker(guild_id, sticker_id)) + Nostrum.Api.Sticker.delete(guild_id, sticker_id) end @doc ~S""" @@ -1384,14 +1349,7 @@ defmodule Nostrum.Api do @doc since: "0.10.0" @spec get_sticker_packs() :: {:ok, [Sticker.Pack.t()]} | error def get_sticker_packs do - resp = - request(:get, Constants.sticker_packs()) - |> handle_request_with_decode() - - case resp do - {:ok, %{sticker_packs: packs}} -> {:ok, Util.cast(packs, {:list, {:struct, Sticker.Pack}})} - _ -> resp - end + Nostrum.Api.Sticker.packs() end @doc ~S""" @@ -1907,8 +1865,7 @@ defmodule Nostrum.Api do """ @spec get_guild_roles(Guild.id()) :: error | {:ok, [Role.t()]} def get_guild_roles(guild_id) when is_snowflake(guild_id) do - request(:get, Constants.guild_roles(guild_id)) - |> handle_request_with_decode({:list, {:struct, Role}}) + Nostrum.Api.Guild.roles(guild_id) end @doc ~S""" @@ -2025,22 +1982,8 @@ defmodule Nostrum.Api do """ @spec modify_guild_role(Guild.id(), Role.id(), options, AuditLogEntry.reason()) :: error | {:ok, Role.t()} - def modify_guild_role(guild_id, role_id, options, reason \\ nil) - - def modify_guild_role(guild_id, role_id, options, reason) when is_list(options), - do: modify_guild_role(guild_id, role_id, Map.new(options), reason) - - def modify_guild_role(guild_id, role_id, %{} = options, reason) - when is_snowflake(guild_id) and is_snowflake(role_id) do - %{ - method: :patch, - route: Constants.guild_role(guild_id, role_id), - body: options, - params: [], - headers: maybe_add_reason(reason) - } - |> request() - |> handle_request_with_decode({:struct, Role}) + def modify_guild_role(guild_id, role_id, options, reason \\ nil) do + Nostrum.Api.Role.modify(guild_id, role_id, options, reason) end @doc ~S""" @@ -2100,8 +2043,7 @@ defmodule Nostrum.Api do """ @spec get_guild_prune_count(Guild.id(), 1..30) :: error | {:ok, %{pruned: integer}} def get_guild_prune_count(guild_id, days) when is_snowflake(guild_id) and days in 1..30 do - request(:get, Constants.guild_prune(guild_id), "", days: days) - |> handle_request_with_decode + Nostrum.Api.Guild.estimate_prune_count(guild_id, days) end @doc ~S""" @@ -2154,8 +2096,7 @@ defmodule Nostrum.Api do """ @spec get_voice_region(integer) :: error | {:ok, [Nostrum.Struct.VoiceRegion.t()]} def get_voice_region(guild_id) do - request(:get, Constants.guild_voice_regions(guild_id)) - |> handle_request_with_decode + Nostrum.Api.Guild.voice_region(guild_id) end @doc ~S""" @@ -2194,8 +2135,7 @@ defmodule Nostrum.Api do @spec get_guild_integrations(Guild.id()) :: error | {:ok, [Nostrum.Struct.Guild.Integration.t()]} def get_guild_integrations(guild_id) do - request(:get, Constants.guild_integrations(guild_id)) - |> handle_request_with_decode + Nostrum.Api.Guild.integrations(guild_id) end @doc """ @@ -3284,11 +3224,7 @@ defmodule Nostrum.Api do """ @spec create_interaction_response(Interaction.id(), Interaction.token(), map()) :: {:ok} | error def create_interaction_response(id, token, response) do - request( - :post, - Constants.interaction_callback(id, token), - combine_embeds(response) |> combine_files() - ) + Nostrum.Api.Interaction.create_response(id, token, response) end def create_interaction_response!(id, token, response) do @@ -3302,10 +3238,7 @@ defmodule Nostrum.Api do @doc since: "0.7.0" @spec get_original_interaction_response(Interaction.t()) :: error | {:ok, Message.t()} def get_original_interaction_response(interaction) do - path = Constants.original_interaction_response(interaction.application_id, interaction.token) - - request(:get, path) - |> handle_request_with_decode({:struct, Message}) + Nostrum.Api.Interaction.original_response(interaction.application_id, interaction.token) end @doc """ @@ -3336,12 +3269,7 @@ defmodule Nostrum.Api do @spec edit_interaction_response(User.id(), Interaction.token(), map()) :: {:ok, Message.t()} | error def edit_interaction_response(id \\ Me.get().id, token, response) do - request( - :patch, - Constants.interaction_callback_original(id, token), - combine_embeds(response) |> combine_files() - ) - |> handle_request_with_decode({:struct, Message}) + Nostrum.Api.Interaction.edit_response(id, token, response) end @doc """ @@ -3378,7 +3306,7 @@ defmodule Nostrum.Api do @doc since: "0.5.0" @spec delete_interaction_response(User.id(), Interaction.token()) :: {:ok} | error def delete_interaction_response(id \\ Me.get().id, token) do - request(:delete, Constants.interaction_callback_original(id, token)) + Nostrum.Api.Interaction.delete_response(id, token) end @doc """ @@ -3399,7 +3327,7 @@ defmodule Nostrum.Api do @spec create_followup_message(User.id(), Interaction.token(), map()) :: {:ok, Message.t()} | error def create_followup_message(application_id \\ Me.get().id, token, webhook_payload) do - execute_webhook(application_id, token, webhook_payload) + Nostrum.Api.Interaction.create_followup_message(application_id, token, webhook_payload) end @doc """ @@ -3429,7 +3357,7 @@ defmodule Nostrum.Api do token, message_id ) do - request(:delete, Constants.interaction_followup_message(application_id, token, message_id)) + Nostrum.Api.Interaction.delete_followup_message(application_id, token, message_id) end @doc """ @@ -3954,7 +3882,7 @@ defmodule Nostrum.Api do ] end - defp create_file_part_for_multipart(file, index, boundary, name_override \\ nil) do + def create_file_part_for_multipart(file, index, boundary, name_override \\ nil) do {body, name} = get_file_contents(file) file_mime = MIME.from_path(name) diff --git a/lib/nostrum/api/interaction.ex b/lib/nostrum/api/interaction.ex new file mode 100644 index 000000000..b2313e357 --- /dev/null +++ b/lib/nostrum/api/interaction.ex @@ -0,0 +1,159 @@ +defmodule Nostrum.Api.Interaction do + alias Nostrum.Api + alias Nostrum.Constants + alias Nostrum.Cache.Me + alias Nostrum.Struct.Interaction + alias Nostrum.Api.Webhook + alias Nostrum.Struct.Message + alias Nostrum.Struct.User + + @doc """ + Create a followup message for an interaction. + + Delegates to ``execute_webhook/3``, see the function for more details. + """ + @spec create_followup_message(User.id(), Interaction.token(), map()) :: + {:ok, Message.t()} | Api.error() + def create_followup_message(application_id \\ Me.get().id, token, webhook_payload) do + Webhook.execute(application_id, token, webhook_payload) + end + + @doc """ + Same as `create_response/3`, but directly takes the + `t:Nostrum.Struct.Interaction.t/0` received from the gateway. + """ + @spec create_response(Interaction.t(), map()) :: {:ok} | Api.error() + def create_response(interaction, response) do + create_response(interaction.id, interaction.token, response) + end + + @doc """ + Create a response to an interaction received from the gateway. + + ## Parameters + - `id`: The interaction ID to which the response should be created. + - `token`: The interaction token. + - `response`: An [`InteractionResponse`](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object) + object. See the linked documentation. + + + ### Attachments + To include attachments in the response, you can include a `:files` field in the response. + This field expects a list of attachments which can be in either of the following formats: + - A path to the file to upload. + - A map with the following fields: + - `:body` The file contents. + - `:name` The filename of the file. + + ## Example + + ```elixir + response = %{ + type: 4, + data: %{ + content: "I copy and pasted this code." + } + } + Nostrum.Api.Interaction.create_response(interaction, response) + ``` + + As an alternative to passing the interaction ID and token, the + original `t:Nostrum.Struct.Interaction.t/0` can also be passed + directly. See `create_response/2`. + """ + @spec create_response(Interaction.id(), Interaction.token(), map()) :: {:ok} | Api.error() + def create_response(id, token, response) do + Api.request( + :post, + Constants.interaction_callback(id, token), + Api.combine_embeds(response) |> Api.combine_files() + ) + end + + @doc """ + Delete a followup message for an interaction. + + ## Parameters + - `application_id`: Application ID for which to create the command. + If not given, this will be fetched from `Me`. + - `token`: Interaction token. + - `message_id`: Followup message ID. + """ + @spec delete_followup_message(User.id(), Interaction.token(), Message.id()) :: + {:ok} | Api.error() + def delete_followup_message( + application_id \\ Me.get().id, + token, + message_id + ) do + Api.request( + :delete, + Constants.interaction_followup_message(application_id, token, message_id) + ) + end + + @doc """ + Same as `delete_response/3`, but directly takes the + `t:Nostrum.Struct.Interaction.t/0` received from the gateway. + """ + @doc since: "0.5.0" + @spec delete_response(Interaction.t()) :: {:ok} | Api.error() + def delete_response(%Interaction{application_id: application_id, token: token}) do + delete_response(application_id, token) + end + + @doc """ + Deletes the original interaction response. + """ + @doc since: "0.5.0" + @spec delete_response(User.id(), Interaction.token()) :: {:ok} | Api.error() + def delete_response(id \\ Me.get().id, token) do + Api.request(:delete, Constants.interaction_callback_original(id, token)) + end + + @doc """ + Same as `edit_response/3`, but directly takes the + `t:Nostrum.Struct.Interaction.t/0` received from the gateway. + """ + @doc since: "0.5.0" + @spec edit_response(Interaction.t(), map()) :: {:ok, Message.t()} | Api.error() + def edit_response(%Interaction{application_id: application_id, token: token}, response) do + edit_response(application_id, token, response) + end + + @doc """ + Edits the original interaction response. + + Functions the same as `edit_webhook_message/3` + """ + @doc since: "0.5.0" + @spec edit_response(User.id(), Interaction.token(), map()) :: + {:ok, Message.t()} | Api.error() + def edit_response(id \\ Me.get().id, token, response) do + Api.request( + :patch, + Constants.interaction_callback_original(id, token), + Api.combine_embeds(response) |> Api.combine_files() + ) + |> Api.handle_request_with_decode({:struct, Message}) + end + + @doc """ + Retrieves the original message of an interaction. + """ + @doc since: "0.7.0" + @spec original_response(Interaction.t()) :: Api.error() | {:ok, Message.t()} + def original_response(%Interaction{application_id: application_id, token: token}) do + original_response(application_id, token) + end + + @doc """ + Retrieves the original message of an interaction. + """ + @doc since: "0.7.0" + @spec original_response(Interaction.t()) :: Api.error() | {:ok, Message.t()} + def original_response(id \\ Me.get().id, token) do + Api.request(:get, Constants.original_interaction_response(id, token)) + |> Api.handle_request_with_decode({:struct, Message}) + end +end diff --git a/lib/nostrum/api/sticker.ex b/lib/nostrum/api/sticker.ex new file mode 100644 index 000000000..558e166d7 --- /dev/null +++ b/lib/nostrum/api/sticker.ex @@ -0,0 +1,172 @@ +defmodule Nostrum.Api.Sticker do + alias Nostrum.Api + alias Nostrum.Util + alias Nostrum.Snowflake + alias Nostrum.Constants + alias Nostrum.Struct.Sticker + alias Nostrum.Struct.Guild + alias Nostrum.Struct.Guild.AuditLogEntry + + @crlf "\r\n" + + @doc ~S""" + Create a sticker in a guild. + + Every guild has five free sticker slots by default, and each Boost level will + grant access to more slots. + + Uploaded stickers are constrained to 5 seconds in length for animated stickers, and 320 x 320 pixels. + + Stickers in the [Lottie file format](https://airbnb.design/lottie/) can only + be uploaded on guilds that have either the `VERIFIED` and/or the `PARTNERED` + guild feature. + + ## Parameters + + - `name`: Name of the sticker (2-30 characters) + - `description`: Description of the sticker (2-100 characters) + - `tags`: Autocomplete/suggestion tags for the sticker (max 200 characters) + - `file`: A path to a file to upload or a map of `name` (file name) and `body` (file data). + - `reason` (optional): audit log reason to attach to this event + + ## Returns + + Returns a `t:Nostrum.Struct.Sticker.t/0` on success. + """ + @doc since: "0.10.0" + @spec create( + Guild.id(), + Sticker.name(), + Sticker.description(), + Sticker.tags(), + String.t() | %{body: iodata(), name: String.t()}, + AuditLogEntry.reason() + ) :: {:ok, Sticker.t()} | Api.error() + def create(guild_id, name, description, tags, file, reason \\ nil) do + opts = %{ + name: name, + description: description, + tags: tags + } + + boundary = Api.generate_boundary() + + multipart = Api.create_multipart([], Jason.encode_to_iodata!(opts), boundary) + + headers = + Api.maybe_add_reason(reason, [ + {"content-type", "multipart/form-data; boundary=#{boundary}"} + ]) + + file = Api.create_file_part_for_multipart(file, nil, boundary, "file") + + %{ + method: :post, + route: Constants.guild_stickers(guild_id), + body: + {:multipart, + [ + ~s|--#{boundary}#{@crlf}|, + file + | multipart + ]}, + params: [], + headers: headers + } + |> Api.request() + |> Api.handle_request_with_decode({:struct, Sticker}) + end + + @doc ~S""" + Delete a guild sticker with the specified ID. + """ + @doc since: "0.10.0" + @spec delete(Guild.id(), Sticker.id()) :: {:ok} | Api.error() + def delete(guild_id, sticker_id) do + Api.request(:delete, Constants.guild_sticker(guild_id, sticker_id)) + end + + @doc ~S""" + Fetch a sticker with the provided ID. + + Returns a `t:Nostrum.Struct.Sticker.t/0`. + """ + @doc since: "0.10.0" + @spec get(Sticker.id()) :: {:ok, Sticker.t()} | Api.error() + def get(sticker_id) do + Api.request(:get, Constants.sticker(sticker_id)) + |> Api.handle_request_with_decode({:struct, Sticker}) + end + + @doc ~S""" + Return the specified sticker from the specified guild. + + Returns a `t:Nostrum.Struct.Sticker.t/0`. + """ + @doc since: "0.10.0" + @spec get(Guild.id(), Sticker.id()) :: Sticker.t() | Api.error() + def get(guild_id, sticker_id) do + Api.request(:get, Constants.guild_sticker(guild_id, sticker_id)) + |> Api.handle_request_with_decode({:struct, Sticker}) + end + + @doc ~S""" + Fetch a sticker pack with the provided ID. + + Returns a `t:Nostrum.Struct.Sticker.Pack.t/0`. + """ + @doc since: "0.11.0" + @spec pack(Snowflake.t()) :: {:ok, Sticker.Pack.t()} | Api.error() + def pack(id) do + Api.request(:get, Constants.sticker_pack(id)) + |> Api.handle_request_with_decode({:struct, Sticker.Pack}) + end + + @doc ~S""" + Get a list of available sticker packs. + """ + @doc since: "0.10.0" + @spec packs() :: {:ok, [Sticker.Pack.t()]} | Api.error() + def packs do + Api.request(:get, Constants.sticker_packs()) + |> Api.handle_request_with_decode() + |> case do + {:ok, %{sticker_packs: packs}} -> {:ok, Util.cast(packs, {:list, {:struct, Sticker.Pack}})} + resp -> resp + end + end + + @doc ~S""" + List all stickers in the provided guild. + + Returns a list of `t:Nostrum.Struct.Sticker.t/0`. + """ + @doc since: "0.10.0" + @spec list(Guild.id()) :: {:ok, [Sticker.t()]} | Api.error() + def list(guild_id) do + Api.request(:get, Constants.guild_stickers(guild_id)) + |> Api.handle_request_with_decode({:list, {:struct, Sticker}}) + end + + @doc ~S""" + Modify a guild sticker with the specified ID. + + Pass in a map of properties to update, with any of the following keys: + + - `name`: Name of the sticker (2-30 characters) + - `description`: Description of the sticker (2-100 characters) + - `tags`: Autocomplete/suggestion tags for the sticker (max 200 characters) + + Returns an updated sticker on update completion. + """ + @doc since: "0.10.0" + @spec modify(Guild.id(), Sticker.id(), %{ + name: Sticker.name() | nil, + description: Sticker.description() | nil, + tags: Sticker.tags() | nil + }) :: {:ok, Sticker.t()} | Api.error() + def modify(guild_id, sticker_id, options) do + Api.request(:patch, Constants.guild_sticker(guild_id, sticker_id), options) + |> Api.handle_request_with_decode({:struct, Sticker}) + end +end diff --git a/lib/nostrum/constants.ex b/lib/nostrum/constants.ex index 92f893c5b..d449a48a4 100644 --- a/lib/nostrum/constants.ex +++ b/lib/nostrum/constants.ex @@ -73,6 +73,7 @@ defmodule Nostrum.Constants do def guild_sticker(guild_id, sticker_id), do: "/guilds/#{guild_id}/stickers/#{sticker_id}" def sticker_packs, do: "/sticker-packs" + def sticker_pack(pack_id), do: "/sticker-packs/#{pack_id}" def guild_scheduled_events(guild_id), do: "/guilds/#{guild_id}/scheduled-events" From 35117165d87e00926dd229176c1142e78b9b034c Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 12:47:11 -0700 Subject: [PATCH 17/42] Minimize diff and remove unused alias --- lib/nostrum/api.ex | 2 +- lib/nostrum/api/adapter.ex | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 79f19e014..347110c61 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -49,7 +49,7 @@ defmodule Nostrum.Api do alias Nostrum.Api.Ratelimiter alias Nostrum.Cache.Me - alias Nostrum.{Constants, Snowflake, Util} + alias Nostrum.{Snowflake, Util} alias Nostrum.Struct.{ ApplicationCommand, diff --git a/lib/nostrum/api/adapter.ex b/lib/nostrum/api/adapter.ex index 98e631792..a253ae07c 100644 --- a/lib/nostrum/api/adapter.ex +++ b/lib/nostrum/api/adapter.ex @@ -1,4 +1,5 @@ defmodule Nostrum.Api.Adapter do + @moduledoc false @version Nostrum.Mixfile.project()[:version] import Nostrum.Constants, only: [base_route: 0] From 69d3c4bc967315db128411c0bf0ac36eb8fb9c64 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 12:47:36 -0700 Subject: [PATCH 18/42] Minimize diff --- lib/nostrum/api/adapter.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nostrum/api/adapter.ex b/lib/nostrum/api/adapter.ex index a253ae07c..4eb813ce6 100644 --- a/lib/nostrum/api/adapter.ex +++ b/lib/nostrum/api/adapter.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Adapter do @moduledoc false + @version Nostrum.Mixfile.project()[:version] import Nostrum.Constants, only: [base_route: 0] From 10b1c99d40308d81aa1eb4a02d739424715d495d Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 13:31:08 -0700 Subject: [PATCH 19/42] Start moving api helper functions to Api.Helpers module --- lib/nostrum/api.ex | 38 +----------------- lib/nostrum/api/application_command.ex | 25 ++++++------ lib/nostrum/api/auto_moderation.ex | 9 +++-- lib/nostrum/api/channel.ex | 15 ++++---- lib/nostrum/api/guild.ex | 53 +++++++++++++------------- lib/nostrum/api/helpers.ex | 27 +++++++++++++ lib/nostrum/api/interaction.ex | 5 ++- lib/nostrum/api/invite.ex | 11 +++--- lib/nostrum/api/message.ex | 9 +++-- lib/nostrum/api/poll.ex | 5 ++- lib/nostrum/api/role.ex | 5 ++- lib/nostrum/api/scheduled_event.ex | 9 +++-- lib/nostrum/api/self.ex | 13 ++++--- lib/nostrum/api/sticker.ex | 15 ++++---- lib/nostrum/api/thread.ex | 19 ++++----- lib/nostrum/api/user.ex | 7 ++-- lib/nostrum/api/webhook.ex | 17 +++++---- 17 files changed, 145 insertions(+), 137 deletions(-) create mode 100644 lib/nostrum/api/helpers.ex diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 347110c61..c2201759c 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -46,10 +46,11 @@ defmodule Nostrum.Api do require Logger import Nostrum.Snowflake, only: [is_snowflake: 1] + import Nostrum.Api.Helpers, only: [has_files: 1] alias Nostrum.Api.Ratelimiter alias Nostrum.Cache.Me - alias Nostrum.{Snowflake, Util} + alias Nostrum.Snowflake alias Nostrum.Struct.{ ApplicationCommand, @@ -70,41 +71,6 @@ defmodule Nostrum.Api do alias Nostrum.Struct.Guild.{AuditLog, AuditLogEntry, Member, Role, ScheduledEvent} - defguard has_files(args) when is_map_key(args, :files) or is_map_key(args, :file) - - def handle_request_with_decode(response) - def handle_request_with_decode({:ok, body}), do: {:ok, Jason.decode!(body, keys: :atoms)} - def handle_request_with_decode({:error, _} = error), do: error - - def handle_request_with_decode(response, type) - # add_guild_member/3 can return both a 201 and a 204 - def handle_request_with_decode({:ok}, _type), do: {:ok} - def handle_request_with_decode({:error, _} = error, _type), do: error - - def handle_request_with_decode({:ok, body}, type) do - convert = - body - |> Jason.decode!(keys: :atoms) - |> Util.cast(type) - - {:ok, convert} - end - - def handle_request_with_decode!(response) - def handle_request_with_decode!({:ok, body}), do: Jason.decode!(body, keys: :atoms) - def handle_request_with_decode!({:error, error}), do: raise(error) - - def handle_request_with_decode!(response, type) - # add_guild_member/3 can return both a 201 and a 204 - def handle_request_with_decode!({:ok}, _type), do: {:ok} - def handle_request_with_decode!({:error, error}, _type), do: raise(error) - - def handle_request_with_decode!({:ok, body}, type) do - body - |> Jason.decode!(keys: :atoms) - |> Util.cast(type) - end - @typedoc """ Represents a failed response from the API. diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex index 51cf5a567..55b7a2f38 100644 --- a/lib/nostrum/api/application_command.ex +++ b/lib/nostrum/api/application_command.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.ApplicationCommand do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Snowflake alias Nostrum.Cache.Me @@ -45,7 +46,7 @@ defmodule Nostrum.Api.ApplicationCommand do Constants.guild_application_command_permissions(application_id, guild_id), permissions ) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -78,7 +79,7 @@ defmodule Nostrum.Api.ApplicationCommand do ]) :: {:ok, [map()]} | Api.error() def bulk_overwrite_global_commands(application_id \\ Me.get().id, commands) do Api.request(:put, Constants.global_application_commands(application_id), commands) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -113,7 +114,7 @@ defmodule Nostrum.Api.ApplicationCommand do commands ) do Api.request(:put, Constants.guild_application_commands(application_id, guild_id), commands) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -150,7 +151,7 @@ defmodule Nostrum.Api.ApplicationCommand do {:ok, map()} | Api.error() def create_global_command(application_id \\ Me.get().id, command) do Api.request(:post, Constants.global_application_commands(application_id), command) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -181,7 +182,7 @@ defmodule Nostrum.Api.ApplicationCommand do command ) do Api.request(:post, Constants.guild_application_commands(application_id, guild_id), command) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -256,7 +257,7 @@ defmodule Nostrum.Api.ApplicationCommand do permissions: permissions } ) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -289,7 +290,7 @@ defmodule Nostrum.Api.ApplicationCommand do command ) do Api.request(:patch, Constants.global_application_command(application_id, command_id), command) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -331,7 +332,7 @@ defmodule Nostrum.Api.ApplicationCommand do Constants.guild_application_command(application_id, guild_id, command_id), command ) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -360,7 +361,7 @@ defmodule Nostrum.Api.ApplicationCommand do :get, Constants.guild_application_command_permissions(application_id, guild_id, command_id) ) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -393,7 +394,7 @@ defmodule Nostrum.Api.ApplicationCommand do @spec global_commands(User.id()) :: {:ok, [map()]} | Api.error() def global_commands(application_id \\ Me.get().id) do Api.request(:get, Constants.global_application_commands(application_id)) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -416,7 +417,7 @@ defmodule Nostrum.Api.ApplicationCommand do guild_id ) do Api.request(:get, Constants.guild_application_command_permissions(application_id, guild_id)) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -436,6 +437,6 @@ defmodule Nostrum.Api.ApplicationCommand do @spec guild_commands(User.id(), Guild.id()) :: {:ok, [map()]} | Api.error() def guild_commands(application_id \\ Me.get().id, guild_id) do Api.request(:get, Constants.guild_application_commands(application_id, guild_id)) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end end diff --git a/lib/nostrum/api/auto_moderation.ex b/lib/nostrum/api/auto_moderation.ex index a43f1539c..b153ac30a 100644 --- a/lib/nostrum/api/auto_moderation.ex +++ b/lib/nostrum/api/auto_moderation.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.AutoModeration do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.AutoModerationRule alias Nostrum.Struct.Guild @@ -29,7 +30,7 @@ defmodule Nostrum.Api.AutoModeration do def create_rule(guild_id, options) do Api.request(:post, Constants.guild_auto_moderation_rule(guild_id), options) - |> Api.handle_request_with_decode({:struct, AutoModerationRule}) + |> Helpers.handle_request_with_decode({:struct, AutoModerationRule}) end @doc """ @@ -49,7 +50,7 @@ defmodule Nostrum.Api.AutoModeration do @spec rules(Guild.id()) :: {:ok, [AutoModerationRule.t()]} | Api.error() def rules(guild_id) do Api.request(:get, Constants.guild_auto_moderation_rule(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, AutoModerationRule}}) + |> Helpers.handle_request_with_decode({:list, {:struct, AutoModerationRule}}) end @doc """ @@ -60,7 +61,7 @@ defmodule Nostrum.Api.AutoModeration do {:ok, AutoModerationRule.t()} | Api.error() def rule(guild_id, rule_id) do Api.request(:get, Constants.guild_auto_moderation_rule(guild_id, rule_id)) - |> Api.handle_request_with_decode({:struct, AutoModerationRule}) + |> Helpers.handle_request_with_decode({:struct, AutoModerationRule}) end @doc """ @@ -76,6 +77,6 @@ defmodule Nostrum.Api.AutoModeration do def modify_rule(guild_id, rule_id, options) do Api.request(:patch, Constants.guild_auto_moderation_rule(guild_id, rule_id), options) - |> Api.handle_request_with_decode({:struct, AutoModerationRule}) + |> Helpers.handle_request_with_decode({:struct, AutoModerationRule}) end end diff --git a/lib/nostrum/api/channel.ex b/lib/nostrum/api/channel.ex index 880e64fc7..4c915dbd2 100644 --- a/lib/nostrum/api/channel.ex +++ b/lib/nostrum/api/channel.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Channel do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Snowflake alias Nostrum.Struct.Channel @@ -120,7 +121,7 @@ defmodule Nostrum.Api.Channel do def create(guild_id, %{} = options) when is_snowflake(guild_id) do Api.request(:post, Constants.guild_channels(guild_id), options) - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @doc ~S""" @@ -153,7 +154,7 @@ defmodule Nostrum.Api.Channel do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @doc """ @@ -241,7 +242,7 @@ defmodule Nostrum.Api.Channel do @spec get(Channel.id()) :: Api.error() | {:ok, Channel.t()} def get(channel_id) when is_snowflake(channel_id) do Api.request(:get, Constants.channel(channel_id)) - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @doc ~S""" @@ -303,7 +304,7 @@ defmodule Nostrum.Api.Channel do end Api.request(:get, Constants.channel_messages(channel_id), "", qs_params) - |> Api.handle_request_with_decode({:list, {:struct, Message}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Message}}) end @doc """ @@ -315,7 +316,7 @@ defmodule Nostrum.Api.Channel do @spec webhooks(Channel.id()) :: Api.error() | {:ok, [Webhook.t()]} def webhooks(channel_id) do Api.request(:get, Constants.webhooks_channel(channel_id)) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc ~S""" @@ -334,7 +335,7 @@ defmodule Nostrum.Api.Channel do @spec pinned_messages(Channel.id()) :: Api.error() | {:ok, [Message.t()]} def pinned_messages(channel_id) when is_snowflake(channel_id) do Api.request(:get, Constants.channel_pins(channel_id)) - |> Api.handle_request_with_decode({:list, {:struct, Message}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Message}}) end @doc ~S""" @@ -393,7 +394,7 @@ defmodule Nostrum.Api.Channel do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @doc """ diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex index 1993e6b40..e337b592a 100644 --- a/lib/nostrum/api/guild.ex +++ b/lib/nostrum/api/guild.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Guild do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.Channel alias Nostrum.Struct.Emoji @@ -60,7 +61,7 @@ defmodule Nostrum.Api.Guild do def add_member(guild_id, user_id, %{} = options) when is_snowflake(guild_id) and is_snowflake(user_id) do Api.request(:put, Constants.guild_member(guild_id, user_id), options) - |> Api.handle_request_with_decode({:struct, Member}) + |> Helpers.handle_request_with_decode({:struct, Member}) end @doc """ @@ -92,7 +93,7 @@ defmodule Nostrum.Api.Guild do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -156,7 +157,7 @@ defmodule Nostrum.Api.Guild do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Emoji}) + |> Helpers.handle_request_with_decode({:struct, Emoji}) end @doc """ @@ -243,7 +244,7 @@ defmodule Nostrum.Api.Guild do @spec get(Guild.id()) :: Api.error() | {:ok, Guild.rest_guild()} def get(guild_id) when is_snowflake(guild_id) do Api.request(:get, Constants.guild(guild_id)) - |> Api.handle_request_with_decode({:struct, Guild}) + |> Helpers.handle_request_with_decode({:struct, Guild}) end @doc ~S""" @@ -259,7 +260,7 @@ defmodule Nostrum.Api.Guild do @spec audit_log(Guild.id(), Api.options()) :: {:ok, AuditLog.t()} | Api.error() def audit_log(guild_id, options \\ []) do Api.request(:get, Constants.guild_audit_logs(guild_id), "", options) - |> Api.handle_request_with_decode({:struct, AuditLog}) + |> Helpers.handle_request_with_decode({:struct, AuditLog}) end @doc """ @@ -269,7 +270,7 @@ defmodule Nostrum.Api.Guild do @spec ban(Guild.id(), User.id()) :: Api.error() | {:ok, Ban.t()} def ban(guild_id, user_id) do Api.request(:get, Constants.guild_ban(guild_id, user_id)) - |> Api.handle_request_with_decode({:struct, Ban}) + |> Helpers.handle_request_with_decode({:struct, Ban}) end @doc """ @@ -280,7 +281,7 @@ defmodule Nostrum.Api.Guild do @spec bans(Guild.id()) :: Api.error() | {:ok, [User.t()]} def bans(guild_id) do Api.request(:get, Constants.guild_bans(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, User}}) + |> Helpers.handle_request_with_decode({:list, {:struct, User}}) end @doc ~S""" @@ -298,7 +299,7 @@ defmodule Nostrum.Api.Guild do @spec channels(Guild.id()) :: Api.error() | {:ok, [Channel.guild_channel()]} def channels(guild_id) when is_snowflake(guild_id) do Api.request(:get, Constants.guild_channels(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, Channel}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Channel}}) end @doc ~S""" @@ -311,7 +312,7 @@ defmodule Nostrum.Api.Guild do @spec emoji(Guild.id(), Emoji.id()) :: Api.error() | {:ok, Emoji.t()} def emoji(guild_id, emoji_id) do Api.request(:get, Constants.guild_emoji(guild_id, emoji_id)) - |> Api.handle_request_with_decode({:struct, Emoji}) + |> Helpers.handle_request_with_decode({:struct, Emoji}) end @doc """ @@ -323,7 +324,7 @@ defmodule Nostrum.Api.Guild do Api.error() | {:ok, [Integration.t()]} def integrations(guild_id) do Api.request(:get, Constants.guild_integrations(guild_id)) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -340,7 +341,7 @@ defmodule Nostrum.Api.Guild do @spec member(Guild.id(), User.id()) :: Api.error() | {:ok, Member.t()} def member(guild_id, user_id) when is_snowflake(guild_id) and is_snowflake(user_id) do Api.request(:get, Constants.guild_member(guild_id, user_id)) - |> Api.handle_request_with_decode({:struct, Member}) + |> Helpers.handle_request_with_decode({:struct, Member}) end @doc """ @@ -360,7 +361,7 @@ defmodule Nostrum.Api.Guild do @spec estimate_prune_count(Guild.id(), 1..30) :: Api.error() | {:ok, %{pruned: integer}} def estimate_prune_count(guild_id, days) when is_snowflake(guild_id) and days in 1..30 do Api.request(:get, Constants.guild_prune(guild_id), "", days: days) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc ~S""" @@ -377,7 +378,7 @@ defmodule Nostrum.Api.Guild do @spec roles(Guild.id()) :: Api.error() | {:ok, [Role.t()]} def roles(guild_id) when is_snowflake(guild_id) do Api.request(:get, Constants.guild_roles(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, Role}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Role}}) end @doc """ @@ -387,7 +388,7 @@ defmodule Nostrum.Api.Guild do @spec scheduled_events(Guild.id()) :: Api.error() | {:ok, [ScheduledEvent.t()]} def scheduled_events(guild_id) do Api.request(:get, Constants.guild_scheduled_events(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, ScheduledEvent}}) + |> Helpers.handle_request_with_decode({:list, {:struct, ScheduledEvent}}) end @doc """ @@ -399,7 +400,7 @@ defmodule Nostrum.Api.Guild do @spec webhooks(Guild.id()) :: Api.error() | {:ok, [Webhook.t()]} def webhooks(guild_id) do Api.request(:get, Constants.webhooks_guild(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, Webhook}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Webhook}}) end @doc """ @@ -408,7 +409,7 @@ defmodule Nostrum.Api.Guild do @spec widget(Guild.id()) :: Api.error() | {:ok, map} def widget(guild_id) do Api.request(:get, Constants.guild_widget(guild_id)) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -419,7 +420,7 @@ defmodule Nostrum.Api.Guild do @spec voice_region(Guild.id()) :: Api.error() | {:ok, [VoiceRegion.t()]} def voice_region(guild_id) do Api.request(:get, Constants.guild_voice_regions(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, VoiceRegion}}) + |> Helpers.handle_request_with_decode({:list, {:struct, VoiceRegion}}) end @doc """ @@ -448,7 +449,7 @@ defmodule Nostrum.Api.Guild do @spec emojis(Guild.id()) :: Api.error() | {:ok, [Emoji.t()]} def emojis(guild_id) do Api.request(:get, Constants.guild_emojis(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, Emoji}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Emoji}}) end @doc """ @@ -475,7 +476,7 @@ defmodule Nostrum.Api.Guild do def members(guild_id, %{} = options) when is_snowflake(guild_id) do Api.request(:get, Constants.guild_members(guild_id), "", options) - |> Api.handle_request_with_decode({:list, {:struct, Member}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Member}}) end @doc """ @@ -484,7 +485,7 @@ defmodule Nostrum.Api.Guild do @spec voice_regions() :: Api.error() | {:ok, [VoiceRegion.t()]} def voice_regions do Api.request(:get, Constants.regions()) - |> Api.handle_request_with_decode({:list, {:struct, VoiceRegion}}) + |> Helpers.handle_request_with_decode({:list, {:struct, VoiceRegion}}) end @doc """ @@ -506,7 +507,7 @@ defmodule Nostrum.Api.Guild do @spec modify_self_nick(Guild.id(), Api.options()) :: Api.error() | {:ok, %{nick: String.t()}} def modify_self_nick(guild_id, options \\ %{}) do Api.request(:patch, Constants.guild_me_nick(guild_id), options) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -564,7 +565,7 @@ defmodule Nostrum.Api.Guild do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Guild}) + |> Helpers.handle_request_with_decode({:struct, Guild}) end @doc """ @@ -628,7 +629,7 @@ defmodule Nostrum.Api.Guild do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Emoji}) + |> Helpers.handle_request_with_decode({:struct, Emoji}) end @doc """ @@ -697,7 +698,7 @@ defmodule Nostrum.Api.Guild do params: [], headers: Api.maybe_add_reason(reason) }) - |> Api.handle_request_with_decode({:struct, Member}) + |> Helpers.handle_request_with_decode({:struct, Member}) end @doc ~S""" @@ -731,7 +732,7 @@ defmodule Nostrum.Api.Guild do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:list, {:struct, Role}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Role}}) end @doc """ @@ -740,7 +741,7 @@ defmodule Nostrum.Api.Guild do @spec modify_widget(Guild.id(), map) :: Api.error() | {:ok, map} def modify_widget(guild_id, options) do Api.request(:patch, Constants.guild_widget(guild_id), options) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ diff --git a/lib/nostrum/api/helpers.ex b/lib/nostrum/api/helpers.ex new file mode 100644 index 000000000..7b3646bd1 --- /dev/null +++ b/lib/nostrum/api/helpers.ex @@ -0,0 +1,27 @@ +defmodule Nostrum.Api.Helpers do + @moduledoc """ + Helper functions for the Nostrum API. + """ + + alias Nostrum.Util + + defguard has_files(args) when is_map_key(args, :files) or is_map_key(args, :file) + + def handle_request_with_decode(response) + def handle_request_with_decode({:ok, body}), do: {:ok, Jason.decode!(body, keys: :atoms)} + def handle_request_with_decode({:error, _} = error), do: error + + def handle_request_with_decode(response, type) + # add_guild_member/3 can return both a 201 and a 204 + def handle_request_with_decode({:ok}, _type), do: {:ok} + def handle_request_with_decode({:error, _} = error, _type), do: error + + def handle_request_with_decode({:ok, body}, type) do + convert = + body + |> Jason.decode!(keys: :atoms) + |> Util.cast(type) + + {:ok, convert} + end +end diff --git a/lib/nostrum/api/interaction.ex b/lib/nostrum/api/interaction.ex index b2313e357..629d27210 100644 --- a/lib/nostrum/api/interaction.ex +++ b/lib/nostrum/api/interaction.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Interaction do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Cache.Me alias Nostrum.Struct.Interaction @@ -135,7 +136,7 @@ defmodule Nostrum.Api.Interaction do Constants.interaction_callback_original(id, token), Api.combine_embeds(response) |> Api.combine_files() ) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc """ @@ -154,6 +155,6 @@ defmodule Nostrum.Api.Interaction do @spec original_response(Interaction.t()) :: Api.error() | {:ok, Message.t()} def original_response(id \\ Me.get().id, token) do Api.request(:get, Constants.original_interaction_response(id, token)) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end end diff --git a/lib/nostrum/api/invite.ex b/lib/nostrum/api/invite.ex index 5e056ff0b..bf43270eb 100644 --- a/lib/nostrum/api/invite.ex +++ b/lib/nostrum/api/invite.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Invite do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.Invite alias Nostrum.Struct.Guild @@ -29,7 +30,7 @@ defmodule Nostrum.Api.Invite do @spec get(Invite.code(), Api.options()) :: Api.error() | {:ok, Invite.simple_invite()} def get(invite_code, options \\ []) when is_binary(invite_code) do Api.request(:get, Constants.invite(invite_code), "", options) - |> Api.handle_request_with_decode({:struct, Invite}) + |> Helpers.handle_request_with_decode({:struct, Invite}) end @doc ~S""" @@ -49,7 +50,7 @@ defmodule Nostrum.Api.Invite do @spec delete(Invite.code()) :: Api.error() | {:ok, Invite.simple_invite()} def delete(invite_code) when is_binary(invite_code) do Api.request(:delete, Constants.invite(invite_code)) - |> Api.handle_request_with_decode({:struct, Invite}) + |> Helpers.handle_request_with_decode({:struct, Invite}) end @doc ~S""" @@ -69,7 +70,7 @@ defmodule Nostrum.Api.Invite do @spec guild_invites(Guild.id()) :: Api.error() | {:ok, [Invite.detailed_invite()]} def guild_invites(guild_id) when is_snowflake(guild_id) do Api.request(:get, Constants.guild_invites(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, Invite}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Invite}}) end @doc ~S""" @@ -119,7 +120,7 @@ defmodule Nostrum.Api.Invite do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Invite}) + |> Helpers.handle_request_with_decode({:struct, Invite}) end @doc ~S""" @@ -140,6 +141,6 @@ defmodule Nostrum.Api.Invite do @spec channel_invites(Channel.id()) :: Api.error() | {:ok, [Invite.detailed_invite()]} def channel_invites(channel_id) when is_snowflake(channel_id) do Api.request(:get, Constants.channel_invites(channel_id)) - |> Api.handle_request_with_decode({:list, {:struct, Invite}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Invite}}) end end diff --git a/lib/nostrum/api/message.ex b/lib/nostrum/api/message.ex index ebcb64752..5cb8f250a 100644 --- a/lib/nostrum/api/message.ex +++ b/lib/nostrum/api/message.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Message do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.Message alias Nostrum.Struct.Channel @@ -101,7 +102,7 @@ defmodule Nostrum.Api.Message do |> Api.combine_files() Api.request(:post, Constants.channel_messages(channel_id), prepared_options) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc ~S""" @@ -303,7 +304,7 @@ defmodule Nostrum.Api.Message do |> Api.combine_files() Api.request(:patch, Constants.channel_message(channel_id, message_id), prepared_options) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc ~S""" @@ -332,7 +333,7 @@ defmodule Nostrum.Api.Message do def get(channel_id, message_id) when is_snowflake(channel_id) and is_snowflake(message_id) do Api.request(:get, Constants.channel_message(channel_id, message_id)) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc ~S""" @@ -361,6 +362,6 @@ defmodule Nostrum.Api.Message do "", params ) - |> Api.handle_request_with_decode({:list, {:struct, User}}) + |> Helpers.handle_request_with_decode({:list, {:struct, User}}) end end diff --git a/lib/nostrum/api/poll.ex b/lib/nostrum/api/poll.ex index 2ae2a5c79..9cdbdfa54 100644 --- a/lib/nostrum/api/poll.ex +++ b/lib/nostrum/api/poll.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Poll do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Util alias Nostrum.Struct.Message @@ -15,7 +16,7 @@ defmodule Nostrum.Api.Poll do @spec expire(Channel.id(), Message.id()) :: Api.error() | {:ok, Message.t()} def expire(channel_id, message_id) do Api.request(:post, Constants.poll_expire(channel_id, message_id)) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc ~S""" @@ -37,7 +38,7 @@ defmodule Nostrum.Api.Poll do "", params ) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() case result do {:ok, %{users: users}} -> {:ok, Util.cast(users, {:list, {:struct, User}})} diff --git a/lib/nostrum/api/role.ex b/lib/nostrum/api/role.ex index fece5bee9..27cbf0f2a 100644 --- a/lib/nostrum/api/role.ex +++ b/lib/nostrum/api/role.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Role do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.Guild.AuditLogEntry alias Nostrum.Struct.Guild.Role @@ -68,7 +69,7 @@ defmodule Nostrum.Api.Role do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Role}) + |> Helpers.handle_request_with_decode({:struct, Role}) end @doc ~S""" @@ -140,7 +141,7 @@ defmodule Nostrum.Api.Role do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Role}) + |> Helpers.handle_request_with_decode({:struct, Role}) end @doc """ diff --git a/lib/nostrum/api/scheduled_event.ex b/lib/nostrum/api/scheduled_event.ex index 581c82767..6b0f42267 100644 --- a/lib/nostrum/api/scheduled_event.ex +++ b/lib/nostrum/api/scheduled_event.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.ScheduledEvent do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.Guild alias Nostrum.Struct.Guild.AuditLogEntry @@ -47,7 +48,7 @@ defmodule Nostrum.Api.ScheduledEvent do params: [], headers: Api.maybe_add_reason(reason) }) - |> Api.handle_request_with_decode({:struct, ScheduledEvent}) + |> Helpers.handle_request_with_decode({:struct, ScheduledEvent}) end @doc """ @@ -68,7 +69,7 @@ defmodule Nostrum.Api.ScheduledEvent do Api.error() | {:ok, ScheduledEvent.t()} def get(guild_id, event_id) do Api.request(:get, Constants.guild_scheduled_event(guild_id, event_id)) - |> Api.handle_request_with_decode({:struct, ScheduledEvent}) + |> Helpers.handle_request_with_decode({:struct, ScheduledEvent}) end @doc """ @@ -86,7 +87,7 @@ defmodule Nostrum.Api.ScheduledEvent do Api.error() | {:ok, [ScheduledEvent.User.t()]} def users(guild_id, event_id, params \\ []) do Api.request(:get, Constants.guild_scheduled_event_users(guild_id, event_id), "", params) - |> Api.handle_request_with_decode({:list, {:struct, ScheduledEvent.User}}) + |> Helpers.handle_request_with_decode({:list, {:struct, ScheduledEvent.User}}) end @doc """ @@ -131,6 +132,6 @@ defmodule Nostrum.Api.ScheduledEvent do params: [], headers: Api.maybe_add_reason(reason) }) - |> Api.handle_request_with_decode({:struct, ScheduledEvent}) + |> Helpers.handle_request_with_decode({:struct, ScheduledEvent}) end end diff --git a/lib/nostrum/api/self.ex b/lib/nostrum/api/self.ex index 10df1054a..742640821 100644 --- a/lib/nostrum/api/self.ex +++ b/lib/nostrum/api/self.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Self do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.User alias Nostrum.Struct.Channel @@ -33,7 +34,7 @@ defmodule Nostrum.Api.Self do @spec application_information() :: Api.error() | {:ok, map()} def application_information do Api.request(:get, Constants.application_information()) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -49,7 +50,7 @@ defmodule Nostrum.Api.Self do @spec get() :: Api.error() | {:ok, User.t()} def get do Api.request(:get, Constants.me()) - |> Api.handle_request_with_decode({:struct, User}) + |> Helpers.handle_request_with_decode({:struct, User}) end @doc """ @@ -67,7 +68,7 @@ defmodule Nostrum.Api.Self do @spec dms() :: Api.error() | {:ok, [Channel.dm_channel()]} def dms do Api.request(:get, Constants.me_channels()) - |> Api.handle_request_with_decode({:list, {:struct, Channel}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Channel}}) end @doc ~S""" @@ -92,7 +93,7 @@ defmodule Nostrum.Api.Self do def modify(options) when is_map(options) do Api.request(:patch, Constants.me(), options) - |> Api.handle_request_with_decode({:struct, User}) + |> Helpers.handle_request_with_decode({:struct, User}) end @doc """ @@ -166,7 +167,7 @@ defmodule Nostrum.Api.Self do def guilds(options) when is_map(options) do Api.request(:get, Constants.me_guilds(), "", options) - |> Api.handle_request_with_decode({:list, {:struct, Guild}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Guild}}) end @doc """ @@ -175,6 +176,6 @@ defmodule Nostrum.Api.Self do @spec connections() :: Api.error() | {:ok, list()} def connections do Api.request(:get, Constants.me_connections()) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end end diff --git a/lib/nostrum/api/sticker.ex b/lib/nostrum/api/sticker.ex index 558e166d7..076a4f874 100644 --- a/lib/nostrum/api/sticker.ex +++ b/lib/nostrum/api/sticker.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Sticker do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Util alias Nostrum.Snowflake alias Nostrum.Constants @@ -74,7 +75,7 @@ defmodule Nostrum.Api.Sticker do headers: headers } |> Api.request() - |> Api.handle_request_with_decode({:struct, Sticker}) + |> Helpers.handle_request_with_decode({:struct, Sticker}) end @doc ~S""" @@ -95,7 +96,7 @@ defmodule Nostrum.Api.Sticker do @spec get(Sticker.id()) :: {:ok, Sticker.t()} | Api.error() def get(sticker_id) do Api.request(:get, Constants.sticker(sticker_id)) - |> Api.handle_request_with_decode({:struct, Sticker}) + |> Helpers.handle_request_with_decode({:struct, Sticker}) end @doc ~S""" @@ -107,7 +108,7 @@ defmodule Nostrum.Api.Sticker do @spec get(Guild.id(), Sticker.id()) :: Sticker.t() | Api.error() def get(guild_id, sticker_id) do Api.request(:get, Constants.guild_sticker(guild_id, sticker_id)) - |> Api.handle_request_with_decode({:struct, Sticker}) + |> Helpers.handle_request_with_decode({:struct, Sticker}) end @doc ~S""" @@ -119,7 +120,7 @@ defmodule Nostrum.Api.Sticker do @spec pack(Snowflake.t()) :: {:ok, Sticker.Pack.t()} | Api.error() def pack(id) do Api.request(:get, Constants.sticker_pack(id)) - |> Api.handle_request_with_decode({:struct, Sticker.Pack}) + |> Helpers.handle_request_with_decode({:struct, Sticker.Pack}) end @doc ~S""" @@ -129,7 +130,7 @@ defmodule Nostrum.Api.Sticker do @spec packs() :: {:ok, [Sticker.Pack.t()]} | Api.error() def packs do Api.request(:get, Constants.sticker_packs()) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() |> case do {:ok, %{sticker_packs: packs}} -> {:ok, Util.cast(packs, {:list, {:struct, Sticker.Pack}})} resp -> resp @@ -145,7 +146,7 @@ defmodule Nostrum.Api.Sticker do @spec list(Guild.id()) :: {:ok, [Sticker.t()]} | Api.error() def list(guild_id) do Api.request(:get, Constants.guild_stickers(guild_id)) - |> Api.handle_request_with_decode({:list, {:struct, Sticker}}) + |> Helpers.handle_request_with_decode({:list, {:struct, Sticker}}) end @doc ~S""" @@ -167,6 +168,6 @@ defmodule Nostrum.Api.Sticker do }) :: {:ok, Sticker.t()} | Api.error() def modify(guild_id, sticker_id, options) do Api.request(:patch, Constants.guild_sticker(guild_id, sticker_id), options) - |> Api.handle_request_with_decode({:struct, Sticker}) + |> Helpers.handle_request_with_decode({:struct, Sticker}) end end diff --git a/lib/nostrum/api/thread.ex b/lib/nostrum/api/thread.ex index 691bd9509..99bc05f94 100644 --- a/lib/nostrum/api/thread.ex +++ b/lib/nostrum/api/thread.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Thread do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Util alias Nostrum.Constants alias Nostrum.Struct.Channel @@ -9,7 +10,7 @@ defmodule Nostrum.Api.Thread do alias Nostrum.Struct.ThreadMember alias Nostrum.Struct.Message - import Api, only: [has_files: 1] + import Api.Helpers, only: [has_files: 1] @doc """ Add a user to a thread, requires the ability to send messages in the thread. @@ -27,7 +28,7 @@ defmodule Nostrum.Api.Thread do @spec member(Channel.id(), User.id()) :: {:ok, ThreadMember.t()} | Api.error() def member(thread_id, user_id) do Api.request(:get, Constants.thread_member(thread_id, user_id)) - |> Api.handle_request_with_decode({:struct, ThreadMember}) + |> Helpers.handle_request_with_decode({:struct, ThreadMember}) end @doc """ @@ -39,7 +40,7 @@ defmodule Nostrum.Api.Thread do @spec members(Channel.id()) :: {:ok, [ThreadMember.t()]} | Api.error() def members(thread_id) do Api.request(:get, Constants.thread_members(thread_id)) - |> Api.handle_request_with_decode({:list, {:struct, ThreadMember}}) + |> Helpers.handle_request_with_decode({:list, {:struct, ThreadMember}}) end @doc """ @@ -73,7 +74,7 @@ defmodule Nostrum.Api.Thread do def list(guild_id) do res = Api.request(:get, Constants.guild_active_threads(guild_id)) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() case res do {:ok, %{threads: channels, members: thread_members}} -> @@ -119,7 +120,7 @@ defmodule Nostrum.Api.Thread do params: options, headers: [] }) - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() case res do {:ok, %{threads: channels, members: thread_members, has_more: has_more}} -> @@ -230,7 +231,7 @@ defmodule Nostrum.Api.Thread do params: [], headers: Api.maybe_add_reason(reason) }) - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @doc """ @@ -283,7 +284,7 @@ defmodule Nostrum.Api.Thread do headers: headers } |> Api.request() - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end def create_in_forum(channel_id, options, reason) do @@ -294,7 +295,7 @@ defmodule Nostrum.Api.Thread do params: [], headers: Api.maybe_add_reason(reason) }) - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @type thread_with_message_params :: %{ @@ -334,6 +335,6 @@ defmodule Nostrum.Api.Thread do params: [], headers: Api.maybe_add_reason(reason) }) - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end end diff --git a/lib/nostrum/api/user.ex b/lib/nostrum/api/user.ex index d3bdd4d61..c18bc2681 100644 --- a/lib/nostrum/api/user.ex +++ b/lib/nostrum/api/user.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.User do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.User alias Nostrum.Struct.Channel @@ -21,7 +22,7 @@ defmodule Nostrum.Api.User do @spec create_dm(User.id()) :: Api.error() | {:ok, Channel.dm_channel()} def create_dm(user_id) when is_snowflake(user_id) do Api.request(:post, Constants.me_channels(), %{recipient_id: user_id}) - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @doc """ @@ -43,7 +44,7 @@ defmodule Nostrum.Api.User do Api.error() | {:ok, Channel.group_dm_channel()} def create_group_dm(access_tokens, nicks) when is_list(access_tokens) and is_map(nicks) do Api.request(:post, Constants.me_channels(), %{access_tokens: access_tokens, nicks: nicks}) - |> Api.handle_request_with_decode({:struct, Channel}) + |> Helpers.handle_request_with_decode({:struct, Channel}) end @doc """ @@ -55,6 +56,6 @@ defmodule Nostrum.Api.User do @spec get(User.id()) :: Api.error() | {:ok, User.t()} def get(user_id) do Api.request(:get, Constants.user(user_id)) - |> Api.handle_request_with_decode({:struct, User}) + |> Helpers.handle_request_with_decode({:struct, User}) end end diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex index febd82617..4e9c8590f 100644 --- a/lib/nostrum/api/webhook.ex +++ b/lib/nostrum/api/webhook.ex @@ -1,5 +1,6 @@ defmodule Nostrum.Api.Webhook do alias Nostrum.Api + alias Nostrum.Api.Helpers alias Nostrum.Constants alias Nostrum.Struct.Channel alias Nostrum.Struct.Webhook @@ -35,7 +36,7 @@ defmodule Nostrum.Api.Webhook do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end @doc """ @@ -75,7 +76,7 @@ defmodule Nostrum.Api.Webhook do Constants.webhook_message_edit(webhook_id, webhook_token, message_id), Api.combine_embeds(args) |> Api.combine_files() ) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc """ @@ -148,7 +149,7 @@ defmodule Nostrum.Api.Webhook do Api.combine_embeds(args) |> Api.combine_files(), params ) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc """ @@ -160,7 +161,7 @@ defmodule Nostrum.Api.Webhook do @spec get(Webhook.id()) :: Api.error() | {:ok, Webhook.t()} def get(webhook_id) do Api.request(:get, Constants.webhook(webhook_id)) - |> Api.handle_request_with_decode({:struct, Webhook}) + |> Helpers.handle_request_with_decode({:struct, Webhook}) end @doc """ @@ -171,7 +172,7 @@ defmodule Nostrum.Api.Webhook do Api.error() | {:ok, Message.t()} def get_message(webhook, message_id) do Api.request(:get, Constants.webhook_message(webhook.id, webhook.token, message_id)) - |> Api.handle_request_with_decode({:struct, Message}) + |> Helpers.handle_request_with_decode({:struct, Message}) end @doc """ @@ -188,7 +189,7 @@ defmodule Nostrum.Api.Webhook do Api.error() | {:ok, Webhook.t()} def get_with_token(webhook_id, webhook_token) do Api.request(:get, Constants.webhook_token(webhook_id, webhook_token)) - |> Api.handle_request_with_decode({:struct, Webhook}) + |> Helpers.handle_request_with_decode({:struct, Webhook}) end @doc """ @@ -218,7 +219,7 @@ defmodule Nostrum.Api.Webhook do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode({:struct, Webhook}) + |> Helpers.handle_request_with_decode({:struct, Webhook}) end @doc """ @@ -253,6 +254,6 @@ defmodule Nostrum.Api.Webhook do headers: Api.maybe_add_reason(reason) } |> Api.request() - |> Api.handle_request_with_decode() + |> Helpers.handle_request_with_decode() end end From 938f55aa8023bcd0e4509eebced910d7d0501f58 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 16:45:12 -0700 Subject: [PATCH 20/42] Update helpers.ex Co-authored-by: Michael --- lib/nostrum/api/helpers.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/nostrum/api/helpers.ex b/lib/nostrum/api/helpers.ex index 7b3646bd1..64ab3bdb9 100644 --- a/lib/nostrum/api/helpers.ex +++ b/lib/nostrum/api/helpers.ex @@ -1,7 +1,5 @@ defmodule Nostrum.Api.Helpers do - @moduledoc """ - Helper functions for the Nostrum API. - """ + @moduledoc false alias Nostrum.Util From f21105639763310c0993f45b34955844b46ebd1a Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 14:59:16 -0700 Subject: [PATCH 21/42] Add bypass for testing --- mix.exs | 3 ++- mix.lock | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 49cc1bedf..08c5ce4c3 100644 --- a/mix.exs +++ b/mix.exs @@ -153,7 +153,8 @@ defmodule Nostrum.Mixfile do {:credo, "~> 1.7.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, {:benchee, "~> 1.1", only: :dev, runtime: false}, - {:recon, "~> 2.3", only: :dev, optional: true} + {:recon, "~> 2.3", only: :dev, optional: true}, + {:bypass, "~> 2.1", only: :test} ] end diff --git a/mix.lock b/mix.lock index bad2b2d46..9fe23cd84 100644 --- a/mix.lock +++ b/mix.lock @@ -2,8 +2,11 @@ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "castle": {:hex, :castle, "0.3.0", "47b1a550b2348a6d7e60e43ded1df19dca601ed21ef6f267c3dbb1b3a301fbf5", [:mix], [{:forecastle, "~> 0.1.0", [hex: :forecastle, repo: "hexpm", optional: false]}], "hexpm", "dbdc1c171520c4591101938a3d342dec70d36b7f5b102a5c138098581e35fcef"}, "certifi": {:hex, :certifi, "2.13.0", "e52be248590050b2dd33b0bb274b56678f9068e67805dca8aa8b1ccdb016bbf6", [:rebar3], [], "hexpm", "8f3d9533a0f06070afdfd5d596b32e21c6580667a492891851b0e2737bc507a1"}, + "cowboy": {:hex, :cowboy, "2.11.0", "356bf784599cf6f2cdc6ad12fdcfb8413c2d35dab58404cf000e1feaed3f5645", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0fa395437f1b0e104e0e00999f39d2ac5f4082ac5049b67a5b6d56ecc31b1403"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, @@ -21,6 +24,11 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, } From e4b462fc6559f72526cfcabce89f17809f59cd10 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 22:18:41 -0700 Subject: [PATCH 22/42] WIP Nostrum.Api delegate refactoring --- lib/nostrum/api.ex | 2299 ++++++++++++-------------------------------- 1 file changed, 627 insertions(+), 1672 deletions(-) diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index c2201759c..f262731af 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -63,13 +63,12 @@ defmodule Nostrum.Api do Invite, Message, Message.Poll, - Sticker, ThreadMember, User, Webhook } - alias Nostrum.Struct.Guild.{AuditLog, AuditLogEntry, Member, Role, ScheduledEvent} + alias Nostrum.Struct.Guild.{AuditLogEntry, Member, Role, ScheduledEvent} @typedoc """ Represents a failed response from the API. @@ -156,127 +155,39 @@ defmodule Nostrum.Api do @typedoc since: "0.7.0" @type allowed_mentions :: allowed_mention | [allowed_mention] - @doc """ - Updates the status of the bot for a certain shard. - - ## Parameters - - `pid` - Pid of the shard. - - `status` - Status of the bot. - - `game` - The 'playing' text of the bot. Empty will clear. - - `type` - The type of status to show. 0 (Playing) | 1 (Streaming) | 2 (Listening) | 3 (Watching) - - `stream` - URL of twitch.tv stream + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.update_shard_status/5` directly instead. """ - @spec update_shard_status(pid, status, String.t(), integer, String.t() | nil) :: :ok - def update_shard_status(pid, status, game, type \\ 0, stream \\ nil) do - Nostrum.Api.Self.update_shard_status(pid, status, game, type, stream) - end + defdelegate update_shard_status(pid, status, game, type \\ 0, stream \\ nil), + to: Nostrum.Api.Self - @doc """ - Updates the status of the bot for all shards. - - See `update_shard_status/5` for usage. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.update_status/4` directly instead. """ - @spec update_status(status, String.t(), integer, String.t() | nil) :: :ok - def update_status(status, game, type \\ 0, stream \\ nil) do - Nostrum.Api.Self.update_status(status, game, type, stream) - end + defdelegate update_status(status, game, type \\ 0, stream \\ nil), + to: Nostrum.Api.Self - @doc """ - Joins, moves, or disconnects the bot from a voice channel. - - The correct shard to send the update to will be inferred from the - `guild_id`. If a corresponding `guild_id` is not found a cache error will be - raised. - - To disconnect from a channel, `channel_id` should be set to `nil`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.update_voice_state/4` directly instead. """ - @spec update_voice_state(Guild.id(), Channel.id() | nil, boolean, boolean) :: no_return | :ok - def update_voice_state(guild_id, channel_id, self_mute \\ false, self_deaf \\ false) do - Nostrum.Api.Self.update_voice_state(guild_id, channel_id, self_mute, self_deaf) - end - - @doc ~S""" - Posts a message to a guild text or DM channel. + defdelegate update_voice_state(guild_id, channel_id, self_mute \\ false, self_deaf \\ false), + to: Nostrum.Api.Self - This endpoint requires the `VIEW_CHANNEL` and `SEND_MESSAGES` permissions. It - may situationally need the `SEND_MESSAGES_TTS` permission. It fires the - `t:Nostrum.Consumer.message_create/0` event. - - If `options` is a string, `options` will be used as the message's content. - - If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:content` (string) - the message contents (up to 2000 characters) - * `:nonce` (`t:Nostrum.Snowflake.t/0`) - a nonce that can be used for - optimistic message sending - * `:tts` (boolean) - true if this is a TTS message - * `:file` (`t:Path.t/0` | map) - the path of the file being sent, or a map with the following keys - if sending a binary from memory - * `:name` (string) - the name of the file - * `:body` (string) - binary you wish to send - * `:files` - a list of files where each element is the same format as the `:file` option. If both - `:file` and `:files` are specified, `:file` will be prepended to the `:files` list. - * `:embeds` (`t:Nostrum.Struct.Embed.t/0`) - a list of embedded rich content - * `:allowed_mentions` (`t:allowed_mentions/0`) - see the allowed mentions type documentation - * `:message_reference` (`map`) - See "Message references" below - * `:poll` (`t:Nostrum.Struct.Message.Poll.t/0`) - A poll object to send with the message - - At least one of the following is required: `:content`, `:file`, `:embeds`, `:poll`. - - ### Message reference - - You can create a reply to another message on guilds using this option, given - that you have the ``VIEW_MESSAGE_HISTORY`` permission. To do so, include the - ``message_reference`` field in your call. The complete structure - documentation can be found [on the Discord Developer - Portal](https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure), - but simply passing ``message_id`` will suffice: - - ```elixir - def my_command(msg) do - # Reply to the author - ``msg`` is a ``Nostrum.Struct.Message`` - Nostrum.Api.create_message( - msg.channel_id, - content: "Hello", - message_reference: %{message_id: msg.id} - ) - end - ``` - - Passing a list will merge the settings provided - - ## Examples - - ```elixir - Nostrum.Api.create_message(43189401384091, content: "hello world!") - - Nostrum.Api.create_message(43189401384091, "hello world!") - - import Nostrum.Struct.Embed - embed = - %Nostrum.Struct.Embed{} - |> put_title("embed") - |> put_description("new desc") - Nostrum.Api.create_message(43189401384091, embeds: [embed]) - - Nostrum.Api.create_message(43189401384091, file: "/path/to/file.txt") - - Nostrum.Api.create_message(43189401384091, content: "hello world!", embeds: [embed], file: "/path/to/file.txt") - - Nostrum.Api.create_message(43189401384091, content: "Hello @everyone", allowed_mentions: :none) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.create/2` directly instead. """ - @spec create_message(Channel.id() | Message.t(), options | String.t()) :: - error | {:ok, Message.t()} - def create_message(channel_id, options) do - Nostrum.Api.Message.create(channel_id, options) - end + defdelegate create_message(channel_id, options), + to: Nostrum.Api.Message, + as: :create @doc ~S""" Same as `create_message/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_message!(Channel.id() | Message.t(), options | String.t()) :: no_return | Message.t() def create_message!(channel_id, options) do @@ -284,54 +195,18 @@ defmodule Nostrum.Api do |> bangify end - @doc ~S""" - Edits a previously sent message in a channel. - - This endpoint requires the `VIEW_CHANNEL` permission. It fires the - `t:Nostrum.Consumer.message_update/0` event. - - If `options` is a string, `options` will be used as the message's content. - - If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:content` (string) - the message contents (up to 2000 characters) - * `:embeds` (`t:Nostrum.Struct.Embed.t/0`) - a list of embedded rich content - * `:files` - a list of files where each element is the same format as the - `:file` option. If both `:file` and `:files` are specified, `:file` will be - prepended to the `:files` list. See `create_message/2` for more information. - - Note that if you edit a message with attachments, all attachments that should - be present after edit **must** be included in your request body. This - includes attachments that were sent in the original request. - - ## Examples - - ```elixir - Nostrum.Api.edit_message(43189401384091, 1894013840914098, content: "hello world!") - - Nostrum.Api.edit_message(43189401384091, 1894013840914098, "hello world!") - - import Nostrum.Struct.Embed - embed = - %Nostrum.Struct.Embed{} - |> put_title("embed") - |> put_description("new desc") - Nostrum.Api.edit_message(43189401384091, 1894013840914098, embeds: [embed]) - - Nostrum.Api.edit_message(43189401384091, 1894013840914098, content: "hello world!", embeds: [embed]) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.edit/3` directly instead. """ - @spec edit_message(Channel.id(), Message.id(), options | String.t()) :: - error | {:ok, Message.t()} - def edit_message(channel_id, message_id, options) do - Nostrum.Api.Message.edit(channel_id, message_id, options) - end + defdelegate edit_message(channel_id, message_id, options), + to: Nostrum.Api.Message, + as: :edit @doc ~S""" Same as `edit_message/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec edit_message!(Channel.id(), Message.id(), options) :: no_return | Message.t() def edit_message!(channel_id, message_id, options) do edit_message(channel_id, message_id, options) @@ -350,6 +225,7 @@ defmodule Nostrum.Api do @doc ~S""" Same as `edit_message/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec edit_message!(Message.t(), options) :: no_return | Message.t() def edit_message!(message, options) do edit_message(message, options) @@ -365,29 +241,18 @@ defmodule Nostrum.Api do delete_message(c_id, id) end - @doc ~S""" - Deletes a message. - - This endpoint requires the 'VIEW_CHANNEL' and 'MANAGE_MESSAGES' permission. It - fires the `MESSAGE_DELETE` event. - - If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.delete_message(43189401384091, 43189401384091) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.delete/2` directly instead. """ - @spec delete_message(Channel.id(), Message.id()) :: error | {:ok} - def delete_message(channel_id, message_id) - when is_snowflake(channel_id) and is_snowflake(message_id) do - Nostrum.Api.Message.delete(channel_id, message_id) - end + defdelegate delete_message(channel_id, message_id), + to: Nostrum.Api.Message, + as: :delete @doc ~S""" Same as `delete_message/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_message!(Message.t()) :: error | {:ok} def delete_message!(%Message{id: id, channel_id: c_id}) do delete_message(c_id, id) @@ -397,426 +262,257 @@ defmodule Nostrum.Api do @doc ~S""" Same as `delete_message/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_message!(Channel.id(), Message.id()) :: no_return | {:ok} def delete_message!(channel_id, message_id) do delete_message(channel_id, message_id) |> bangify end - @doc ~S""" - Creates a reaction for a message. - - This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY` - permissions. Additionally, if nobody else has reacted to the message with - the `emoji`, this endpoint requires the `ADD_REACTIONS` permission. It - fires a `t:Nostrum.Consumer.message_reaction_add/0` event. - - If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - # Using a Nostrum.Struct.Emoji. - emoji = %Nostrum.Struct.Emoji{id: 43819043108, name: "foxbot"} - Nostrum.Api.create_reaction(123123123123, 321321321321, emoji) - - # Using a base 16 emoji string. - Nostrum.Api.create_reaction(123123123123, 321321321321, "\xF0\x9F\x98\x81") - - ``` - - For other emoji string examples, see `t:Nostrum.Struct.Emoji.api_name/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.react/3` directly instead. """ - @spec create_reaction(Channel.id(), Message.id(), emoji) :: error | {:ok} - def create_reaction(channel_id, message_id, emoji) do - Nostrum.Api.Message.react(channel_id, message_id, emoji) - end + defdelegate create_reaction(channel_id, message_id, emoji), + to: Nostrum.Api.Message, + as: :react @doc ~S""" Same as `create_reaction/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_reaction!(Channel.id(), Message.id(), emoji) :: no_return | {:ok} def create_reaction!(channel_id, message_id, emoji) do create_reaction(channel_id, message_id, emoji) |> bangify end - @doc ~S""" - Deletes a reaction the current user has made for the message. - - This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY` - permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove/0` event. - - If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - See `create_reaction/3` for similar examples. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.unreact/3` directly instead. """ - @spec delete_own_reaction(Channel.id(), Message.id(), emoji) :: error | {:ok} - def delete_own_reaction(channel_id, message_id, emoji) - - def delete_own_reaction(channel_id, message_id, %Emoji{} = emoji), - do: delete_own_reaction(channel_id, message_id, Emoji.api_name(emoji)) - - def delete_own_reaction(channel_id, message_id, emoji_api_name) do - Nostrum.Api.Message.unreact(channel_id, message_id, emoji_api_name) - end + defdelegate delete_own_reaction(channel_id, message_id, emoji), + to: Nostrum.Api.Message, + as: :unreact @doc ~S""" Same as `delete_own_reaction/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_own_reaction!(Channel.id(), Message.id(), emoji) :: no_return | {:ok} def delete_own_reaction!(channel_id, message_id, emoji) do delete_own_reaction(channel_id, message_id, emoji) |> bangify end - @doc ~S""" - Deletes another user's reaction from a message. - - This endpoint requires the `VIEW_CHANNEL`, `READ_MESSAGE_HISTORY`, and - `MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove/0` event. - - If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - See `create_reaction/3` for similar examples. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.delete_user_reaction/4` directly instead. """ - @spec delete_user_reaction(Channel.id(), Message.id(), emoji, User.id()) :: error | {:ok} - def delete_user_reaction(channel_id, message_id, emoji, user_id) - - def delete_user_reaction(channel_id, message_id, %Emoji{} = emoji, user_id), - do: delete_user_reaction(channel_id, message_id, Emoji.api_name(emoji), user_id) - - def delete_user_reaction(channel_id, message_id, emoji_api_name, user_id) do - Nostrum.Api.Message.delete_user_reaction(channel_id, message_id, emoji_api_name, user_id) - end + defdelegate delete_user_reaction(channel_id, message_id, emoji, user_id), + to: Nostrum.Api.Message @doc ~S""" Same as `delete_user_reaction/4`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_user_reaction!(Channel.id(), Message.id(), emoji, User.id()) :: no_return | {:ok} def delete_user_reaction!(channel_id, message_id, emoji, user_id) do delete_user_reaction(channel_id, message_id, emoji, user_id) |> bangify end - @doc ~S""" - Deletes all reactions of a given emoji from a message. - - This endpoint requires the `MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove_emoji/0` event. - - If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - See `create_reaction/3` for similar examples. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.delete_emoji_reactions/3` directly instead. """ - @spec delete_reaction(Channel.id(), Message.id(), emoji) :: error | {:ok} - def delete_reaction(channel_id, message_id, emoji) - - def delete_reaction(channel_id, message_id, %Emoji{} = emoji), - do: delete_reaction(channel_id, message_id, Emoji.api_name(emoji)) - - def delete_reaction(channel_id, message_id, emoji_api_name) do - Nostrum.Api.Message.delete_emoji_reactions(channel_id, message_id, emoji_api_name) - end + defdelegate delete_reaction(channel_id, message_id, emoji), + to: Nostrum.Api.Message, + as: :delete_emoji_reactions @doc ~S""" Same as `delete_reaction/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_reaction!(Channel.id(), Message.id(), emoji) :: no_return | {:ok} def delete_reaction!(channel_id, message_id, emoji) do delete_reaction(channel_id, message_id, emoji) |> bangify end - @doc ~S""" - Gets all users who reacted with an emoji. - - This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY` permissions. - - If successful, returns `{:ok, users}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - The optional `params` are `after`, the user ID to query after, absent by default, - and `limit`, the max number of users to return, 1-100, 25 by default. - - See `create_reaction/3` for similar examples. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.reactions/4` directly instead. """ - @spec get_reactions(Channel.id(), Message.id(), emoji, keyword()) :: error | {:ok, [User.t()]} - def get_reactions(channel_id, message_id, emoji, params \\ []) - - def get_reactions(channel_id, message_id, %Emoji{} = emoji, params), - do: get_reactions(channel_id, message_id, Emoji.api_name(emoji), params) - - def get_reactions(channel_id, message_id, emoji_api_name, params) do - Nostrum.Api.Message.reactions(channel_id, message_id, emoji_api_name, params) - end + defdelegate get_reactions(channel_id, message_id, emoji, params \\ []), + to: Nostrum.Api.Message, + as: :reactions @doc ~S""" Same as `get_reactions/4`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_reactions!(Channel.id(), Message.id(), emoji, keyword()) :: no_return | [User.t()] def get_reactions!(channel_id, message_id, emoji, params \\ []) do get_reactions(channel_id, message_id, emoji, params) |> bangify end - @doc ~S""" - Deletes all reactions from a message. - - This endpoint requires the `VIEW_CHANNEL`, `READ_MESSAGE_HISTORY`, and - `MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove_all/0` event. - - If successful, returns `{:ok}`. Otherwise, return `t:Nostrum.Api.error/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.clear_reactions/2` directly instead. """ - @spec delete_all_reactions(Channel.id(), Message.id()) :: error | {:ok} - def delete_all_reactions(channel_id, message_id) do - Nostrum.Api.Message.clear_reactions(channel_id, message_id) - end + defdelegate delete_all_reactions(channel_id, message_id), + to: Nostrum.Api.Message, + as: :clear_reactions @doc ~S""" Same as `delete_all_reactions/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_all_reactions!(Channel.id(), Message.id()) :: no_return | {:ok} def delete_all_reactions!(channel_id, message_id) do delete_all_reactions(channel_id, message_id) |> bangify end - @doc ~S""" - Get voters for the provided answer on the poll attached to the provided message. - - If successful, returns `{:ok, users}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - The optional `params` are `after`, the user ID to query after, absent by default, - and `limit`, the max number of users to return, 1-100, 25 by default. Results are - sorted by Discord user snowflake (ID) in ascending order. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Poll.answer_voters/4` directly instead. """ - @spec get_poll_answer_voters(Channel.id(), Message.id(), Poll.Answer.answer_id()) :: - error | {:ok, [User.t()]} - def get_poll_answer_voters(channel_id, message_id, answer_id, params \\ []) do - Nostrum.Api.Poll.answer_voters(channel_id, message_id, answer_id, params) - end + defdelegate get_poll_answer_voters(channel_id, message_id, answer_id, params \\ []), + to: Nostrum.Api.Poll, + as: :answer_voters @doc ~S""" Same as `get_poll_answer_voters/4`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_poll_answer_voters!(Channel.id(), Message.id(), Poll.Answer.answer_id()) :: [User.t()] def get_poll_answer_voters!(channel_id, message_id, answer_id, params \\ []) do get_poll_answer_voters(channel_id, message_id, answer_id, params) |> bangify end - @doc ~S""" - Expire (close voting on) a poll before the scheduled end time. - - Returns the original message containing the poll. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Poll.expire/2` directly instead. """ - @spec expire_poll(Channel.id(), Message.id()) :: error | {:ok, Message.t()} - def expire_poll(channel_id, message_id) do - Nostrum.Api.Poll.expire(channel_id, message_id) - end + defdelegate expire_poll(channel_id, message_id), + to: Nostrum.Api.Poll, + as: :expire @doc ~S""" Same as `expire_poll/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec expire_poll!(Channel.id(), Message.id()) :: Message.t() def expire_poll!(channel_id, message_id) do expire_poll(channel_id, message_id) |> bangify end - @doc ~S""" - Gets a channel. - - If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_channel(381889573426429952) - {:ok, %Nostrum.Struct.Channel{id: 381889573426429952}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.get/1` directly instead. """ - @spec get_channel(Channel.id()) :: error | {:ok, Channel.t()} - def get_channel(channel_id) when is_snowflake(channel_id) do - Nostrum.Api.Channel.get(channel_id) - end + defdelegate get_channel(channel_id), + to: Nostrum.Api.Channel, + as: :get @doc ~S""" Same as `get_channel/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_channel!(Channel.id()) :: no_return | Channel.t() def get_channel!(channel_id) do get_channel(channel_id) |> bangify end - @doc ~S""" - Modifies a channel's settings. - - An optional `reason` can be given for the guild audit log. - - If a `t:Nostrum.Struct.Channel.guild_channel/0` is being modified, this - endpoint requires the `MANAGE_CHANNEL` permission. It fires a - `t:Nostrum.Consumer.channel_update/0` event. If a - `t:Nostrum.Struct.Channel.guild_category_channel/0` is being modified, then this - endpoint fires multiple `t:Nostrum.Consumer.channel_update/0` events. - - If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - 2-100 character channel name - * `:position` (integer) - the position of the channel in the left-hand listing - * `:topic` (string) (`t:Nostrum.Struct.Channel.text_channel/0` only) - - 0-1024 character channel topic - * `:nsfw` (boolean) (`t:Nostrum.Struct.Channel.text_channel/0` only) - - if the channel is nsfw - * `:bitrate` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - - the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers) - * `:user_limit` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) - - the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit - * `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) - - channel or category-specific permissions - * `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) (`t:Nostrum.Struct.Channel.guild_channel/0` only) - - id of the new parent category for a channel - - ## Examples - - ```elixir - Nostrum.Api.modify_channel(41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion") - {:ok, %Nostrum.Struct.Channel{id: 41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion"}} - - Nostrum.Api.modify_channel(41771983423143933) - {:ok, %Nostrum.Struct.Channel{id: 41771983423143933}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.modify/3` directly instead. """ - @spec modify_channel(Channel.id(), options, AuditLogEntry.reason()) :: - error | {:ok, Channel.t()} - def modify_channel(channel_id, options, reason \\ nil) do - Nostrum.Api.Channel.modify(channel_id, options, reason) - end + defdelegate modify_channel(channel_id, options, reason \\ nil), + to: Nostrum.Api.Channel, + as: :modify @doc ~S""" Same as `modify_channel/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_channel!(Channel.id(), options, AuditLogEntry.reason()) :: no_return | Channel.t() def modify_channel!(channel_id, options, reason \\ nil) do modify_channel(channel_id, options, reason) |> bangify end - @doc ~S""" - Deletes a channel. - - An optional `reason` can be provided for the guild audit log. - - If deleting a `t:Nostrum.Struct.Channel.guild_channel/0`, this endpoint requires - the `MANAGE_CHANNELS` permission. It fires a - `t:Nostrum.Consumer.channel_delete/0`. If a `t:Nostrum.Struct.Channel.guild_category_channel/0` - is deleted, then a `t:Nostrum.Consumer.channel_update/0` event will fire - for each channel under the category. - - If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.delete_channel(421533712753360896) - {:ok, %Nostrum.Struct.Channel{id: 421533712753360896}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.delete/2` directly instead. """ - @spec delete_channel(Channel.id(), AuditLogEntry.reason()) :: error | {:ok, Channel.t()} - def delete_channel(channel_id, reason \\ nil) when is_snowflake(channel_id) do - Nostrum.Api.Channel.delete(channel_id, reason) - end + defdelegate delete_channel(channel_id, reason \\ nil), + to: Nostrum.Api.Channel, + as: :delete @doc ~S""" Same as `delete_channel/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_channel!(Channel.id(), AuditLogEntry.reason()) :: no_return | Channel.t() def delete_channel!(channel_id, reason \\ nil) do delete_channel(channel_id, reason) |> bangify end - @doc ~S""" - Retrieves a channel's messages around a `locator` up to a `limit`. - - This endpoint requires the 'VIEW_CHANNEL' permission. If the current user - is missing the 'READ_MESSAGE_HISTORY' permission, then this function will - return no messages. - - If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_channel_messages(43189401384091, 5, {:before, 130230401384}) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.messages/3` directly instead. """ - @spec get_channel_messages(Channel.id(), limit, locator) :: error | {:ok, [Message.t()]} - def get_channel_messages(channel_id, limit, locator \\ {}) when is_snowflake(channel_id) do - Nostrum.Api.Channel.messages(channel_id, limit, locator) - end + defdelegate get_channel_messages(channel_id, limit, locator \\ {}), + to: Nostrum.Api.Channel, + as: :messages @doc ~S""" Same as `get_channel_messages/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_channel_messages!(Channel.id(), limit, locator) :: no_return | [Message.t()] def get_channel_messages!(channel_id, limit, locator \\ {}) do get_channel_messages(channel_id, limit, locator) |> bangify end - @doc ~S""" - Retrieves a message from a channel. - - This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions. - - If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_channel_message(43189401384091, 198238475613443) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Message.get/2` directly instead. """ - @spec get_channel_message(Channel.id(), Message.id()) :: error | {:ok, Message.t()} - def get_channel_message(channel_id, message_id) - when is_snowflake(channel_id) and is_snowflake(message_id) do - Nostrum.Api.Message.get(channel_id, message_id) - end + defdelegate get_channel_message(channel_id, message_id), + to: Nostrum.Api.Message, + as: :get @doc ~S""" Same as `get_channel_message/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_channel_message!(Channel.id(), Message.id()) :: no_return | Message.t() def get_channel_message!(channel_id, message_id) do get_channel_message(channel_id, message_id) |> bangify end - @doc """ - Deletes multiple messages from a channel. - - `messages` is a list of `Nostrum.Struct.Message.id` that you wish to delete. - When given more than 100 messages, this function will chunk the given message - list into blocks of 100 and send them off to the API. It will stop deleting - on the first error that occurs. Keep in mind that deleting thousands of - messages will take a pretty long time and it may be proper to just delete - the channel you want to bulk delete in and recreate it. - - This method can only delete messages sent within the last two weeks. - `Filter` is an optional parameter that specifies whether messages sent over - two weeks ago should be filtered out; defaults to `true`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.bulk_delete_messages/3` directly instead. """ - @spec bulk_delete_messages(integer, [Nostrum.Struct.Message.id()], boolean) :: error | {:ok} - def bulk_delete_messages(channel_id, messages, filter) do - Nostrum.Api.Channel.bulk_delete_messages(channel_id, messages, filter) - end + defdelegate bulk_delete_messages(channel_id, messages, filter), + to: Nostrum.Api.Channel @doc """ Same as `bulk_delete_messages/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec bulk_delete_messages!(integer, [Nostrum.Struct.Message.id()], boolean) :: no_return | {:ok} def bulk_delete_messages!(channel_id, messages, filter \\ true) do @@ -824,40 +520,18 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Edit the permission overwrites for a user or role. - - Role or user to overwrite is specified by `overwrite_id`. - - `permission_info` is a map with the following keys: - * `type` - Required; `member` if editing a user, `role` if editing a role. - * `allow` - Bitwise value of allowed permissions. - * `deny` - Bitwise value of denied permissions. - * `type` - `member` if editing a user, `role` if editing a role. - - An optional `reason` can be provided for the audit log. - - `allow` and `deny` are defaulted to `0`, meaning that even if you don't - specify them, they will override their respective former values in an - existing overwrite. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.edit_permissions/4` directly instead. """ - @spec edit_channel_permissions( - integer, - integer, - %{ - required(:type) => String.t(), - optional(:allow) => integer, - optional(:deny) => integer - }, - AuditLogEntry.reason() - ) :: error | {:ok} - def edit_channel_permissions(channel_id, overwrite_id, permission_info, reason \\ nil) do - Nostrum.Api.Channel.edit_permissions(channel_id, overwrite_id, permission_info, reason) - end + defdelegate edit_channel_permissions(channel_id, overwrite_id, permission_info, reason \\ nil), + to: Nostrum.Api.Channel, + as: :edit_permissions @doc """ Same as `edit_channel_permissions/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec edit_channel_permissions!( integer, integer, @@ -873,85 +547,44 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Delete a channel permission for a user or role. - - Role or user overwrite to delete is specified by `channel_id` and `overwrite_id`. - An optional `reason` can be given for the audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.delete_permissions/3` directly instead. """ - @spec delete_channel_permissions(integer, integer, AuditLogEntry.reason()) :: error | {:ok} - def delete_channel_permissions(channel_id, overwrite_id, reason \\ nil) do - Nostrum.Api.Channel.delete_permissions(channel_id, overwrite_id, reason) - end - - @doc ~S""" - Gets a list of invites for a channel. - - This endpoint requires the 'VIEW_CHANNEL' and 'MANAGE_CHANNELS' permissions. - - If successful, returns `{:ok, invite}`. Otherwise, returns a - `t:Nostrum.Api.error/0`. + defdelegate delete_channel_permissions(channel_id, overwrite_id, reason \\ nil), + to: Nostrum.Api.Channel, + as: :delete_permissions - ## Examples - - ```elixir - Nostrum.Api.get_channel_invites(43189401384091) - {:ok, [%Nostrum.Struct.Invite{} | _]} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Invite.channel_invites/1` directly instead. """ - @spec get_channel_invites(Channel.id()) :: error | {:ok, [Invite.detailed_invite()]} - def get_channel_invites(channel_id) when is_snowflake(channel_id) do - Nostrum.Api.Invite.channel_invites(channel_id) - end + defdelegate get_channel_invites(channel_id), + to: Nostrum.Api.Invite, + as: :channel_invites @doc ~S""" Same as `get_channel_invites/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_channel_invites!(Channel.id()) :: no_return | [Invite.detailed_invite()] def get_channel_invites!(channel_id) do get_channel_invites(channel_id) |> bangify end - @doc ~S""" - Creates an invite for a guild channel. - - An optional `reason` can be provided for the audit log. - - This endpoint requires the `CREATE_INSTANT_INVITE` permission. - - If successful, returns `{:ok, invite}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:max_age` (integer) - duration of invite in seconds before expiry, or 0 for never. - (default: `86400`) - * `:max_uses` (integer) - max number of uses or 0 for unlimited. - (default: `0`) - * `:temporary` (boolean) - Whether the invite should grant temporary - membership. (default: `false`) - * `:unique` (boolean) - used when creating unique one time use invites. - (default: `false`) - - ## Examples - - ```elixir - Nostrum.Api.create_channel_invite(41771983423143933) - {:ok, Nostrum.Struct.Invite{}} - - Nostrum.Api.create_channel_invite(41771983423143933, max_uses: 20) - {:ok, %Nostrum.Struct.Invite{}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Invite.create/3` directly instead. """ - @spec create_channel_invite(Channel.id(), options, AuditLogEntry.reason()) :: - error | {:ok, Invite.detailed_invite()} - def create_channel_invite(channel_id, options \\ [], reason \\ nil) do - Nostrum.Api.Invite.create(channel_id, options, reason) - end + defdelegate create_channel_invite(channel_id, options \\ [], reason \\ nil), + to: Nostrum.Api.Invite, + as: :create @doc ~S""" Same as `create_channel_invite/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_channel_invite!(Channel.id(), options, AuditLogEntry.reason()) :: no_return | Invite.detailed_invite() def create_channel_invite!(channel_id, options \\ [], reason \\ nil) do @@ -959,225 +592,143 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Triggers the typing indicator. - - Triggers the typing indicator in the channel specified by `channel_id`. - The typing indicator lasts for about 8 seconds and then automatically stops. - - Returns `{:ok}` if successful. `error` otherwise. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.start_typing/1` directly instead. """ - @spec start_typing(integer) :: error | {:ok} - def start_typing(channel_id) do - Nostrum.Api.Channel.start_typing(channel_id) - end + defdelegate start_typing(channel_id), + to: Nostrum.Api.Channel @doc """ Same as `start_typing/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec start_typing!(integer) :: no_return | {:ok} def start_typing!(channel_id) do start_typing(channel_id) |> bangify end - @doc ~S""" - Retrieves all pinned messages from a channel. - - This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions. - - If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_pinned_messages(43189401384091) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.pinned_messages/1` directly instead. """ - @spec get_pinned_messages(Channel.id()) :: error | {:ok, [Message.t()]} - def get_pinned_messages(channel_id) when is_snowflake(channel_id) do - Nostrum.Api.Channel.pinned_messages(channel_id) - end + defdelegate get_pinned_messages(channel_id), + to: Nostrum.Api.Channel, + as: :pinned_messages @doc ~S""" Same as `get_pinned_messages/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_pinned_messages!(Channel.id()) :: no_return | [Message.t()] def get_pinned_messages!(channel_id) do get_pinned_messages(channel_id) |> bangify end - @doc ~S""" - Pins a message in a channel. - - This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and - 'MANAGE_MESSAGES' permissions. It fires the - `t:Nostrum.Consumer.message_update/0` and - `t:Nostrum.Consumer.channel_pins_update/0` events. - - If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.add_pinned_channel_message(43189401384091, 18743893102394) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.pin_message/2` directly instead. """ - @spec add_pinned_channel_message(Channel.id(), Message.id()) :: error | {:ok} - def add_pinned_channel_message(channel_id, message_id) - when is_snowflake(channel_id) and is_snowflake(message_id) do - Nostrum.Api.Channel.pin_message(channel_id, message_id) - end + defdelegate add_pinned_channel_message(channel_id, message_id), + to: Nostrum.Api.Channel, + as: :pin_message @doc ~S""" Same as `add_pinned_channel_message/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec add_pinned_channel_message!(Channel.id(), Message.id()) :: no_return | {:ok} def add_pinned_channel_message!(channel_id, message_id) do add_pinned_channel_message(channel_id, message_id) |> bangify end - @doc """ - Unpins a message in a channel. - - This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and - 'MANAGE_MESSAGES' permissions. It fires the - `t:Nostrum.Consumer.message_update/0` and - `t:Nostrum.Consumer.channel_pins_update/0` events. - - Returns `{:ok}` if successful. `error` otherwise. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.unpin_message/2` directly instead. """ - @spec delete_pinned_channel_message(Channel.id(), Message.id()) :: error | {:ok} - def delete_pinned_channel_message(channel_id, message_id) - when is_snowflake(channel_id) and is_snowflake(message_id) do - Nostrum.Api.Channel.unpin_message(channel_id, message_id) - end + defdelegate delete_pinned_channel_message(channel_id, message_id), + to: Nostrum.Api.Channel, + as: :unpin_message @doc ~S""" Same as `delete_pinned_channel_message/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_pinned_channel_message!(Channel.id(), Message.id()) :: no_return | {:ok} def delete_pinned_channel_message!(channel_id, message_id) do delete_pinned_channel_message(channel_id, message_id) |> bangify end - @doc ~S""" - Gets a list of emojis for a given guild. - - This endpoint requires the `MANAGE_EMOJIS` permission. - - If successful, returns `{:ok, emojis}`. Otherwise, returns `t:Nostrum.Api.error/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.emojis/1` directly instead. """ - @spec list_guild_emojis(Guild.id()) :: error | {:ok, [Emoji.t()]} - def list_guild_emojis(guild_id) do - Nostrum.Api.Guild.emojis(guild_id) - end + defdelegate list_guild_emojis(guild_id), + to: Nostrum.Api.Guild, + as: :emojis @doc ~S""" Same as `list_guild_emojis/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec list_guild_emojis!(Guild.id()) :: no_return | [Emoji.t()] def list_guild_emojis!(guild_id) do list_guild_emojis(guild_id) |> bangify end - @doc ~S""" - Gets an emoji for the given guild and emoji ids. - - This endpoint requires the `MANAGE_EMOJIS` permission. - - If successful, returns `{:ok, emoji}`. Otherwise, returns `t:Nostrum.Api.error/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.emoji/2` directly instead. """ - @spec get_guild_emoji(Guild.id(), Emoji.id()) :: error | {:ok, Emoji.t()} - def get_guild_emoji(guild_id, emoji_id) do - Nostrum.Api.Guild.emoji(guild_id, emoji_id) - end + defdelegate get_guild_emoji(guild_id, emoji_id), + to: Nostrum.Api.Guild, + as: :emoji @doc ~S""" Same as `get_guild_emoji/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_guild_emoji!(Guild.id(), Emoji.id()) :: no_return | Emoji.t() def get_guild_emoji!(guild_id, emoji_id) do get_guild_emoji(guild_id, emoji_id) |> bangify end - @doc ~S""" - Creates a new emoji for the given guild. - - This endpoint requires the `MANAGE_EMOJIS` permission. It fires a - `t:Nostrum.Consumer.guild_emojis_update/0` event. - - An optional `reason` can be provided for the audit log. - - If successful, returns `{:ok, emoji}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - name of the emoji - * `:image` (base64 data URI) - the 128x128 emoji image. Maximum size of 256kb - * `:roles` (list of `t:Nostrum.Snowflake.t/0`) - roles for which this emoji will be whitelisted - (default: []) - - `:name` and `:image` are always required. - - ## Examples - - ```elixir - image = "data:image/png;base64,YXl5IGJieSB1IGx1a2luIDQgc3VtIGZ1az8=" - - Nostrum.Api.create_guild_emoji(43189401384091, name: "nostrum", image: image, roles: []) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.create_emoji/3` directly instead. """ - @spec create_guild_emoji(Guild.id(), options, AuditLogEntry.reason()) :: - error | {:ok, Emoji.t()} - def create_guild_emoji(guild_id, options, reason \\ nil) do - Nostrum.Api.Guild.create_emoji(guild_id, options, reason) - end + defdelegate create_guild_emoji(guild_id, options, reason \\ nil), + to: Nostrum.Api.Guild, + as: :create_emoji @doc ~S""" Same as `create_guild_emoji/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_guild_emoji!(Guild.id(), options, AuditLogEntry.reason()) :: no_return | Emoji.t() def create_guild_emoji!(guild_id, params, reason \\ nil) do create_guild_emoji(guild_id, params, reason) |> bangify end - @doc ~S""" - Modify the given emoji. - - This endpoint requires the `MANAGE_EMOJIS` permission. It fires a - `t:Nostrum.Consumer.guild_emojis_update/0` event. - - An optional `reason` can be provided for the audit log. - - If successful, returns `{:ok, emoji}`. Otherwise, returns `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - name of the emoji - * `:roles` (list of `t:Nostrum.Snowflake.t/0`) - roles to which this emoji will be whitelisted - - ## Examples - - ```elixir - Nostrum.Api.modify_guild_emoji(43189401384091, 4314301984301, name: "elixir", roles: []) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify_emoji/4` directly instead. """ - @spec modify_guild_emoji(Guild.id(), Emoji.id(), options, AuditLogEntry.reason()) :: - error | {:ok, Emoji.t()} - def modify_guild_emoji(guild_id, emoji_id, options \\ %{}, reason \\ nil) do - Nostrum.Api.Guild.modify_emoji(guild_id, emoji_id, options, reason) - end + defdelegate modify_guild_emoji(guild_id, emoji_id, options \\ %{}, reason \\ nil), + to: Nostrum.Api.Guild, + as: :modify_emoji @doc ~S""" Same as `modify_guild_emoji/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_guild_emoji!(Guild.id(), Emoji.id(), options, AuditLogEntry.reason()) :: no_return | Emoji.t() def modify_guild_emoji!(guild_id, emoji_id, options, reason \\ nil) do @@ -1185,359 +736,190 @@ defmodule Nostrum.Api do |> bangify end - @doc ~S""" - Deletes the given emoji. - - An optional `reason` can be provided for the audit log. - - This endpoint requires the `MANAGE_EMOJIS` permission. It fires a - `t:Nostrum.Consumer.guild_emojis_update/0` event. - - If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.delete_emoji/3` directly instead. """ - @spec delete_guild_emoji(Guild.id(), Emoji.id(), AuditLogEntry.reason()) :: error | {:ok} - def delete_guild_emoji(guild_id, emoji_id, reason \\ nil) do - Nostrum.Api.Guild.delete_emoji(guild_id, emoji_id, reason) - end + defdelegate delete_guild_emoji(guild_id, emoji_id, reason \\ nil), + to: Nostrum.Api.Guild, + as: :delete_emoji @doc ~S""" Same as `delete_guild_emoji/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_guild_emoji!(Guild.id(), Emoji.id(), AuditLogEntry.reason()) :: no_return | {:ok} def delete_guild_emoji!(guild_id, emoji_id, reason \\ nil) do delete_guild_emoji(guild_id, emoji_id, reason) |> bangify end - @doc ~S""" - Fetch a sticker with the provided ID. - - Returns a `t:Nostrum.Struct.Sticker.t/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Sticker.get/1` directly instead. """ - @doc since: "0.10.0" - @spec get_sticker(Snowflake.t()) :: {:ok, Sticker.t()} | error - def get_sticker(sticker_id) do - Nostrum.Api.Sticker.get(sticker_id) - end - - @doc ~S""" - List all stickers in the provided guild. + defdelegate get_sticker(sticker_id), + to: Nostrum.Api.Sticker, + as: :get - Returns a list of `t:Nostrum.Struct.Sticker.t/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Sticker.list/1` directly instead. """ - @doc since: "0.10.0" - @spec list_guild_stickers(Guild.id()) :: {:ok, [Sticker.t()]} | error - def list_guild_stickers(guild_id) do - Nostrum.Api.Sticker.list(guild_id) - end - - @doc ~S""" - Return the specified sticker from the specified guild. + defdelegate list_guild_stickers(guild_id), + to: Nostrum.Api.Sticker, + as: :list - Returns a `t:Nostrum.Struct.Sticker.t/0`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Sticker.get/2` directly instead. """ - @doc since: "0.10.0" - @spec get_guild_sticker(Guild.id(), Sticker.id()) :: Sticker.t() | error - def get_guild_sticker(guild_id, sticker_id) do - Nostrum.Api.Sticker.get(guild_id, sticker_id) - end - - @doc ~S""" - Create a sticker in a guild. - - Every guild has five free sticker slots by default, and each Boost level will - grant access to more slots. - - Uploaded stickers are constrained to 5 seconds in length for animated stickers, and 320 x 320 pixels. - - Stickers in the [Lottie file format](https://airbnb.design/lottie/) can only - be uploaded on guilds that have either the `VERIFIED` and/or the `PARTNERED` - guild feature. + defdelegate get_guild_sticker(guild_id, sticker_id), + to: Nostrum.Api.Sticker, + as: :get - ## Parameters - - - `name`: Name of the sticker (2-30 characters) - - `description`: Description of the sticker (2-100 characters) - - `tags`: Autocomplete/suggestion tags for the sticker (max 200 characters) - - `file`: A path to a file to upload or a map of `name` (file name) and `body` (file data). - - `reason` (optional): audit log reason to attach to this event - - ## Returns - - Returns a `t:Nostrum.Struct.Sticker.t/0` on success. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Sticker.create/6` directly instead. """ - @doc since: "0.10.0" - @spec create_guild_sticker( - Guild.id(), - Sticker.name(), - Sticker.description(), - Sticker.tags(), - String.t() | %{body: iodata(), name: String.t()}, - String.t() | nil - ) :: {:ok, Sticker.t()} | error - def create_guild_sticker(guild_id, name, description, tags, file, reason \\ nil) do - Nostrum.Api.Sticker.create(guild_id, name, description, tags, file, reason) - end + defdelegate create_guild_sticker(guild_id, name, description, tags, file, reason \\ nil), + to: Nostrum.Api.Sticker, + as: :create - @doc ~S""" - Modify a guild sticker with the specified ID. - - Pass in a map of properties to update, with any of the following keys: - - - `name`: Name of the sticker (2-30 characters) - - `description`: Description of the sticker (2-100 characters) - - `tags`: Autocomplete/suggestion tags for the sticker (max 200 characters) - - Returns an updated sticker on update completion. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Sticker.modify/3` directly instead. """ - @doc since: "0.10.0" - @spec modify_guild_sticker(Guild.id(), Sticker.id(), %{ - name: Sticker.name() | nil, - description: Sticker.description() | nil, - tags: Sticker.tags() | nil - }) :: {:ok, Sticker.t()} | error - def modify_guild_sticker(guild_id, sticker_id, options) do - Nostrum.Api.Sticker.modify(guild_id, sticker_id, options) - end + defdelegate modify_guild_sticker(guild_id, sticker_id, options), + to: Nostrum.Api.Sticker, + as: :modify - @doc ~S""" - Delete a guild sticker with the specified ID. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Sticker.delete/2` directly instead. """ - @doc since: "0.10.0" - @spec delete_guild_sticker(Guild.id(), Sticker.id()) :: {:ok} | error - def delete_guild_sticker(guild_id, sticker_id) do - Nostrum.Api.Sticker.delete(guild_id, sticker_id) - end + defdelegate delete_guild_sticker(guild_id, sticker_id), + to: Nostrum.Api.Sticker, + as: :delete - @doc ~S""" - Get a list of available sticker packs. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Sticker.packs/0` directly instead. """ - @doc since: "0.10.0" - @spec get_sticker_packs() :: {:ok, [Sticker.Pack.t()]} | error - def get_sticker_packs do - Nostrum.Api.Sticker.packs() - end - - @doc ~S""" - Get the `t:Nostrum.Struct.Guild.AuditLog.t/0` for the given `guild_id`. - - ## Options + defdelegate get_sticker_packs, + to: Nostrum.Api.Sticker, + as: :packs - * `:user_id` (`t:Nostrum.Struct.User.id/0`) - filter the log for a user ID - * `:action_type` (`t:integer/0`) - filter the log by audit log type, see [Audit Log Events](https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events) - * `:before` (`t:Nostrum.Struct.Snowflake.t/0`) - filter the log before a certain entry ID - * `:limit` (`t:pos_integer/0`) - how many entries are returned (default 50, minimum 1, maximum 100) + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.audit_log/2` directly instead. """ - @spec get_guild_audit_log(Guild.id(), options) :: {:ok, AuditLog.t()} | error - def get_guild_audit_log(guild_id, options \\ []) do - Nostrum.Api.Guild.audit_log(guild_id, options) - end - - @doc ~S""" - Gets a guild. + defdelegate get_guild_audit_log(guild_id, options \\ []), + to: Nostrum.Api.Guild, + as: :audit_log - If successful, returns `{:ok, guild}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_guild(81384788765712384) - {:ok, %Nostrum.Struct.Guild{id: 81384788765712384}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.get/1` directly instead. """ - @spec get_guild(Guild.id()) :: error | {:ok, Guild.rest_guild()} - def get_guild(guild_id) when is_snowflake(guild_id) do - Nostrum.Api.Guild.get(guild_id) - end + defdelegate get_guild(guild_id), + to: Nostrum.Api.Guild, + as: :get @doc """ Same as `get_guild/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_guild!(Guild.id()) :: no_return | Guild.rest_guild() def get_guild!(guild_id) do get_guild(guild_id) |> bangify end - @doc """ - Modifies a guild's settings. - - This endpoint requires the `MANAGE_GUILD` permission. It fires the - `t:Nostrum.Consumer.guild_update/0` event. - - An optional `reason` can be provided for the audit log. - - If successful, returns `{:ok, guild}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - guild name - * `:region` (string) - guild voice region id - * `:verification_level` (integer) - verification level - * `:default_message_notifications` (integer) - default message - notification level - * `:explicit_content_filter` (integer) - explicit content filter level - * `:afk_channel_id` (`t:Nostrum.Snowflake.t/0`) - id for afk channel - * `:afk_timeout` (integer) - afk timeout in seconds - * `:icon` (base64 data URI) - 128x128 jpeg image for the guild icon - * `:owner_id` (`t:Nostrum.Snowflake.t/0`) - user id to transfer - guild ownership to (must be owner) - * `:splash` (base64 data URI) - 128x128 jpeg image for the guild splash - (VIP only) - * `:system_channel_id` (`t:Nostrum.Snowflake.t/0`) - the id of the - channel to which system messages are sent - * `:rules_channel_id` (`t:Nostrum.Snowflake.t/0`) - the id of the channel that - is used for rules in public guilds - * `:public_updates_channel_id` (`t:Nostrum.Snowflake.t/0`) - the id of the channel - where admins and moderators receive notices from Discord in public guilds - - ## Examples - - ```elixir - Nostrum.Api.modify_guild(451824027976073216, name: "Nose Drum") - {:ok, %Nostrum.Struct.Guild{id: 451824027976073216, name: "Nose Drum", ...}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify/3` directly instead. """ - @spec modify_guild(Guild.id(), options, AuditLogEntry.reason()) :: - error | {:ok, Guild.rest_guild()} - def modify_guild(guild_id, options \\ [], reason \\ nil) do - Nostrum.Api.Guild.modify(guild_id, options, reason) - end + defdelegate modify_guild(guild_id, options \\ [], reason \\ nil), + to: Nostrum.Api.Guild, + as: :modify @doc """ Same as `modify_guild/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_guild!(Guild.id(), options) :: no_return | Guild.rest_guild() def modify_guild!(guild_id, options \\ []) do modify_guild(guild_id, options) |> bangify end - @doc ~S""" - Deletes a guild. - - This endpoint requires that the current user is the owner of the guild. - It fires the `t:Nostrum.Consumer.guild_delete/0` event. - - If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.delete_guild(81384788765712384) - {:ok} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.delete/1` directly instead. """ - @spec delete_guild(Guild.id()) :: error | {:ok} - def delete_guild(guild_id) when is_snowflake(guild_id) do - Nostrum.Api.Guild.delete(guild_id) - end + defdelegate delete_guild(guild_id), + to: Nostrum.Api.Guild, + as: :delete @doc ~S""" Same as `delete_guild/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_guild!(Guild.id()) :: no_return | {:ok} def delete_guild!(guild_id) do delete_guild(guild_id) |> bangify end - @doc ~S""" - Gets a list of guild channels. - - If successful, returns `{:ok, channels}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_guild_channels(81384788765712384) - {:ok, [%Nostrum.Struct.Channel{guild_id: 81384788765712384} | _]} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.channels/1` directly instead. """ - @spec get_guild_channels(Guild.id()) :: error | {:ok, [Channel.guild_channel()]} - def get_guild_channels(guild_id) when is_snowflake(guild_id) do - Nostrum.Api.Guild.channels(guild_id) - end + defdelegate get_guild_channels(guild_id), + to: Nostrum.Api.Guild, + as: :channels @doc ~S""" Same as `get_guild_channels/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_guild_channels!(Guild.id()) :: no_return | [Channel.guild_channel()] def get_guild_channels!(guild_id) do get_guild_channels(guild_id) |> bangify end - @doc """ - Creates a channel for a guild. - - This endpoint requires the `MANAGE_CHANNELS` permission. It fires a - `t:Nostrum.Consumer.channel_create/0` event. - - If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - channel name (2-100 characters) - * `:type` (integer) - the type of channel (See `Nostrum.Struct.Channel`) - * `:topic` (string) - channel topic (0-1024 characters) - * `:bitrate` (integer) - the bitrate (in bits) of the voice channel (voice only) - * `:user_limit` (integer) - the user limit of the voice channel (voice only) - * `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) - - the channel's permission overwrites - * `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) - id of the parent category for a channel - * `:nsfw` (boolean) - if the channel is nsfw - - `:name` is always required. - - ## Examples - - ```elixir - Nostrum.Api.create_guild_channel(81384788765712384, name: "elixir-nostrum", topic: "craig's domain") - {:ok, %Nostrum.Struct.Channel{guild_id: 81384788765712384}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.create/2` directly instead. """ - @spec create_guild_channel(Guild.id(), options) :: error | {:ok, Channel.guild_channel()} - def create_guild_channel(guild_id, options) do - Nostrum.Api.Channel.create(guild_id, options) - end + defdelegate create_guild_channel(guild_id, options), + to: Nostrum.Api.Channel, + as: :create @doc ~S""" Same as `create_guild_channel/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_guild_channel!(Guild.id(), options) :: no_return | Channel.guild_channel() def create_guild_channel!(guild_id, options) do create_guild_channel(guild_id, options) |> bangify end - @doc """ - Reorders a guild's channels. - - This endpoint requires the `MANAGE_CHANNELS` permission. It fires multiple - `t:Nostrum.Consumer.channel_update/0` events. - - If successful, returns `{:ok, channels}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - `positions` is a list of maps that each map a channel id with a position. - - ## Examples - - ```elixir - Nostrum.Api.modify_guild_channel_positions(279093381723062272, [%{id: 351500354581692420, position: 2}]) - {:ok} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify_channel_positions/2` directly instead. """ - @spec modify_guild_channel_positions(Guild.id(), [%{id: integer, position: integer}]) :: - error | {:ok} - def modify_guild_channel_positions(guild_id, positions) - when is_snowflake(guild_id) and is_list(positions) do - Nostrum.Api.Guild.modify_channel_positions(guild_id, positions) - end + defdelegate modify_guild_channel_positions(guild_id, positions), + to: Nostrum.Api.Guild, + as: :modify_channel_positions @doc ~S""" Same as `modify_guild_channel_positions/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_guild_channel_positions!(Guild.id(), [%{id: integer, position: integer}]) :: no_return | {:ok} def modify_guild_channel_positions!(guild_id, positions) do @@ -1545,144 +927,72 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Gets a guild member. - - If successful, returns `{:ok, member}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_guild_member(4019283754613, 184937267485) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.member/2` directly instead. """ - @spec get_guild_member(Guild.id(), User.id()) :: error | {:ok, Member.t()} - def get_guild_member(guild_id, user_id) when is_snowflake(guild_id) and is_snowflake(user_id) do - Nostrum.Api.Guild.member(guild_id, user_id) - end + defdelegate get_guild_member(guild_id, user_id), + to: Nostrum.Api.Guild, + as: :member @doc """ Same as `get_guild_member/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_guild_member!(Guild.id(), User.id()) :: no_return | Member.t() def get_guild_member!(guild_id, user_id) do get_guild_member(guild_id, user_id) |> bangify end - @doc """ - Gets a list of a guild's members. - - If successful, returns `{:ok, members}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:limit` (integer) - max number of members to return (1-1000) (default: 1) - * `:after` (`t:Nostrum.Struct.User.id/0`) - the highest user id in the previous page (default: 0) - - ## Examples - - ```elixir - Nostrum.Api.list_guild_members(41771983423143937, limit: 1) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.members/2` directly instead. """ - @spec list_guild_members(Guild.id(), options) :: error | {:ok, [Member.t()]} - def list_guild_members(guild_id, options \\ %{}) do - Nostrum.Api.Guild.members(guild_id, options) - end + defdelegate list_guild_members(guild_id, options \\ %{}), + to: Nostrum.Api.Guild, + as: :members @doc """ Same as `list_guild_members/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec list_guild_members!(Guild.id(), options) :: no_return | [Member.t()] def list_guild_members!(guild_id, options \\ %{}) do list_guild_members(guild_id, options) |> bangify end - @doc ~S""" - Puts a user in a guild. - - This endpoint fires the `t:Nostrum.Consumer.guild_member_add/0` event. - It requires the `CREATE_INSTANT_INVITE` permission. Additionally, it - situationally requires the `MANAGE_NICKNAMES`, `MANAGE_ROLES`, - `MUTE_MEMBERS`, and `DEAFEN_MEMBERS` permissions. - - If successful, returns `{:ok, member}` or `{:ok}` if the user was already a member of the - guild. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:access_token` (string) - the user's oauth2 access token - * `:nick` (string) - value to set users nickname to - * `:roles` (list of `t:Nostrum.Struct.Guild.Role.id/0`) - array of role ids the member is assigned - * `:mute` (boolean) - if the user is muted - * `:deaf` (boolean) - if the user is deafened - - `:access_token` is always required. - - ## Examples - - ```elixir - Nostrum.Api.add_guild_member( - 41771983423143937, - 18374719829378473, - access_token: "6qrZcUqja7812RVdnEKjpzOL4CvHBFG", - nick: "nostrum", - roles: [431849301, 913809431] - ) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.add_member/3` directly instead. """ - @spec add_guild_member(Guild.id(), User.id(), options) :: error | {:ok, Member.t()} | {:ok} - def add_guild_member(guild_id, user_id, options) do - Nostrum.Api.Guild.add_member(guild_id, user_id, options) - end + defdelegate add_guild_member(guild_id, user_id, options), + to: Nostrum.Api.Guild, + as: :add_member @doc """ Same as `add_guild_member/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec add_guild_member!(Guild.id(), User.id(), options) :: no_return | Member.t() | {:ok} def add_guild_member!(guild_id, user_id, options) do add_guild_member(guild_id, user_id, options) |> bangify end - @doc ~S""" - Modifies a guild member's attributes. - - This endpoint fires the `t:Nostrum.Consumer.guild_member_update/0` event. - It situationally requires the `MANAGE_NICKNAMES`, `MANAGE_ROLES`, - `MUTE_MEMBERS`, `DEAFEN_MEMBERS`, and `MOVE_MEMBERS` permissions. - - If successful, returns `{:ok, member}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - An optional `reason` argument can be given for the audit log. - - ## Options - - * `:nick` (string) - value to set users nickname to - * `:roles` (list of `t:Nostrum.Snowflake.t/0`) - array of role ids the member is assigned - * `:mute` (boolean) - if the user is muted - * `:deaf` (boolean) - if the user is deafened - * `:channel_id` (`t:Nostrum.Snowflake.t/0`) - id of channel to move user to (if they are connected to voice) - * `:communication_disabled_until` (`t:DateTime.t/0` or `nil`) - datetime to disable user communication (timeout) until, or `nil` to remove timeout. - - ## Examples - - ```elixir - Nostrum.Api.modify_guild_member(41771983423143937, 637162356451, nick: "Nostrum") - {:ok, %Nostrum.Struct.Member{}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify_member/4` directly instead. """ - @spec modify_guild_member(Guild.id(), User.id(), options, AuditLogEntry.reason()) :: - error | {:ok, Member.t()} - def modify_guild_member(guild_id, user_id, options \\ %{}, reason \\ nil) do - Nostrum.Api.Guild.modify_member(guild_id, user_id, options, reason) - end + defdelegate modify_guild_member(guild_id, user_id, options \\ %{}, reason \\ nil), + to: Nostrum.Api.Guild, + as: :modify_member @doc """ Same as `modify_guild_member/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_guild_member!(Guild.id(), User.id(), options, AuditLogEntry.reason()) :: error | {:ok} def modify_guild_member!(guild_id, user_id, options \\ %{}, reason \\ nil) do @@ -1690,228 +1000,138 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Modifies the nickname of the current user in a guild. - - If successful, returns `{:ok, %{nick: nick}}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:nick` (string) - value to set users nickname to - - ## Examples - - ```elixir - Nostrum.Api.modify_current_user_nick(41771983423143937, nick: "Nostrum") - {:ok, %{nick: "Nostrum"}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify_self_nick/2` directly instead. """ - @spec modify_current_user_nick(Guild.id(), options) :: error | {:ok, %{nick: String.t()}} - def modify_current_user_nick(guild_id, options \\ %{}) do - Nostrum.Api.Guild.modify_self_nick(guild_id, options) - end + defdelegate modify_current_user_nick(guild_id, options \\ %{}), + to: Nostrum.Api.Guild, + as: :modify_self_nick @doc """ Same as `modify_current_user_nick/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_current_user_nick!(Guild.id(), options) :: no_return | %{nick: String.t()} def modify_current_user_nick!(guild_id, options \\ %{}) do modify_current_user_nick(guild_id, options) |> bangify() end - @doc """ - Adds a role to a member. - - Role to add is specified by `role_id`. - User to add role to is specified by `guild_id` and `user_id`. - An optional `reason` can be given for the audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Role.add_member/4` directly instead. """ - @spec add_guild_member_role(integer, integer, integer, AuditLogEntry.reason()) :: error | {:ok} - def add_guild_member_role(guild_id, user_id, role_id, reason \\ nil) do - Nostrum.Api.Role.add_member(guild_id, user_id, role_id, reason) - end - - @doc """ - Removes a role from a member. + defdelegate add_guild_member_role(guild_id, user_id, role_id, reason \\ nil), + to: Nostrum.Api.Role, + as: :add_member - Role to remove is specified by `role_id`. - User to remove role from is specified by `guild_id` and `user_id`. - An optional `reason` can be given for the audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Role.remove_member/4` directly instead. """ - @spec remove_guild_member_role(integer, integer, integer, AuditLogEntry.reason()) :: - error | {:ok} - def remove_guild_member_role(guild_id, user_id, role_id, reason \\ nil) do - Nostrum.Api.Role.remove_member(guild_id, user_id, role_id, reason) - end - - @doc """ - Removes a member from a guild. - - This event requires the `KICK_MEMBERS` permission. It fires a - `t:Nostrum.Consumer.guild_member_remove/0` event. - - An optional reason can be provided for the audit log with `reason`. - - If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples + defdelegate remove_guild_member_role(guild_id, user_id, role_id, reason \\ nil), + to: Nostrum.Api.Role, + as: :remove_member - ```elixir - Nostrum.Api.remove_guild_member(1453827904102291, 18739485766253) - {:ok} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.kick_member/3` directly instead. """ - @spec remove_guild_member(Guild.id(), User.id(), AuditLogEntry.reason()) :: error | {:ok} - def remove_guild_member(guild_id, user_id, reason \\ nil) - when is_snowflake(guild_id) and is_snowflake(user_id) do - Nostrum.Api.Guild.kick_member(guild_id, user_id, reason) - end + defdelegate remove_guild_member(guild_id, user_id, reason \\ nil), + to: Nostrum.Api.Guild, + as: :kick_member @doc """ Same as `remove_guild_member/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec remove_guild_member!(Guild.id(), User.id(), AuditLogEntry.reason()) :: no_return | {:ok} def remove_guild_member!(guild_id, user_id, reason \\ nil) do remove_guild_member(guild_id, user_id, reason) |> bangify end - @doc """ - Gets a ban object for the given user from a guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.ban/2` directly instead. """ - @doc since: "0.5.0" - @spec get_guild_ban(integer, integer) :: error | {:ok, Guild.Ban.t()} - def get_guild_ban(guild_id, user_id) do - Nostrum.Api.Guild.ban(guild_id, user_id) - end - - @doc """ - Gets a list of users banned from a guild. + defdelegate get_guild_ban(guild_id, user_id), + to: Nostrum.Api.Guild, + as: :ban - Guild to get bans for is specified by `guild_id`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.bans/1` directly instead. """ - @spec get_guild_bans(integer) :: error | {:ok, [Nostrum.Struct.User.t()]} - def get_guild_bans(guild_id) do - Nostrum.Api.Guild.bans(guild_id) - end - - @doc """ - Bans a user from a guild. + defdelegate get_guild_bans(guild_id), + to: Nostrum.Api.Guild, + as: :bans - User to delete is specified by `guild_id` and `user_id`. - An optional `reason` can be specified for the audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.ban_member/4` directly instead. """ - @spec create_guild_ban(integer, integer, integer, AuditLogEntry.reason()) :: error | {:ok} - def create_guild_ban(guild_id, user_id, days_to_delete, reason \\ nil) do - Nostrum.Api.Guild.ban_member(guild_id, user_id, days_to_delete, reason) - end + defdelegate create_guild_ban(guild_id, user_id, days_to_delete, reason \\ nil), + to: Nostrum.Api.Guild, + as: :ban_member - @doc """ - Removes a ban for a user. - - User to unban is specified by `guild_id` and `user_id`. - An optional `reason` can be specified for the audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.unban_member/3` directly instead. """ - @spec remove_guild_ban(integer, integer, AuditLogEntry.reason()) :: error | {:ok} - def remove_guild_ban(guild_id, user_id, reason \\ nil) do - Nostrum.Api.Guild.unban_member(guild_id, user_id, reason) - end - - @doc ~S""" - Gets a guild's roles. - - If successful, returns `{:ok, roles}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + defdelegate remove_guild_ban(guild_id, user_id, reason \\ nil), + to: Nostrum.Api.Guild, + as: :unban_member - ## Examples - - ```elixir - Nostrum.Api.get_guild_roles(147362948571673) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.roles/1` directly instead. """ - @spec get_guild_roles(Guild.id()) :: error | {:ok, [Role.t()]} - def get_guild_roles(guild_id) when is_snowflake(guild_id) do - Nostrum.Api.Guild.roles(guild_id) - end + defdelegate get_guild_roles(guild_id), + to: Nostrum.Api.Guild, + as: :roles @doc ~S""" Same as `get_guild_roles/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_guild_roles!(Guild.id()) :: no_return | [Role.t()] def get_guild_roles!(guild_id) do get_guild_roles(guild_id) |> bangify end - @doc ~S""" - Creates a guild role. - - An optional reason for the audit log can be provided via `reason`. - - This endpoint requires the `MANAGE_ROLES` permission. It fires a - `t:Nostrum.Consumer.guild_role_create/0` event. - - If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - name of the role (default: "new role") - * `:permissions` (integer) - bitwise of the enabled/disabled permissions (default: @everyone perms) - * `:color` (integer) - RGB color value (default: 0) - * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar (default: false) - * `:mentionable` (boolean) - whether the role should be mentionable (default: false) - * `:icon` (string) - URL role icon (default: `nil`) - * `:unicode_emoji` (string) - standard unicode character emoji role icon (default: `nil`) - - ## Examples - - ```elixir - Nostrum.Api.create_guild_role(41771983423143937, name: "nostrum-club", hoist: true) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Role.create/3` directly instead. """ - @spec create_guild_role(Guild.id(), options, AuditLogEntry.reason()) :: error | {:ok, Role.t()} - def create_guild_role(guild_id, options, reason \\ nil) do - Nostrum.Api.Role.create(guild_id, options, reason) - end + defdelegate create_guild_role(guild_id, options, reason \\ nil), + to: Nostrum.Api.Role, + as: :create @doc ~S""" Same as `create_guild_role/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_guild_role!(Guild.id(), options, AuditLogEntry.reason()) :: no_return | Role.t() def create_guild_role!(guild_id, options, reason \\ nil) do create_guild_role(guild_id, options, reason) |> bangify end - @doc ~S""" - Reorders a guild's roles. - - This endpoint requires the `MANAGE_ROLES` permission. It fires multiple - `t:Nostrum.Consumer.guild_role_update/0` events. - - If successful, returns `{:ok, roles}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - `positions` is a list of maps that each map a role id with a position. - - ## Examples - - ```elixir - Nostrum.Api.modify_guild_role_positions(41771983423143937, [%{id: 41771983423143936, position: 2}]) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify_role_positions/3` directly instead. """ - @spec modify_guild_role_positions( - Guild.id(), - [%{id: Role.id(), position: integer}], - AuditLogEntry.reason() - ) :: error | {:ok, [Role.t()]} - def modify_guild_role_positions(guild_id, positions, reason \\ nil) - when is_snowflake(guild_id) and is_list(positions) do - Nostrum.Api.Guild.modify_role_positions(guild_id, positions, reason) - end + defdelegate modify_guild_role_positions(guild_id, positions, reason \\ nil), + to: Nostrum.Api.Guild, + as: :modify_role_positions @doc ~S""" Same as `modify_guild_role_positions/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_guild_role_positions!( Guild.id(), [%{id: Role.id(), position: integer}], @@ -1922,39 +1142,18 @@ defmodule Nostrum.Api do |> bangify end - @doc ~S""" - Modifies a guild role. - - This endpoint requires the `MANAGE_ROLES` permission. It fires a - `t:Nostrum.Consumer.guild_role_update/0` event. - - An optional `reason` can be specified for the audit log. - - If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - name of the role - * `:permissions` (integer) - bitwise of the enabled/disabled permissions - * `:color` (integer) - RGB color value (default: 0) - * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar - * `:mentionable` (boolean) - whether the role should be mentionable - - ## Examples - - ```elixir - Nostrum.Api.modify_guild_role(41771983423143937, 392817238471936, hoist: false, name: "foo-bar") - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Role.modify/4` directly instead. """ - @spec modify_guild_role(Guild.id(), Role.id(), options, AuditLogEntry.reason()) :: - error | {:ok, Role.t()} - def modify_guild_role(guild_id, role_id, options, reason \\ nil) do - Nostrum.Api.Role.modify(guild_id, role_id, options, reason) - end + defdelegate modify_guild_role(guild_id, role_id, options, reason \\ nil), + to: Nostrum.Api.Role, + as: :modify @doc ~S""" Same as `modify_guild_role/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_guild_role!(Guild.id(), Role.id(), options, AuditLogEntry.reason()) :: no_return | Role.t() def modify_guild_role!(guild_id, role_id, options, reason \\ nil) do @@ -1962,92 +1161,54 @@ defmodule Nostrum.Api do |> bangify end - @doc ~S""" - Deletes a role from a guild. - - An optional `reason` can be specified for the audit log. - - This endpoint requires the `MANAGE_ROLES` permission. It fires a - `t:Nostrum.Consumer.guild_role_delete/0` event. - - If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.delete_guild_role(41771983423143937, 392817238471936) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Role.delete/3` directly instead. """ - @spec delete_guild_role(Guild.id(), Role.id(), AuditLogEntry.reason()) :: error | {:ok} - def delete_guild_role(guild_id, role_id, reason \\ nil) - when is_snowflake(guild_id) and is_snowflake(role_id) do - Nostrum.Api.Role.delete(guild_id, role_id, reason) - end + defdelegate delete_guild_role(guild_id, role_id, reason \\ nil), + to: Nostrum.Api.Role, + as: :delete @doc ~S""" Same as `delete_guild_role/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_guild_role!(Guild.id(), Role.id(), AuditLogEntry.reason()) :: no_return | {:ok} def delete_guild_role!(guild_id, role_id, reason \\ nil) do delete_guild_role(guild_id, role_id, reason) |> bangify end - @doc """ - Gets the number of members that would be removed in a prune given `days`. - - This endpoint requires the `KICK_MEMBERS` permission. - - If successful, returns `{:ok, %{pruned: pruned}}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.get_guild_prune_count(81384788765712384, 1) - {:ok, %{pruned: 0}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.estimate_prune_count/2` directly instead. """ - @spec get_guild_prune_count(Guild.id(), 1..30) :: error | {:ok, %{pruned: integer}} - def get_guild_prune_count(guild_id, days) when is_snowflake(guild_id) and days in 1..30 do - Nostrum.Api.Guild.estimate_prune_count(guild_id, days) - end + defdelegate get_guild_prune_count(guild_id, days), + to: Nostrum.Api.Guild, + as: :estimate_prune_count @doc ~S""" Same as `get_guild_prune_count/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_guild_prune_count!(Guild.id(), 1..30) :: no_return | %{pruned: integer} def get_guild_prune_count!(guild_id, days) do get_guild_prune_count(guild_id, days) |> bangify end - @doc """ - Begins a guild prune to prune members within `days`. - - An optional `reason` can be provided for the guild audit log. - - This endpoint requires the `KICK_MEMBERS` permission. It fires multiple - `t:Nostrum.Consumer.guild_member_remove/0` events. - - If successful, returns `{:ok, %{pruned: pruned}}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.begin_guild_prune(81384788765712384, 1) - {:ok, %{pruned: 0}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.begin_prune/3` directly instead. """ - @spec begin_guild_prune(Guild.id(), 1..30, AuditLogEntry.reason()) :: - error | {:ok, %{pruned: integer}} - def begin_guild_prune(guild_id, days, reason \\ nil) - when is_snowflake(guild_id) and days in 1..30 do - Nostrum.Api.Guild.begin_prune(guild_id, days, reason) - end + defdelegate begin_guild_prune(guild_id, days, reason \\ nil), + to: Nostrum.Api.Guild, + as: :begin_prune @doc ~S""" Same as `begin_guild_prune/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec begin_guild_prune!(Guild.id(), 1..30, AuditLogEntry.reason()) :: no_return | %{pruned: integer} def begin_guild_prune!(guild_id, days, reason) do @@ -2055,483 +1216,298 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Gets a list of voice regions for the guild. - - Guild to get voice regions for is specified by `guild_id`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.voice_region/1` directly instead. """ - @spec get_voice_region(integer) :: error | {:ok, [Nostrum.Struct.VoiceRegion.t()]} - def get_voice_region(guild_id) do - Nostrum.Api.Guild.voice_region(guild_id) - end - - @doc ~S""" - Gets a list of invites for a guild. - - This endpoint requires the `MANAGE_GUILD` permission. - - If successful, returns `{:ok, invites}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples + defdelegate get_voice_region(guild_id), + to: Nostrum.Api.Guild, + as: :voice_region - ```elixir - Nostrum.Api.get_guild_invites(81384788765712384) - {:ok, [%Nostrum.Struct.Invite{} | _]} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Invite.guild_invites/1` directly instead. """ - @spec get_guild_invites(Guild.id()) :: error | {:ok, [Invite.detailed_invite()]} - def get_guild_invites(guild_id) when is_snowflake(guild_id) do - Nostrum.Api.Invite.guild_invites(guild_id) - end + defdelegate get_guild_invites(guild_id), + to: Nostrum.Api.Invite, + as: :guild_invites @doc ~S""" Same as `get_guild_invites/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_guild_invites!(Guild.id()) :: no_return | [Invite.detailed_invite()] def get_guild_invites!(guild_id) do get_guild_invites(guild_id) |> bangify end - @doc """ - Gets a list of guild integerations. - - Guild to get integrations for is specified by `guild_id`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.integrations/1` directly instead. """ - @spec get_guild_integrations(Guild.id()) :: - error | {:ok, [Nostrum.Struct.Guild.Integration.t()]} - def get_guild_integrations(guild_id) do - Nostrum.Api.Guild.integrations(guild_id) - end - - @doc """ - Creates a new guild integeration. + defdelegate get_guild_integrations(guild_id), + to: Nostrum.Api.Guild, + as: :integrations - Guild to create integration with is specified by `guild_id`. - - `options` is a map with the following requires keys: - * `type` - Integration type. - * `id` - Integeration id. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.create_integration/2` directly instead. """ - @spec create_guild_integrations(integer, %{ - type: String.t(), - id: integer - }) :: error | {:ok} - def create_guild_integrations(guild_id, options) do - Nostrum.Api.Guild.create_integration(guild_id, options) - end - - @doc """ - Changes the settings and behaviours for a guild integeration. + defdelegate create_guild_integrations(guild_id, options), + to: Nostrum.Api.Guild, + as: :create_integration - Integration to modify is specified by `guild_id` and `integeration_id`. - - `options` is a map with the following keys: - * `expire_behavior` - Expiry behavior. - * `expire_grace_period` - Period where the integration will ignore elapsed subs. - * `enable_emoticons` - Whether emoticons should be synced. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify_integration/3` directly instead. """ - @spec modify_guild_integrations(integer, integer, %{ - expire_behaviour: integer, - expire_grace_period: integer, - enable_emoticons: boolean - }) :: error | {:ok} - def modify_guild_integrations(guild_id, integration_id, options) do - Nostrum.Api.Guild.modify_integration(guild_id, integration_id, options) - end - - @doc """ - Deletes a guild integeration. + defdelegate modify_guild_integrations(guild_id, integration_id, options), + to: Nostrum.Api.Guild, + as: :modify_integration - Integration to delete is specified by `guild_id` and `integeration_id`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.delete_integration/2` directly instead. """ - @spec delete_guild_integrations(integer, integer) :: error | {:ok} - def delete_guild_integrations(guild_id, integration_id) do - Nostrum.Api.Guild.delete_integration(guild_id, integration_id) - end - - @doc """ - Syncs a guild integration. + defdelegate delete_guild_integrations(guild_id, integration_id), + to: Nostrum.Api.Guild, + as: :delete_integration - Integration to sync is specified by `guild_id` and `integeration_id`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.sync_integration/2` directly instead. """ - @spec sync_guild_integrations(integer, integer) :: error | {:ok} - def sync_guild_integrations(guild_id, integration_id) do - Nostrum.Api.Guild.sync_integration(guild_id, integration_id) - end + defdelegate sync_guild_integrations(guild_id, integration_id), + to: Nostrum.Api.Guild, + as: :sync_integration - @doc """ - Gets a guild embed. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.widget/1` directly instead. """ - @spec get_guild_widget(integer) :: error | {:ok, map} - def get_guild_widget(guild_id) do - Nostrum.Api.Guild.widget(guild_id) - end + defdelegate get_guild_widget(guild_id), + to: Nostrum.Api.Guild, + as: :widget - @doc """ - Modifies a guild embed. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.modify_widget/2` directly instead. """ - @spec modify_guild_widget(integer, map) :: error | {:ok, map} - def modify_guild_widget(guild_id, options) do - Nostrum.Api.Guild.modify_widget(guild_id, options) - end - - @doc """ - Creates a new scheduled event for the guild. - - ## Options - * `:channel_id` - (`t:Nostrum.Snowflake.t/0`) optional channel id for the event - * `:entity_metadata` - (`t:Nostrum.Struct.Guild.ScheduledEvent.EntityMetadata.t/0`) metadata for the event - * `:name` - (string) required name for the event - * `:privacy_level` - (integer) at the time of writing, this must always be 2 for `GUILD_ONLY` - * `:scheduled_start_time` - required time for the event to start as a `DateTime` or (ISO8601 timestamp)[`DateTime.to_iso8601/3`] - * `:scheduled_end_time` - optional time for the event to end as a `DateTime` or (ISO8601 timestamp)[`DateTime.to_iso8601/3`] - * `:description` - (string) optional description for the event - * `:entity_type` - (integer) an integer representing the type of entity the event is for - * `1` - `STAGE_INSTANCE` - * `2` - `VOICE` - * `3` - `EXTERNAL` + defdelegate modify_guild_widget(guild_id, options), + to: Nostrum.Api.Guild, + as: :modify_widget - See the (official documentation)[https://discord.com/developers/docs/resources/guild-scheduled-event] for more information. - - - An optional `reason` can be specified for the audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ScheduledEvent.create/3` directly instead. """ - @doc since: "0.5.0" - @spec create_guild_scheduled_event(Guild.id(), reason :: AuditLogEntry.reason(), options) :: - {:ok, ScheduledEvent.t()} | error - def create_guild_scheduled_event(guild_id, reason \\ nil, options) do - Nostrum.Api.ScheduledEvent.create(guild_id, reason, options) - end + defdelegate create_guild_scheduled_event(guild_id, reason \\ nil, options), + to: Nostrum.Api.ScheduledEvent, + as: :create - @doc """ - Get a list of scheduled events for a guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.scheduled_events/1` directly instead. """ - @doc since: "0.5.0" - @spec get_guild_scheduled_events(Guild.id()) :: error | {:ok, [ScheduledEvent.t()]} - def get_guild_scheduled_events(guild_id) do - Nostrum.Api.Guild.scheduled_events(guild_id) - end + defdelegate get_guild_scheduled_events(guild_id), + to: Nostrum.Api.Guild, + as: :scheduled_events - @doc """ - Get a scheduled event for a guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ScheduledEvent.get/2` directly instead. """ - @doc since: "0.5.0" - @spec get_guild_scheduled_event(Guild.id(), ScheduledEvent.id()) :: - error | {:ok, ScheduledEvent.t()} - def get_guild_scheduled_event(guild_id, event_id) do - Nostrum.Api.ScheduledEvent.get(guild_id, event_id) - end + defdelegate get_guild_scheduled_event(guild_id, event_id), + to: Nostrum.Api.ScheduledEvent, + as: :get - @doc """ - Delete a scheduled event for a guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ScheduledEvent.delete/2` directly instead. """ - @doc since: "0.5.0" - @spec delete_guild_scheduled_event(Guild.id(), ScheduledEvent.id()) :: - error | {:ok} - def delete_guild_scheduled_event(guild_id, event_id) do - Nostrum.Api.ScheduledEvent.delete(guild_id, event_id) - end - - @doc """ - Modify a scheduled event for a guild. + defdelegate delete_guild_scheduled_event(guild_id, event_id), + to: Nostrum.Api.ScheduledEvent, + as: :delete - Options are the same as for `create_guild_scheduled_event/2` except all fields are optional, - with the additional optional integer field `:status` which can be one of: - - * `1` - `SCHEDULED` - * `2` - `ACTIVE` - * `3` - `COMPLETED` - * `4` - `CANCELLED` - - Copied from the official documentation: - * If updating entity_type to `EXTERNAL`: - * `channel_id` is required and must be set to null - * `entity_metadata` with a `location` field must be provided - * `scheduled_end_time` must be provided + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ScheduledEvent.modify/4` directly instead. """ - @doc since: "0.5.0" - @spec modify_guild_scheduled_event( - Guild.id(), - ScheduledEvent.id(), - reason :: AuditLogEntry.reason(), - options - ) :: error | {:ok, ScheduledEvent.t()} - def modify_guild_scheduled_event(guild_id, event_id, reason \\ nil, options) do - Nostrum.Api.ScheduledEvent.modify(guild_id, event_id, reason, options) - end - - @doc """ - Get a list of users who have subscribed to an event. + defdelegate modify_guild_scheduled_event(guild_id, event_id, reason \\ nil, options), + to: Nostrum.Api.ScheduledEvent, + as: :modify - ## Options - All are optional, with their default values listed. - * `:limit` (integer) maximum number of users to return, defaults to `100` - * `:with_member` (boolean) whether to include the member object for each user, defaults to `false` - * `:before` (`t:Nostrum.Snowflake.t/0`) return only users before this user id, defaults to `nil` - * `:after` (`t:Nostrum.Snowflake.t/0`) return only users after this user id, defaults to `nil` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ScheduledEvent.users/3` directly instead. """ - @doc since: "0.5.0" - @spec get_guild_scheduled_event_users(Guild.id(), ScheduledEvent.id(), options) :: - error | {:ok, [ScheduledEvent.User.t()]} - def get_guild_scheduled_event_users(guild_id, event_id, params \\ []) do - Nostrum.Api.ScheduledEvent.users(guild_id, event_id, params) - end - - @doc ~S""" - Gets an invite by its `invite_code`. - - If successful, returns `{:ok, invite}`. Otherwise, returns a - `t:Nostrum.Api.error/0`. - - ## Options - - * `:with_counts` (boolean) - whether to include member count fields + defdelegate get_guild_scheduled_event_users(guild_id, event_id, params \\ []), + to: Nostrum.Api.ScheduledEvent, + as: :users - ## Examples - - ```elixir - Nostrum.Api.get_invite("zsjUsC") - - Nostrum.Api.get_invite("zsjUsC", with_counts: true) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Invite.get/2` directly instead. """ - @spec get_invite(Invite.code(), options) :: error | {:ok, Invite.simple_invite()} - def get_invite(invite_code, options \\ []) when is_binary(invite_code) do - Nostrum.Api.Invite.get(invite_code, options) - end + defdelegate get_invite(invite_code, options \\ []), + to: Nostrum.Api.Invite, + as: :get @doc ~S""" Same as `get_invite/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_invite!(Invite.code(), options) :: no_return | Invite.simple_invite() def get_invite!(invite_code, options \\ []) do get_invite(invite_code, options) |> bangify end - @doc ~S""" - Deletes an invite by its `invite_code`. - - This endpoint requires the `MANAGE_CHANNELS` permission. - - If successful, returns `{:ok, invite}`. Otherwise, returns a - `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.delete_invite("zsjUsC") - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Invite.delete/1` directly instead. """ - @spec delete_invite(Invite.code()) :: error | {:ok, Invite.simple_invite()} - def delete_invite(invite_code) when is_binary(invite_code) do - Nostrum.Api.Invite.delete(invite_code) - end + defdelegate delete_invite(invite_code), + to: Nostrum.Api.Invite, + as: :delete @doc ~S""" Same as `delete_invite/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec delete_invite!(Invite.code()) :: no_return | Invite.simple_invite() def delete_invite!(invite_code) do delete_invite(invite_code) |> bangify end - @doc """ - Gets a user by its `t:Nostrum.Struct.User.id/0`. - - If the request is successful, this function returns `{:ok, user}`, where - `user` is a `Nostrum.Struct.User`. Otherwise, returns `{:error, reason}`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.User.get/1` directly instead. """ - @spec get_user(User.id()) :: error | {:ok, User.t()} - def get_user(user_id) do - Nostrum.Api.User.get(user_id) - end + defdelegate get_user(user_id), + to: Nostrum.Api.User, + as: :get @doc """ Same as `get_user/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_user!(User.id()) :: no_return | User.t() def get_user!(user_id) do get_user(user_id) |> bangify end - @doc """ - Gets info on the current user. - - If nostrum's caching is enabled, it is recommended to use `Me.get/0` - instead of this function. This is because sending out an API request is much slower - than pulling from our cache. - - If the request is successful, this function returns `{:ok, user}`, where - `user` is nostrum's `Nostrum.Struct.User`. Otherwise, returns `{:error, reason}`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.get/0` directly instead. """ - @spec get_current_user() :: error | {:ok, User.t()} - def get_current_user do - Nostrum.Api.Self.get() - end + defdelegate get_current_user, + to: Nostrum.Api.Self, + as: :get @doc """ Same as `get_current_user/0`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_current_user!() :: no_return | User.t() def get_current_user! do get_current_user() |> bangify end - @doc ~S""" - Changes the username or avatar of the current user. - - ## Options - - * `:username` (string) - new username - * `:avatar` (string) - the user's avatar as [avatar data](https://discord.com/developers/docs/resources/user#avatar-data) - - ## Examples - - ```elixir - Nostrum.Api.modify_current_user(avatar: "data:image/jpeg;base64,YXl5IGJieSB1IGx1a2luIDQgc3VtIGZ1az8=") - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.modify/1` directly instead. """ - @spec modify_current_user(options) :: error | {:ok, User.t()} - def modify_current_user(options) do - Nostrum.Api.Self.modify(options) - end + defdelegate modify_current_user(options), + to: Nostrum.Api.Self, + as: :modify @doc """ Same as `modify_current_user/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec modify_current_user!(options) :: no_return | User.t() def modify_current_user!(options) do modify_current_user(options) |> bangify end - @doc """ - Gets a list of guilds the user is currently in. - - This endpoint requires the `guilds` OAuth2 scope. - - If successful, returns `{:ok, guilds}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:before` (`t:Nostrum.Snowflake.t/0`) - get guilds before this - guild ID - * `:after` (`t:Nostrum.Snowflake.t/0`) - get guilds after this guild - ID - * `:limit` (integer) - max number of guilds to return (1-100) - - ## Examples - - ```elixir - iex> Nostrum.Api.get_current_user_guilds(limit: 1) - {:ok, [%Nostrum.Struct.Guild{}]} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.guilds/1` directly instead. """ - @spec get_current_user_guilds(options) :: error | {:ok, [Guild.user_guild()]} - def get_current_user_guilds(options \\ []) do - Nostrum.Api.Self.guilds(options) - end + defdelegate get_current_user_guilds(options \\ []), + to: Nostrum.Api.Self, + as: :guilds @doc ~S""" Same as `get_current_user_guilds/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_current_user_guilds!(options) :: no_return | [Guild.user_guild()] def get_current_user_guilds!(options \\ []) do get_current_user_guilds(options) |> bangify end - @doc """ - Leaves a guild. - - Guild to leave is specified by `guild_id`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.leave/1` directly instead. """ - @spec leave_guild(integer) :: error | {:ok} - def leave_guild(guild_id) do - Nostrum.Api.Guild.leave(guild_id) - end - - @doc """ - Gets a list of our user's DM channels. - - If successful, returns `{:ok, dm_channels}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + defdelegate leave_guild(guild_id), + to: Nostrum.Api.Guild, + as: :leave - ## Examples - - ```elixir - Nostrum.Api.get_user_dms() - {:ok, [%Nostrum.Struct.Channel{type: 1} | _]} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.dms/0` directly instead. """ - @spec get_user_dms() :: error | {:ok, [Channel.dm_channel()]} - def get_user_dms do - Nostrum.Api.Self.dms() - end + defdelegate get_user_dms, + to: Nostrum.Api.Self, + as: :dms @doc ~S""" Same as `get_user_dms/0`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec get_user_dms!() :: no_return | [Channel.dm_channel()] def get_user_dms! do get_user_dms() |> bangify end - @doc ~S""" - Create a new DM channel with a user. - - If successful, returns `{:ok, dm_channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.create_dm(150061853001777154) - {:ok, %Nostrum.Struct.Channel{type: 1}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.User.create_dm/1` directly instead. """ - @spec create_dm(User.id()) :: error | {:ok, Channel.dm_channel()} - def create_dm(user_id) when is_snowflake(user_id) do - Nostrum.Api.User.create_dm(user_id) - end + defdelegate create_dm(user_id), + to: Nostrum.Api.User @doc ~S""" Same as `create_dm/1`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_dm!(User.id()) :: no_return | Channel.dm_channel() def create_dm!(user_id) do create_dm(user_id) |> bangify end - @doc """ - Creates a new group DM channel. - - If successful, returns `{:ok, group_dm_channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - `access_tokens` are user oauth2 tokens. `nicks` is a map that maps a user id - to a nickname. - - ## Examples - - ```elixir - Nostrum.Api.create_group_dm(["6qrZcUqja7812RVdnEKjpzOL4CvHBFG"], %{41771983423143937 => "My Nickname"}) - {:ok, %Nostrum.Struct.Channel{type: 3}} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.User.create_group_dm/2` directly instead. """ - @spec create_group_dm([String.t()], %{optional(User.id()) => String.t()}) :: - error | {:ok, Channel.group_dm_channel()} - def create_group_dm(access_tokens, nicks) when is_list(access_tokens) and is_map(nicks) do - Nostrum.Api.User.create_group_dm(access_tokens, nicks) - end + defdelegate create_group_dm(access_tokens, nicks), + to: Nostrum.Api.User @doc ~S""" Same as `create_group_dm/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ + @deprecated "Bang functions will be removed in v1.0" @spec create_group_dm!([String.t()], %{optional(User.id()) => String.t()}) :: no_return | Channel.group_dm_channel() def create_group_dm!(access_tokens, nicks) do @@ -2539,43 +1515,29 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Gets a list of user connections. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.connections/0` directly instead. """ - @spec get_user_connections() :: error | {:ok, list()} - def get_user_connections do - Nostrum.Api.Self.connections() - end + defdelegate get_user_connections, + to: Nostrum.Api.Self, + as: :connections - @doc """ - Gets a list of voice regions. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.voice_regions/0` directly instead. """ - @spec list_voice_regions() :: error | {:ok, [Nostrum.Struct.VoiceRegion.t()]} - def list_voice_regions do - Nostrum.Api.Guild.voice_regions() - end - - @doc """ - Creates a webhook. + defdelegate list_voice_regions, + to: Nostrum.Api.Guild, + as: :voice_regions - ## Parameters - - `channel_id` - Id of the channel to send the message to. - - `args` - Map with the following **required** keys: - - `name` - Name of the webhook. - - `avatar` - Base64 128x128 jpeg image for the default avatar. - - `reason` - An optional reason for the guild audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.create/3` directly instead. """ - @spec create_webhook( - Channel.id(), - %{ - name: String.t(), - avatar: String.t() - }, - AuditLogEntry.reason() - ) :: error | {:ok, Nostrum.Struct.Webhook.t()} - def create_webhook(channel_id, args, reason \\ nil) do - Nostrum.Api.Webhook.create(channel_id, args, reason) - end + defdelegate create_webhook(channel_id, args, reason \\ nil), + to: Nostrum.Api.Webhook, + as: :create @doc """ Retrieves the original message of a webhook. @@ -3149,6 +2111,7 @@ defmodule Nostrum.Api do Same as `create_interaction_response/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ @doc since: "0.5.0" + @deprecated "Bang functions will be removed in v1.0" @spec create_interaction_response!(Interaction.t(), map()) :: no_return() | {:ok} def create_interaction_response!(interaction, response) do create_interaction_response!(interaction.id, interaction.token, response) @@ -3221,6 +2184,7 @@ defmodule Nostrum.Api do Same as `edit_interaction_response/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ @doc since: "0.5.0" + @deprecated "Bang functions will be removed in v1.0" @spec edit_interaction_response!(Interaction.t(), map()) :: no_return() | Message.t() def edit_interaction_response!(%Interaction{} = interaction, response) do edit_interaction_response!(interaction.application_id, interaction.token, response) @@ -3242,6 +2206,7 @@ defmodule Nostrum.Api do Same as `edit_interaction_response/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ @doc since: "0.5.0" + @deprecated "Bang functions will be removed in v1.0" @spec edit_interaction_response!(User.id(), Interaction.token(), map()) :: no_return() | Message.t() def edit_interaction_response!(id \\ Me.get().id, token, response) do @@ -3260,6 +2225,7 @@ defmodule Nostrum.Api do end @doc since: "0.5.0" + @deprecated "Bang functions will be removed in v1.0" @spec delete_interaction_response!(Interaction.t()) :: no_return() | {:ok} def delete_interaction_response!(%Interaction{} = interaction) do delete_interaction_response(interaction.application_id, interaction.token) @@ -3279,6 +2245,7 @@ defmodule Nostrum.Api do Same as `delete_interaction_response/2`, but raises `Nostrum.Error.ApiError` in case of failure. """ @doc since: "0.5.0" + @deprecated "Bang functions will be removed in v1.0" @spec delete_interaction_response!(User.id(), Interaction.token()) :: no_return() | {:ok} def delete_interaction_response!(id \\ Me.get().id, token) do delete_interaction_response(id, token) @@ -3300,6 +2267,7 @@ defmodule Nostrum.Api do Same as `create_followup_message/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ @doc since: "0.5.0" + @deprecated "Bang functions will be removed in v1.0" @spec create_followup_message!(User.id(), Interaction.token(), map()) :: no_return() | Message.t() def create_followup_message!(application_id \\ Me.get().id, token, webhook_payload) do @@ -3330,6 +2298,7 @@ defmodule Nostrum.Api do Same as `delete_interaction_followup_message/3`, but raises `Nostrum.Error.ApiError` in case of failure. """ @doc since: "0.5.0" + @deprecated "Bang functions will be removed in v1.0" @spec delete_interaction_followup_message!(User.id(), Interaction.token(), Message.id()) :: no_return() | {:ok} def delete_interaction_followup_message!( @@ -3798,23 +2767,9 @@ defmodule Nostrum.Api do def pop_files(args), do: Map.pop!(args, :files) @doc false - def bangify(to_bang) do - Logger.warning(""" - Using the bangified version of the Api call is deprecated and will be removed in the next major release. - Please use the non-bangified version and handle the error case yourself. - """) - - case to_bang do - {:error, error} -> - raise(error) - - {:ok, body} -> - body - - {:ok} -> - {:ok} - end - end + def bangify({:error, error}), do: raise(error) + def bangify({:ok, body}), do: body + def bangify({:ok}), do: {:ok} def prepare_allowed_mentions(options) do with raw_options when raw_options != :all <- Map.get(options, :allowed_mentions, :all), From d1154c69d40a34ca113e40303b127eba173e0b80 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 23:04:24 -0700 Subject: [PATCH 23/42] `Nostrum.Api` delegate refactoring and deprecation warnings --- lib/nostrum/api.ex | 1275 ++++++++++++-------------------------------- 1 file changed, 337 insertions(+), 938 deletions(-) diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index f262731af..adca09ff0 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -45,30 +45,23 @@ defmodule Nostrum.Api do require Logger - import Nostrum.Snowflake, only: [is_snowflake: 1] import Nostrum.Api.Helpers, only: [has_files: 1] alias Nostrum.Api.Ratelimiter alias Nostrum.Cache.Me - alias Nostrum.Snowflake alias Nostrum.Struct.{ - ApplicationCommand, - AutoModerationRule, Channel, - Embed, Emoji, Guild, Interaction, Invite, Message, Message.Poll, - ThreadMember, - User, - Webhook + User } - alias Nostrum.Struct.Guild.{AuditLogEntry, Member, Role, ScheduledEvent} + alias Nostrum.Struct.Guild.{AuditLogEntry, Member, Role} @typedoc """ Represents a failed response from the API. @@ -1539,558 +1532,202 @@ defmodule Nostrum.Api do to: Nostrum.Api.Webhook, as: :create - @doc """ - Retrieves the original message of a webhook. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.get_message/2` directly instead. """ - @doc since: "0.7.0" - @spec get_webhook_message(Webhook.t(), Message.id()) :: - error | {:ok, Message.t()} - def get_webhook_message(webhook, message_id) do - Nostrum.Api.Webhook.get_message(webhook, message_id) - end - - @doc """ - Gets a list of webhooks for a channel. + defdelegate get_webhook_message(webhook, message_id), + to: Nostrum.Api.Webhook, + as: :get_message - ## Parameters - - `channel_id` - Channel to get webhooks for. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Channel.webhooks/1` directly instead. """ - @spec get_channel_webhooks(Channel.id()) :: error | {:ok, [Nostrum.Struct.Webhook.t()]} - def get_channel_webhooks(channel_id) do - Nostrum.Api.Channel.webhooks(channel_id) - end - - @doc """ - Gets a list of webhooks for a guild. + defdelegate get_channel_webhooks(channel_id), + to: Nostrum.Api.Channel, + as: :webhooks - ## Parameters - - `guild_id` - Guild to get webhooks for. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Guild.webhooks/1` directly instead. """ - @spec get_guild_webhooks(Guild.id()) :: error | {:ok, [Nostrum.Struct.Webhook.t()]} - def get_guild_webhooks(guild_id) do - Nostrum.Api.Guild.webhooks(guild_id) - end - - @doc """ - Gets a webhook by id. + defdelegate get_guild_webhooks(guild_id), + to: Nostrum.Api.Guild, + as: :webhooks - ## Parameters - - `webhook_id` - Id of the webhook to get. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.get/1` directly instead. """ - @spec get_webhook(Webhook.id()) :: error | {:ok, Nostrum.Struct.Webhook.t()} - def get_webhook(webhook_id) do - Nostrum.Api.Webhook.get(webhook_id) - end - - @doc """ - Gets a webhook by id and token. - - This method is exactly like `get_webhook/1` but does not require - authentication. + defdelegate get_webhook(webhook_id), + to: Nostrum.Api.Webhook, + as: :get - ## Parameters - - `webhook_id` - Id of the webhook to get. - - `webhook_token` - Token of the webhook to get. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.get_with_token/2` directly instead. """ - @spec get_webhook_with_token(Webhook.id(), Webhook.token()) :: - error | {:ok, Nostrum.Struct.Webhook.t()} - def get_webhook_with_token(webhook_id, webhook_token) do - Nostrum.Api.Webhook.get_with_token(webhook_id, webhook_token) - end - - @doc """ - Modifies a webhook. + defdelegate get_webhook_with_token(webhook_id, webhook_token), + to: Nostrum.Api.Webhook, + as: :get_with_token - ## Parameters - - `webhook_id` - Id of the webhook to modify. - - `args` - Map with the following *optional* keys: - - `name` - Name of the webhook. - - `avatar` - Base64 128x128 jpeg image for the default avatar. - - `reason` - An optional reason for the guild audit log. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.modify/3` directly instead. """ - @spec modify_webhook( - Webhook.id(), - %{ - name: String.t(), - avatar: String.t() - }, - AuditLogEntry.reason() - ) :: error | {:ok, Nostrum.Struct.Webhook.t()} - def modify_webhook(webhook_id, args, reason \\ nil) do - Nostrum.Api.Webhook.modify(webhook_id, args, reason) - end - - @doc """ - Modifies a webhook with a token. - - This method is exactly like `modify_webhook/1` but does not require - authentication. - - ## Parameters - - `webhook_id` - Id of the webhook to modify. - - `webhook_token` - Token of the webhook to get. - - `args` - Map with the following *optional* keys: - - `name` - Name of the webhook. - - `avatar` - Base64 128x128 jpeg image for the default avatar. - - `reason` - An optional reason for the guild audit log. - """ - @spec modify_webhook_with_token( - Webhook.id(), - Webhook.token(), - %{ - name: String.t(), - avatar: String.t() - }, - AuditLogEntry.reason() - ) :: error | {:ok, Nostrum.Struct.Webhook.t()} - def modify_webhook_with_token(webhook_id, webhook_token, args, reason \\ nil) do - Nostrum.Api.Webhook.modify_with_token(webhook_id, webhook_token, args, reason) - end - - @doc """ - Deletes a webhook. - - ## Parameters - - `webhook_id` - Id of webhook to delete. - - `reason` - An optional reason for the guild audit log. - """ - @spec delete_webhook(Webhook.id(), AuditLogEntry.reason()) :: error | {:ok} - def delete_webhook(webhook_id, reason \\ nil) do - Nostrum.Api.Webhook.delete(webhook_id, reason) - end - - @typep m1 :: %{ - required(:content) => String.t(), - optional(:username) => String.t(), - optional(:avatar_url) => String.t(), - optional(:tts) => boolean, - optional(:files) => [String.t() | %{body: iodata(), name: String.t()}], - optional(:flags) => non_neg_integer(), - optional(:thread_id) => Snowflake.t(), - optional(:embeds) => nonempty_list(Embed.t()) | nil, - optional(:allowed_mentions) => allowed_mentions() - } - - @typep m2 :: - %{ - optional(:content) => String.t() | nil, - optional(:username) => String.t(), - optional(:avatar_url) => String.t(), - optional(:tts) => boolean, - required(:files) => [String.t() | %{body: iodata(), name: String.t()}], - optional(:flags) => non_neg_integer(), - optional(:thread_id) => Snowflake.t(), - optional(:embeds) => nonempty_list(Embed.t()) | nil, - optional(:allowed_mentions) => allowed_mentions() - } - - @typep m3 :: - %{ - optional(:content) => String.t() | nil, - optional(:username) => String.t(), - optional(:avatar_url) => String.t(), - optional(:tts) => boolean, - optional(:files) => [String.t() | %{body: iodata(), name: String.t()}], - optional(:flags) => non_neg_integer(), - optional(:thread_id) => Snowflake.t(), - required(:embeds) => nonempty_list(Embed.t()), - optional(:allowed_mentions) => allowed_mentions() - } - - @type matrix :: m1 | m2 | m3 - - @spec execute_webhook( - Webhook.id() | User.id(), - Webhook.token() | Interaction.token(), - matrix, - boolean - ) :: - error | {:ok} | {:ok, Message.t()} - - @doc """ - Executes a webhook. - - ## Parameters - - `webhook_id` - Id of the webhook to execute. - - `webhook_token` - Token of the webhook to execute. - - `args` - Map with the following allowed keys: - - `content` - Message content. - - `files` - List of Files to send. - - `embeds` - List of embeds to send. - - `username` - Overrides the default name of the webhook. - - `avatar_url` - Overrides the default avatar of the webhook. - - `tts` - Whether the message should be read over text to speech. - - `flags` - Bitwise flags. - - `thread_id` - Send a message to the specified thread within the webhook's channel. - - `allowed_mentions` - Mentions to allow in the webhook message - - `wait` - Whether to return an error or not. Defaults to `false`. - - **Note**: If `wait` is `true`, this method will return a `Message.t()` on success. + defdelegate modify_webhook(webhook_id, args, reason \\ nil), + to: Nostrum.Api.Webhook, + as: :modify - At least one of `content`, `files` or `embeds` should be supplied in the `args` parameter. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.modify_with_token/4` directly instead. """ + defdelegate modify_webhook_with_token(webhook_id, webhook_token, args, reason \\ nil), + to: Nostrum.Api.Webhook, + as: :modify_with_token - def execute_webhook(webhook_id, webhook_token, args, wait \\ false) do - Nostrum.Api.Webhook.execute(webhook_id, webhook_token, args, wait) - end - - @doc """ - Edits a message previously created by the same webhook, - args are the same as `execute_webhook/3`, - however all fields are optional. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.delete/2` directly instead. """ - @doc since: "0.5.0" - @spec edit_webhook_message( - Webhook.id(), - Webhook.token(), - Message.id(), - map() - ) :: - error | {:ok, Message.t()} - def edit_webhook_message(webhook_id, webhook_token, message_id, args) do - Nostrum.Api.Webhook.edit_message(webhook_id, webhook_token, message_id, args) - end - - @doc """ - Executes a slack webhook. + defdelegate delete_webhook(webhook_id, reason \\ nil), + to: Nostrum.Api.Webhook, + as: :delete - ## Parameters - - `webhook_id` - Id of the webhook to execute. - - `webhook_token` - Token of the webhook to execute. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.execute/4` directly instead. """ - @spec execute_slack_webhook(Webhook.id(), Webhook.token(), boolean) :: error | {:ok} - def execute_slack_webhook(webhook_id, webhook_token, wait \\ false) do - Nostrum.Api.Webhook.execute_slack(webhook_id, webhook_token, wait) - end - - @doc """ - Executes a git webhook. + defdelegate execute_webhook(webhook_id, webhook_token, args, wait \\ false), + to: Nostrum.Api.Webhook, + as: :execute - ## Parameters - - `webhook_id` - Id of the webhook to execute. - - `webhook_token` - Token of the webhook to execute. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.edit_message/4` directly instead. """ - @spec execute_git_webhook(Webhook.id(), Webhook.token(), boolean) :: error | {:ok} - def execute_git_webhook(webhook_id, webhook_token, wait \\ false) do - Nostrum.Api.Webhook.execute_git(webhook_id, webhook_token, wait) - end - - @doc """ - Gets the bot's OAuth2 application info. + defdelegate edit_webhook_message(webhook_id, webhook_token, message_id, args), + to: Nostrum.Api.Webhook, + as: :edit_message - ## Example - ```elixir - Nostrum.Api.get_application_information - {:ok, - %{ - bot_public: false, - bot_require_code_grant: false, - description: "Test", - icon: nil, - id: "172150183260323840", - name: "Baba O-Riley", - owner: %{ - avatar: nil, - discriminator: "0042", - id: "172150183260323840", - username: "i own a bot" - }, - }} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.execute_slack/3` directly instead. """ - @spec get_application_information() :: error | {:ok, map()} - def get_application_information do - Nostrum.Api.Self.application_information() - end - - @doc """ - Fetch all global commands. - - ## Parameters - - `application_id`: Application ID for which to search commands. - If not given, this will be fetched from `Me`. - - ## Return value - A list of ``ApplicationCommand``s on success. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure - - ## Example + defdelegate execute_slack_webhook(webhook_id, webhook_token, wait \\ false), + to: Nostrum.Api.Webhook, + as: :execute_slack - ```elixir - iex> Nostrum.Api.get_global_application_commands - {:ok, - [ - %{ - application_id: "455589479713865749", - description: "ed, man! man, ed", - id: "789841753196331029", - name: "edit" - } - ]} - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Webhook.execute_git/3` directly instead. """ - @spec get_global_application_commands() :: {:ok, [map()]} | error - @spec get_global_application_commands(User.id()) :: {:ok, [map()]} | error - def get_global_application_commands(application_id \\ Me.get().id) do - Nostrum.Api.ApplicationCommand.global_commands(application_id) - end - - @doc """ - Create a new global application command. - - The new command will be available on all guilds in around an hour. - If you want to test commands, use `create_guild_application_command/2` instead, - as commands will become available instantly there. - If an existing command with the same name exists, it will be overwritten. - - ## Parameters - - `application_id`: Application ID for which to create the command. - If not given, this will be fetched from `Me`. - - `command`: Command configuration, see the linked API documentation for reference. - - ## Return value - The created command. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#create-global-application-command - - ## Example + defdelegate execute_git_webhook(webhook_id, webhook_token, wait \\ false), + to: Nostrum.Api.Webhook, + as: :execute_git - ```elixir - Nostrum.Api.create_global_application_command( - %{name: "edit", description: "ed, man! man, ed", options: []} - ) - ``` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Self.application_information/0` directly instead. """ - @spec create_global_application_command(ApplicationCommand.application_command_map()) :: - {:ok, map()} | error - @spec create_global_application_command(User.id(), ApplicationCommand.application_command_map()) :: - {:ok, map()} | error - def create_global_application_command(application_id \\ Me.get().id, command) do - Nostrum.Api.ApplicationCommand.create_global_command(application_id, command) - end - - @doc """ - Update an existing global application command. - - The updated command will be available on all guilds in around an hour. - - ## Parameters - - `application_id`: Application ID for which to edit the command. - If not given, this will be fetched from `Me`. - - `command_id`: The current snowflake of the command. - - `command`: Command configuration, see the linked API documentation for reference. - - ## Return value - The updated command. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command - """ - @spec edit_global_application_command( - Snowflake.t(), - ApplicationCommand.application_command_edit_map() - ) :: {:ok, map()} | error - @spec edit_global_application_command( - User.id(), - Snowflake.t(), - ApplicationCommand.application_command_edit_map() - ) :: {:ok, map()} | error - def edit_global_application_command( - application_id \\ Me.get().id, - command_id, - command - ) do - Nostrum.Api.ApplicationCommand.edit_global_command(application_id, command_id, command) - end - - @doc """ - Delete an existing global application command. + defdelegate get_application_information, + to: Nostrum.Api.Self, + as: :application_information - ## Parameters - - `application_id`: Application ID for which to create the command. - If not given, this will be fetched from `Me`. - - `command_id`: The current snowflake of the command. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.global_commands/1` directly instead. """ - @spec delete_global_application_command(Snowflake.t()) :: {:ok} | error - @spec delete_global_application_command(User.id(), Snowflake.t()) :: {:ok} | error - def delete_global_application_command(application_id \\ Me.get().id, command_id) do - Nostrum.Api.ApplicationCommand.delete_global_command(application_id, command_id) - end - - @doc """ - Overwrite the existing global application commands. - - This action will: - - Create any command that was provided and did not already exist - - Update any command that was provided and already existed if its configuration changed - - Delete any command that was not provided but existed on Discord's end - - Updates will be available in all guilds after 1 hour. - Commands that do not already exist will count toward daily application command create limits. + defdelegate get_global_application_commands(application_id \\ Me.get().id), + to: Nostrum.Api.ApplicationCommand, + as: :global_commands - ## Parameters - - `application_id`: Application ID for which to overwrite the commands. - If not given, this will be fetched from `Me`. - - `commands`: List of command configurations, see the linked API documentation for reference. - - ## Return value - Updated list of global application commands. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.create_global_command/2` directly instead. """ - @doc since: "0.5.0" - @spec bulk_overwrite_global_application_commands([ApplicationCommand.application_command_map()]) :: - {:ok, [map()]} | error - @spec bulk_overwrite_global_application_commands(User.id(), [ - ApplicationCommand.application_command_map() - ]) :: {:ok, [map()]} | error - def bulk_overwrite_global_application_commands(application_id \\ Me.get().id, commands) do - Nostrum.Api.ApplicationCommand.bulk_overwrite_global_commands(application_id, commands) - end + defdelegate create_global_application_command(application_id \\ Me.get().id, command), + to: Nostrum.Api.ApplicationCommand, + as: :create_global_command - @doc """ - Fetch all guild application commands for the given guild. - - ## Parameters - - `application_id`: Application ID for which to fetch commands. - If not given, this will be fetched from `Me`. - - `guild_id`: The guild ID for which guild application commands - should be requested. - - ## Return value - A list of ``ApplicationCommand``s on success. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.edit_global_command/3` directly instead. """ - @spec get_guild_application_commands(Guild.id()) :: {:ok, [map()]} | error - @spec get_guild_application_commands(User.id(), Guild.id()) :: {:ok, [map()]} | error - def get_guild_application_commands(application_id \\ Me.get().id, guild_id) do - Nostrum.Api.ApplicationCommand.guild_commands(application_id, guild_id) - end + defdelegate edit_global_application_command(application_id \\ Me.get().id, command_id, command), + to: Nostrum.Api.ApplicationCommand, + as: :edit_global_command - @doc """ - Create a guild application command on the specified guild. - - The new command will be available immediately. - - ## Parameters - - `application_id`: Application ID for which to create the command. - If not given, this will be fetched from `Me`. - - `guild_id`: Guild on which to create the command. - - `command`: Command configuration, see the linked API documentation for reference. - - ## Return value - The created command. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.delete_global_command/2` directly instead. """ - @spec create_guild_application_command(Guild.id(), ApplicationCommand.application_command_map()) :: - {:ok, map()} | error - @spec create_guild_application_command( - User.id(), - Guild.id(), - ApplicationCommand.application_command_map() - ) :: {:ok, map()} | error - def create_guild_application_command( - application_id \\ Me.get().id, - guild_id, - command - ) do - Nostrum.Api.ApplicationCommand.create_guild_command(application_id, guild_id, command) - end - - @doc """ - Update an existing guild application command. - - The updated command will be available immediately. - - ## Parameters - - `application_id`: Application ID for which to edit the command. - If not given, this will be fetched from `Me`. - - `guild_id`: Guild for which to update the command. - - `command_id`: The current snowflake of the command. - - `command`: Command configuration, see the linked API documentation for reference. + defdelegate delete_global_application_command(application_id \\ Me.get().id, command_id), + to: Nostrum.Api.ApplicationCommand, + as: :delete_global_command - ## Return value - The updated command. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.bulk_overwrite_global_commands/2` directly instead. """ - @spec edit_guild_application_command( - Guild.id(), - Snowflake.t(), - ApplicationCommand.application_command_edit_map() - ) :: {:ok, map()} | error - @spec edit_guild_application_command( - User.id(), - Guild.id(), - Snowflake.t(), - ApplicationCommand.application_command_edit_map() - ) :: - {:ok, map()} | error - def edit_guild_application_command( - application_id \\ Me.get().id, - guild_id, - command_id, - command - ) do - Nostrum.Api.ApplicationCommand.edit_guild_command( - application_id, - guild_id, - command_id, - command - ) - end - - @doc """ - Delete an existing guild application command. + defdelegate bulk_overwrite_global_application_commands(application_id \\ Me.get().id, commands), + to: Nostrum.Api.ApplicationCommand, + as: :bulk_overwrite_global_commands - ## Parameters - - `application_id`: Application ID for which to create the command. - If not given, this will be fetched from `Me`. - - `guild_id`: The guild on which the command exists. - - `command_id`: The current snowflake of the command. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.guild_commands/2` directly instead. """ - @spec delete_guild_application_command(Guild.id(), Snowflake.t()) :: {:ok} | error - @spec delete_guild_application_command(User.id(), Guild.id(), Snowflake.t()) :: {:ok} | error - def delete_guild_application_command( - application_id \\ Me.get().id, - guild_id, - command_id - ) do - Nostrum.Api.ApplicationCommand.delete_guild_command(application_id, guild_id, command_id) - end + defdelegate get_guild_application_commands(application_id \\ Me.get().id, guild_id), + to: Nostrum.Api.ApplicationCommand, + as: :guild_commands - @doc """ - Overwrite the existing guild application commands on the specified guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.create_guild_command/3` directly instead. + """ + defdelegate create_guild_application_command(application_id \\ Me.get().id, guild_id, command), + to: Nostrum.Api.ApplicationCommand, + as: :create_guild_command - This action will: - - Create any command that was provided and did not already exist - - Update any command that was provided and already existed if its configuration changed - - Delete any command that was not provided but existed on Discord's end + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.edit_guild_command/4` directly instead. + """ + defdelegate edit_guild_application_command( + application_id \\ Me.get().id, + guild_id, + command_id, + command + ), + to: Nostrum.Api.ApplicationCommand, + as: :edit_guild_command - ## Parameters - - `application_id`: Application ID for which to overwrite the commands. - If not given, this will be fetched from `Me`. - - `guild_id`: Guild on which to overwrite the commands. - - `commands`: List of command configurations, see the linked API documentation for reference. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.delete_guild_command/3` directly instead. + """ + defdelegate delete_guild_application_command( + application_id \\ Me.get().id, + guild_id, + command_id + ), + to: Nostrum.Api.ApplicationCommand, + as: :delete_guild_command - ## Return value - Updated list of guild application commands. See the official reference: - https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.bulk_overwrite_guild_commands/3` directly instead. """ - @doc since: "0.5.0" - @spec bulk_overwrite_guild_application_commands(Guild.id(), [ - ApplicationCommand.application_command_map() - ]) :: {:ok, [map()]} | error - @spec bulk_overwrite_guild_application_commands(User.id(), Guild.id(), [ - ApplicationCommand.application_command_map() - ]) :: - {:ok, [map()]} | error - def bulk_overwrite_guild_application_commands( - application_id \\ Me.get().id, - guild_id, - commands - ) do - Nostrum.Api.ApplicationCommand.bulk_overwrite_guild_commands( - application_id, - guild_id, - commands - ) - end + defdelegate bulk_overwrite_guild_application_commands( + application_id \\ Me.get().id, + guild_id, + commands + ), + to: Nostrum.Api.ApplicationCommand, + as: :bulk_overwrite_guild_commands # Why the two separate functions here? # For the standard use case of "responding to an interaction retrieved @@ -2117,58 +1754,27 @@ defmodule Nostrum.Api do create_interaction_response!(interaction.id, interaction.token, response) end - @doc """ - Create a response to an interaction received from the gateway. - - ## Parameters - - `id`: The interaction ID to which the response should be created. - - `token`: The interaction token. - - `response`: An [`InteractionResponse`](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object) - object. See the linked documentation. - - - ### Attachments - To include attachments in the response, you can include a `:files` field in the response. - This field expects a list of attachments which can be in either of the following formats: - - A path to the file to upload. - - A map with the following fields: - - `:body` The file contents. - - `:name` The filename of the file. - - ## Example - - ```elixir - response = %{ - type: 4, - data: %{ - content: "I copy and pasted this code." - } - } - Nostrum.Api.create_interaction_response(interaction, response) - ``` - - As an alternative to passing the interaction ID and token, the - original `t:Nostrum.Struct.Interaction.t/0` can also be passed - directly. See `create_interaction_response/2`. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Interaction.create_response/3` directly instead. """ - @spec create_interaction_response(Interaction.id(), Interaction.token(), map()) :: {:ok} | error - def create_interaction_response(id, token, response) do - Nostrum.Api.Interaction.create_response(id, token, response) - end + defdelegate create_interaction_response(id, token, response), + to: Nostrum.Api.Interaction, + as: :create_response + @deprecated "Bang functions will be removed in v1.0" def create_interaction_response!(id, token, response) do create_interaction_response(id, token, response) |> bangify end - @doc """ - Retrieves the original message of an interaction. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Interaction.original_response/2` directly instead. """ - @doc since: "0.7.0" - @spec get_original_interaction_response(Interaction.t()) :: error | {:ok, Message.t()} - def get_original_interaction_response(interaction) do - Nostrum.Api.Interaction.original_response(interaction.application_id, interaction.token) - end + defdelegate get_original_interaction_response(interaction), + to: Nostrum.Api.Interaction, + as: :original_response @doc """ Same as `edit_interaction_response/3`, but directly takes the @@ -2190,17 +1796,13 @@ defmodule Nostrum.Api do edit_interaction_response!(interaction.application_id, interaction.token, response) end - @doc """ - Edits the original interaction response. - - Functions the same as `edit_webhook_message/3` + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Interaction.edit_response/3` directly instead. """ - @doc since: "0.5.0" - @spec edit_interaction_response(User.id(), Interaction.token(), map()) :: - {:ok, Message.t()} | error - def edit_interaction_response(id \\ Me.get().id, token, response) do - Nostrum.Api.Interaction.edit_response(id, token, response) - end + defdelegate edit_interaction_response(id \\ Me.get().id, token, response), + to: Nostrum.Api.Interaction, + as: :edit_response @doc """ Same as `edit_interaction_response/3`, but raises `Nostrum.Error.ApiError` in case of failure. @@ -2232,14 +1834,13 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Deletes the original interaction response. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Interaction.delete_response/2` directly instead. """ - @doc since: "0.5.0" - @spec delete_interaction_response(User.id(), Interaction.token()) :: {:ok} | error - def delete_interaction_response(id \\ Me.get().id, token) do - Nostrum.Api.Interaction.delete_response(id, token) - end + defdelegate delete_interaction_response(id \\ Me.get().id, token), + to: Nostrum.Api.Interaction, + as: :delete_response @doc """ Same as `delete_interaction_response/2`, but raises `Nostrum.Error.ApiError` in case of failure. @@ -2252,16 +1853,13 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Create a followup message for an interaction. - - Delegates to ``execute_webhook/3``, see the function for more details. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Interaction.create_followup_message/3` directly instead. """ - @spec create_followup_message(User.id(), Interaction.token(), map()) :: - {:ok, Message.t()} | error - def create_followup_message(application_id \\ Me.get().id, token, webhook_payload) do - Nostrum.Api.Interaction.create_followup_message(application_id, token, webhook_payload) - end + defdelegate create_followup_message(application_id \\ Me.get().id, token, webhook_payload), + to: Nostrum.Api.Interaction, + as: :create_followup_message @doc """ Same as `create_followup_message/3`, but raises `Nostrum.Error.ApiError` in case of failure. @@ -2275,24 +1873,17 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Delete a followup message for an interaction. - - ## Parameters - - `application_id`: Application ID for which to create the command. - If not given, this will be fetched from `Me`. - - `token`: Interaction token. - - `message_id`: Followup message ID. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Interaction.delete_followup_message/3` directly instead. """ - @spec delete_interaction_followup_message(User.id(), Interaction.token(), Message.id()) :: - {:ok} | error - def delete_interaction_followup_message( - application_id \\ Me.get().id, - token, - message_id - ) do - Nostrum.Api.Interaction.delete_followup_message(application_id, token, message_id) - end + defdelegate delete_interaction_followup_message( + application_id \\ Me.get().id, + token, + message_id + ), + to: Nostrum.Api.Interaction, + as: :delete_followup_message @doc """ Same as `delete_interaction_followup_message/3`, but raises `Nostrum.Error.ApiError` in case of failure. @@ -2310,378 +1901,186 @@ defmodule Nostrum.Api do |> bangify end - @doc """ - Fetches command permissions for all commands for your application in a guild. - - ## Parameters - - `application_id`: Application ID commands are registered under. - If not given, this will be fetched from `Me`. - - `guild_id`: Guild ID to fetch command permissions from. - - ## Return value - This method returns a list of guild application command permission objects, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.guild_permissions/2` directly instead. """ - @doc since: "0.5.0" - @spec get_guild_application_command_permissions(Guild.id()) :: {:ok, [map()]} | error - @spec get_guild_application_command_permissions(User.id(), Guild.id()) :: {:ok, [map()]} | error - def get_guild_application_command_permissions( - application_id \\ Me.get().id, - guild_id - ) do - Nostrum.Api.ApplicationCommand.guild_permissions(application_id, guild_id) - end - - @doc """ - Fetches command permissions for a specific command for your application in a guild. - - ## Parameters - - `application_id`: Application ID commands are registered under. - If not given, this will be fetched from `Me`. - - `guild_id`: Guild ID to fetch command permissions from. - - `command_id`: Command ID to fetch permissions for. + defdelegate get_guild_application_command_permissions(application_id \\ Me.get().id, guild_id), + to: Nostrum.Api.ApplicationCommand, + as: :guild_permissions - ## Return value - This method returns a single guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.permissions/3` directly instead. """ - @doc since: "0.5.0" - @spec get_application_command_permissions(Guild.id(), Snowflake.t()) :: - {:ok, map()} | error - @spec get_application_command_permissions(User.id(), Guild.id(), Snowflake.t()) :: - {:ok, map()} | error - def get_application_command_permissions( - application_id \\ Me.get().id, - guild_id, - command_id - ) do - Nostrum.Api.ApplicationCommand.permissions(application_id, guild_id, command_id) - end + defdelegate get_application_command_permissions( + application_id \\ Me.get().id, + guild_id, + command_id + ), + to: Nostrum.Api.ApplicationCommand, + as: :permissions - @doc """ - Edits command permissions for a specific command for your application in a guild. You can only add up to 10 permission overwrites for a command. - - ## Parameters - - `application_id`: Application ID commands are registered under. - If not given, this will be fetched from `Me`. - - `guild_id`: Guild ID to fetch command permissions from. - - `command_id`: Command ID to fetch permissions for. - - `permissions`: List of [application command permissions](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure) - - ## Return value - This method returns a guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.edit_command_permissions/4` directly instead. """ - @doc since: "0.5.0" - @spec edit_application_command_permissions(Guild.id(), Snowflake.t(), [ - ApplicationCommand.application_command_permissions() - ]) :: - {:ok, map()} | error - @spec edit_application_command_permissions(User.id(), Guild.id(), Snowflake.t(), [ - ApplicationCommand.application_command_permissions() - ]) :: - {:ok, map()} | error - def edit_application_command_permissions( - application_id \\ Me.get().id, - guild_id, - command_id, - permissions - ) do - Nostrum.Api.ApplicationCommand.edit_command_permissions( - application_id, - guild_id, - command_id, - permissions - ) - end - - @doc """ - Edits command permissions for a specific command for your application in a guild. You can only add up to 10 permission overwrites for a command. + defdelegate edit_application_command_permissions( + application_id \\ Me.get().id, + guild_id, + command_id, + permissions + ), + to: Nostrum.Api.ApplicationCommand, + as: :edit_command_permissions - ## Parameters - - `application_id`: Application ID commands are registered under. - If not given, this will be fetched from `Me`. - - `guild_id`: Guild ID to fetch command permissions from. - - `command_id`: Command ID to fetch permissions for. - - `permissions`: List of partial [guild application command permissions](hhttps://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure) with `id` and `permissions`. You can add up to 10 overwrites per command. - - ## Return value - This method returns a guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.ApplicationCommand.batch_edit_permissions/3` directly instead. """ - @doc since: "0.5.0" - @spec batch_edit_application_command_permissions(Guild.id(), [ - %{ - id: Snowflake.t(), - permissions: [ApplicationCommand.application_command_permissions()] - } - ]) :: - {:ok, map()} | error - @spec batch_edit_application_command_permissions(User.id(), Guild.id(), [ - %{ - id: Snowflake.t(), - permissions: [ApplicationCommand.application_command_permissions()] - } - ]) :: - {:ok, map()} | error - def batch_edit_application_command_permissions( - application_id \\ Me.get().id, - guild_id, - permissions - ) do - Nostrum.Api.ApplicationCommand.batch_edit_permissions( - application_id, - guild_id, - permissions - ) - end - - @type thread_with_message_params :: %{ - required(:name) => String.t(), - optional(:auto_archive_duration) => 60 | 1440 | 4320 | 10_080, - optional(:rate_limit_per_user) => 0..21_600 - } - - @doc """ - Create a thread on a channel message. - - The `thread_id` will be the same as the id of the message, as such no message can have more than one thread. - - If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`. - - An optional `reason` argument can be given for the audit log. - - ## Options - - `name`: Name of the thread, max 100 characters. - - `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080. - - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. + defdelegate batch_edit_application_command_permissions( + application_id \\ Me.get().id, + guild_id, + permissions + ), + to: Nostrum.Api.ApplicationCommand, + as: :batch_edit_permissions + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.create_with_message/4` directly instead. """ - @doc since: "0.5.1" - @spec start_thread_with_message( - Channel.id(), - Message.id(), - thread_with_message_params, - AuditLogEntry.reason() - ) :: - {:ok, Channel.t()} | error - def start_thread_with_message(channel_id, message_id, options, reason \\ nil) do - Nostrum.Api.Thread.create_with_message(channel_id, message_id, options, reason) - end - - @doc """ - Create a new thread in a forum channel. + defdelegate start_thread_with_message(channel_id, message_id, options, reason \\ nil), + to: Nostrum.Api.Thread, + as: :create_with_message - If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`. - - An optional `reason` argument can be given for the audit log. - - ## Options - - `name`: Name of the thread, max 100 characters. - - `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080. - - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. - - `applied_tags`: An array of tag ids to apply to the thread. - - `message`: The first message in the created thread. - - ### Thread Message Options - - `content`: The content of the message. - - `embeds`: A list of embeds. - - `allowed_mentions`: Allowed mentions object. - - `components`: A list of components. - - `sticker_ids`: A list of sticker ids. - - `:files` - a list of files where each element is the same format as the `:file` option. If both - `:file` and `:files` are specified, `:file` will be prepended to the `:files` list. - - At least one of `content`, `embeds`, `sticker_ids`, or `files` must be specified. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.create_in_forum/3` directly instead. """ - @doc since: "0.7.0" - @spec start_thread_in_forum_channel(Channel.id(), map(), AuditLogEntry.reason()) :: - {:ok, Channel.t()} | error - def start_thread_in_forum_channel(channel_id, options, reason \\ nil) do - Nostrum.Api.Thread.create_in_forum(channel_id, options, reason) - end + defdelegate start_thread_in_forum_channel(channel_id, options, reason \\ nil), + to: Nostrum.Api.Thread, + as: :create_in_forum - @doc """ - Returns a thread member object for the specified user if they are a member of the thread + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.member/2` directly instead. """ - @doc since: "0.5.1" - @spec get_thread_member(Channel.id(), User.id()) :: {:ok, ThreadMember.t()} | error - def get_thread_member(thread_id, user_id) do - Nostrum.Api.Thread.member(thread_id, user_id) - end - - @doc """ - Returns a list of thread members for the specified thread. + defdelegate get_thread_member(thread_id, user_id), + to: Nostrum.Api.Thread, + as: :member - This endpoint is restricted according to whether the `GUILD_MEMBERS` privileged intent is enabled. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.members/1` directly instead. """ - @doc since: "0.5.1" - @spec get_thread_members(Channel.id()) :: {:ok, [ThreadMember.t()]} | error - def get_thread_members(thread_id) do - Nostrum.Api.Thread.members(thread_id) - end - - @doc """ - Return all active threads for the current guild. + defdelegate get_thread_members(thread_id), + to: Nostrum.Api.Thread, + as: :members - Response body is a map with the following keys: - - `threads`: A list of channel objects. - - `members`: A list of `ThreadMember` objects, one for each returned thread the current user has joined. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.list/1` directly instead. """ - @doc since: "0.5.1" - @spec list_guild_threads(Guild.id()) :: - {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()]}} | error - def list_guild_threads(guild_id) do - Nostrum.Api.Thread.list(guild_id) - end - - @doc """ - Returns a list of archived threads for a given channel. - - Threads are sorted by the `archive_timestamp` field, in descending order. - - ## Response body - Response body is a map with the following keys: - - `threads`: A list of channel objects. - - `members`: A list of `ThreadMember` objects, one for each returned thread the current user has joined. - - `has_more`: A boolean indicating whether there are more archived threads that can be fetched. + defdelegate list_guild_threads(guild_id), + to: Nostrum.Api.Thread, + as: :list - ## Options - - `before`: Returns threads before this timestamp, can be either a `DateTime` or [ISO8601 timestamp](`DateTime.to_iso8601/3`). - - `limit`: Optional maximum number of threads to return. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.public_archived_threads/2` directly instead. """ - @doc since: "0.5.1" - @spec list_public_archived_threads(Channel.id(), options) :: - {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} - | error - def list_public_archived_threads(channel_id, options \\ []) do - Nostrum.Api.Thread.public_archived_threads(channel_id, options) - end + defdelegate list_public_archived_threads(channel_id, options \\ []), + to: Nostrum.Api.Thread, + as: :public_archived_threads - @doc """ - Same as `list_public_archived_threads/2`, but for private threads instead of public. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.private_archived_threads/2` directly instead. """ - @doc since: "0.5.1" - @spec list_private_archived_threads(Channel.id(), options) :: - {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} - | error - def list_private_archived_threads(channel_id, options \\ []) do - Nostrum.Api.Thread.private_archived_threads(channel_id, options) - end + defdelegate list_private_archived_threads(channel_id, options \\ []), + to: Nostrum.Api.Thread, + as: :private_archived_threads - @doc """ - Same as `list_public_archived_threads/2`, but only returns private threads that the current user has joined. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.joined_private_archived_threads/2` directly instead. """ - @doc since: "0.5.1" - @spec list_joined_private_archived_threads(Channel.id(), options) :: - {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} - | error - def list_joined_private_archived_threads(channel_id, options \\ []) do - Nostrum.Api.Thread.joined_private_archived_threads(channel_id, options) - end + defdelegate list_joined_private_archived_threads(channel_id, options \\ []), + to: Nostrum.Api.Thread, + as: :joined_private_archived_threads - @doc """ - Join an existing thread, requires that the thread is not archived. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.join/1` directly instead. """ - @doc since: "0.5.1" - @spec join_thread(Channel.id()) :: {:ok} | error - def join_thread(thread_id) do - Nostrum.Api.Thread.join(thread_id) - end + defdelegate join_thread(thread_id), + to: Nostrum.Api.Thread, + as: :join - @doc """ - Add a user to a thread, requires the ability to send messages in the thread. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.add_member/2` directly instead. """ - @doc since: "0.5.1" - def add_thread_member(thread_id, user_id) do - Nostrum.Api.Thread.add_member(thread_id, user_id) - end + defdelegate add_thread_member(thread_id, user_id), + to: Nostrum.Api.Thread, + as: :add_member - @doc """ - Leave a thread, requires that the thread is not archived. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.leave/1` directly instead. """ - @doc since: "0.5.1" - @spec leave_thread(Channel.id()) :: {:ok} | error - def leave_thread(thread_id) do - Nostrum.Api.Thread.leave(thread_id) - end - - @doc """ - Removes another user from a thread, requires that the thread is not archived. + defdelegate leave_thread(thread_id), + to: Nostrum.Api.Thread, + as: :leave - Also requires the `MANAGE_THREADS` permission, or the creator of the thread if the thread is private. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.Thread.remove_member/2` directly instead. """ - @doc since: "0.5.1" - @spec remove_thread_member(Channel.id(), User.id()) :: {:ok} | error - def remove_thread_member(thread_id, user_id) do - Nostrum.Api.Thread.remove_member(thread_id, user_id) - end + defdelegate remove_thread_member(thread_id, user_id), + to: Nostrum.Api.Thread, + as: :remove_member - @doc """ - Get a list of all auto-moderation rules for a guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.AutoModeration.rules/1` directly instead. """ - @doc since: "0.7.0" - @spec get_guild_auto_moderation_rules(Guild.id()) :: {:ok, [AutoModerationRule.t()]} | error - def get_guild_auto_moderation_rules(guild_id) do - Nostrum.Api.AutoModeration.rules(guild_id) - end + defdelegate get_guild_auto_moderation_rules(guild_id), + to: Nostrum.Api.AutoModeration, + as: :rules - @doc """ - Get a single auto-moderation rule for a guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.AutoModeration.rule/2` directly instead. """ - @doc since: "0.7.0" - @spec get_guild_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: - {:ok, AutoModerationRule.t()} | error - def get_guild_auto_moderation_rule(guild_id, rule_id) do - Nostrum.Api.AutoModeration.rule(guild_id, rule_id) - end - - @doc """ - Create a new auto-moderation rule for a guild. - - ## Options - * `:name` (`t:String.t/0`) - The name of the rule. - * `:event_type` (`t:AutoModerationRule.event_type/0`) - The type of event that triggers the rule. - * `:trigger_type` (`t:AutoModerationRule.trigger_type/0`) - The type of content that triggers the rule. - * `:trigger_metadata` (`t:AutoModerationRule.trigger_metadata/0`) - The metadata associated with the rule trigger. - - optional, based on the `:trigger_type`. - * `:actions` (`t:AutoModerationRule.actions/0`) - The actions to take when the rule is triggered. - * `:enabled` (`t:AutoModerationRule.enabled/0`) - Whether the rule is enabled or not. - - optional, defaults to `false`. - * `:exempt_roles` - (`t:AutoModerationRule.exempt_roles/0`) - A list of role id's that are exempt from the rule. - - optional, defaults to `[]`, maximum of 20. - * `:exempt_channels` - (`t:AutoModerationRule.exempt_channels/0`) - A list of channel id's that are exempt from the rule. - - optional, defaults to `[]`, maximum of 50. - """ - @doc since: "0.7.0" - @spec create_guild_auto_moderation_rule(Guild.id(), options()) :: - {:ok, AutoModerationRule.t()} | error - def create_guild_auto_moderation_rule(guild_id, options) when is_list(options), - do: create_guild_auto_moderation_rule(guild_id, Map.new(options)) - - def create_guild_auto_moderation_rule(guild_id, options) do - Nostrum.Api.AutoModeration.create_rule(guild_id, options) - end - - @doc """ - Modify an auto-moderation rule for a guild. + defdelegate get_guild_auto_moderation_rule(guild_id, rule_id), + to: Nostrum.Api.AutoModeration, + as: :rule - Takes the same options as `create_guild_auto_moderation_rule/2`, however all fields are optional. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.AutoModeration.create_rule/2` directly instead. """ - @doc since: "0.7.0" - @spec modify_guild_auto_moderation_rule(Guild.id(), AutoModerationRule.id(), options()) :: - {:ok, AutoModerationRule.t()} | error - def modify_guild_auto_moderation_rule(guild_id, rule_id, options) when is_list(options), - do: modify_guild_auto_moderation_rule(guild_id, rule_id, Map.new(options)) + defdelegate create_guild_auto_moderation_rule(guild_id, options), + to: Nostrum.Api.AutoModeration, + as: :create_rule - def modify_guild_auto_moderation_rule(guild_id, rule_id, options) do - Nostrum.Api.AutoModeration.modify_rule(guild_id, rule_id, options) - end + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.AutoModeration.modify_rule/3` directly instead. + """ + defdelegate modify_guild_auto_moderation_rule(guild_id, rule_id, options), + to: Nostrum.Api.AutoModeration, + as: :modify_rule - @doc """ - Delete an auto-moderation rule for a guild. + @deprecated """ + Calling `Nostrum.Api` functions directly will be removed in v1.0 + Use `Nostrum.Api.AutoModeration.delete_rule/2` directly instead. """ - @doc since: "0.7.0" - @spec delete_guild_auto_moderation_rule(Guild.id(), AutoModerationRule.id()) :: {:ok} | error - def delete_guild_auto_moderation_rule(guild_id, rule_id) do - Nostrum.Api.AutoModeration.delete_rule(guild_id, rule_id) - end + defdelegate delete_guild_auto_moderation_rule(guild_id, rule_id), + to: Nostrum.Api.AutoModeration, + as: :delete_rule @spec maybe_add_reason(String.t() | nil) :: list() def maybe_add_reason(reason) do From 1c135be8c5053280473241369b96892cc869374e Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 23:04:58 -0700 Subject: [PATCH 24/42] move type definitions to Nostrum.Api.Webhook --- lib/nostrum/api/webhook.ex | 42 +++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex index 4e9c8590f..a62b7b1be 100644 --- a/lib/nostrum/api/webhook.ex +++ b/lib/nostrum/api/webhook.ex @@ -103,6 +103,46 @@ defmodule Nostrum.Api.Webhook do Api.request(:post, Constants.webhook_slack(webhook_id, webhook_token), wait: wait) end + @typep m1 :: %{ + required(:content) => String.t(), + optional(:username) => String.t(), + optional(:avatar_url) => String.t(), + optional(:tts) => boolean, + optional(:files) => [String.t() | %{body: iodata(), name: String.t()}], + optional(:flags) => non_neg_integer(), + optional(:thread_id) => Snowflake.t(), + optional(:embeds) => nonempty_list(Embed.t()) | nil, + optional(:allowed_mentions) => Api.allowed_mentions() + } + + @typep m2 :: + %{ + optional(:content) => String.t() | nil, + optional(:username) => String.t(), + optional(:avatar_url) => String.t(), + optional(:tts) => boolean, + required(:files) => [String.t() | %{body: iodata(), name: String.t()}], + optional(:flags) => non_neg_integer(), + optional(:thread_id) => Snowflake.t(), + optional(:embeds) => nonempty_list(Embed.t()) | nil, + optional(:allowed_mentions) => Api.allowed_mentions() + } + + @typep m3 :: + %{ + optional(:content) => String.t() | nil, + optional(:username) => String.t(), + optional(:avatar_url) => String.t(), + optional(:tts) => boolean, + optional(:files) => [String.t() | %{body: iodata(), name: String.t()}], + optional(:flags) => non_neg_integer(), + optional(:thread_id) => Snowflake.t(), + required(:embeds) => nonempty_list(Embed.t()), + optional(:allowed_mentions) => Api.allowed_mentions() + } + + @type matrix :: m1 | m2 | m3 + @doc """ Executes a webhook. @@ -128,7 +168,7 @@ defmodule Nostrum.Api.Webhook do @spec execute( Webhook.id() | User.id(), Webhook.token() | Interaction.token(), - Api.matrix(), + matrix, boolean ) :: Api.error() | {:ok} | {:ok, Message.t()} From 2844303b1c9eb6e96415e23b25c15c288af7dc78 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 25 Oct 2024 23:06:32 -0700 Subject: [PATCH 25/42] alias missing modules in Nostrum.Api.Webhook --- lib/nostrum/api/webhook.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex index a62b7b1be..8b107f227 100644 --- a/lib/nostrum/api/webhook.ex +++ b/lib/nostrum/api/webhook.ex @@ -2,12 +2,14 @@ defmodule Nostrum.Api.Webhook do alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants + alias Nostrum.Snowflake alias Nostrum.Struct.Channel alias Nostrum.Struct.Webhook alias Nostrum.Struct.Guild.AuditLogEntry alias Nostrum.Struct.Message alias Nostrum.Struct.Interaction alias Nostrum.Struct.User + alias Nostrum.Struct.Embed @doc """ Creates a webhook. From 323a3bc5ab662e475a8f4a8afecb6669afb9ee3b Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Wed, 30 Oct 2024 08:12:03 -0700 Subject: [PATCH 26/42] Change doc "since" version to 1.x.x --- lib/nostrum/api/application_command.ex | 12 ++++++------ lib/nostrum/api/auto_moderation.ex | 10 +++++----- lib/nostrum/api/guild.ex | 4 ++-- lib/nostrum/api/interaction.ex | 12 ++++++------ lib/nostrum/api/scheduled_event.ex | 10 +++++----- lib/nostrum/api/sticker.ex | 16 ++++++++-------- lib/nostrum/api/thread.ex | 26 +++++++++++++------------- lib/nostrum/api/webhook.ex | 4 ++-- 8 files changed, 47 insertions(+), 47 deletions(-) diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex index 55b7a2f38..ecd299b1f 100644 --- a/lib/nostrum/api/application_command.ex +++ b/lib/nostrum/api/application_command.ex @@ -21,7 +21,7 @@ defmodule Nostrum.Api.ApplicationCommand do ## Return value This method returns a guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec batch_edit_permissions(Guild.id(), [ %{ id: Snowflake.t(), @@ -69,7 +69,7 @@ defmodule Nostrum.Api.ApplicationCommand do Updated list of global application commands. See the official reference: https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec bulk_overwrite_global_commands([ ApplicationCommand.application_command_map() ]) :: @@ -100,7 +100,7 @@ defmodule Nostrum.Api.ApplicationCommand do Updated list of guild application commands. See the official reference: https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec bulk_overwrite_guild_commands(Guild.id(), [ ApplicationCommand.application_command_map() ]) :: {:ok, [map()]} | Api.error() @@ -235,7 +235,7 @@ defmodule Nostrum.Api.ApplicationCommand do ## Return value This method returns a guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec edit_command_permissions(Guild.id(), Snowflake.t(), [ ApplicationCommand.application_command_permissions() ]) :: @@ -347,7 +347,7 @@ defmodule Nostrum.Api.ApplicationCommand do ## Return value This method returns a single guild application command permission object, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec permissions(Guild.id(), Snowflake.t()) :: {:ok, map()} | Api.error() @spec permissions(User.id(), Guild.id(), Snowflake.t()) :: @@ -408,7 +408,7 @@ defmodule Nostrum.Api.ApplicationCommand do ## Return value This method returns a list of guild application command permission objects, see all available values on the [Discord API docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure). """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec guild_permissions(Guild.id()) :: {:ok, [map()]} | Api.error() @spec guild_permissions(User.id(), Guild.id()) :: {:ok, [map()]} | Api.error() diff --git a/lib/nostrum/api/auto_moderation.ex b/lib/nostrum/api/auto_moderation.ex index b153ac30a..a54b28fdf 100644 --- a/lib/nostrum/api/auto_moderation.ex +++ b/lib/nostrum/api/auto_moderation.ex @@ -22,7 +22,7 @@ defmodule Nostrum.Api.AutoModeration do * `:exempt_channels` - (`t:AutoModerationRule.exempt_channels/0`) - A list of channel id's that are exempt from the rule. - optional, defaults to `[]`, maximum of 50. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec create_rule(Guild.id(), Api.options()) :: {:ok, AutoModerationRule.t()} | Api.error() def create_rule(guild_id, options) when is_list(options), @@ -36,7 +36,7 @@ defmodule Nostrum.Api.AutoModeration do @doc """ Delete an auto-moderation rule for a guild. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec delete_rule(Guild.id(), AutoModerationRule.id()) :: {:ok} | Api.error() def delete_rule(guild_id, rule_id) do @@ -46,7 +46,7 @@ defmodule Nostrum.Api.AutoModeration do @doc """ Get a list of all auto-moderation rules for a guild. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec rules(Guild.id()) :: {:ok, [AutoModerationRule.t()]} | Api.error() def rules(guild_id) do Api.request(:get, Constants.guild_auto_moderation_rule(guild_id)) @@ -56,7 +56,7 @@ defmodule Nostrum.Api.AutoModeration do @doc """ Get a single auto-moderation rule for a guild. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec rule(Guild.id(), AutoModerationRule.id()) :: {:ok, AutoModerationRule.t()} | Api.error() def rule(guild_id, rule_id) do @@ -69,7 +69,7 @@ defmodule Nostrum.Api.AutoModeration do Takes the same options as `create_rule/2`, however all fields are optional. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec modify_rule(Guild.id(), AutoModerationRule.id(), Api.options()) :: {:ok, AutoModerationRule.t()} | Api.error() def modify_rule(guild_id, rule_id, options) when is_list(options), diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex index e337b592a..a8665237a 100644 --- a/lib/nostrum/api/guild.ex +++ b/lib/nostrum/api/guild.ex @@ -266,7 +266,7 @@ defmodule Nostrum.Api.Guild do @doc """ Gets a ban object for the given user from a guild. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec ban(Guild.id(), User.id()) :: Api.error() | {:ok, Ban.t()} def ban(guild_id, user_id) do Api.request(:get, Constants.guild_ban(guild_id, user_id)) @@ -384,7 +384,7 @@ defmodule Nostrum.Api.Guild do @doc """ Get a list of scheduled events for a guild. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec scheduled_events(Guild.id()) :: Api.error() | {:ok, [ScheduledEvent.t()]} def scheduled_events(guild_id) do Api.request(:get, Constants.guild_scheduled_events(guild_id)) diff --git a/lib/nostrum/api/interaction.ex b/lib/nostrum/api/interaction.ex index 629d27210..ff0a1269e 100644 --- a/lib/nostrum/api/interaction.ex +++ b/lib/nostrum/api/interaction.ex @@ -97,7 +97,7 @@ defmodule Nostrum.Api.Interaction do Same as `delete_response/3`, but directly takes the `t:Nostrum.Struct.Interaction.t/0` received from the gateway. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec delete_response(Interaction.t()) :: {:ok} | Api.error() def delete_response(%Interaction{application_id: application_id, token: token}) do delete_response(application_id, token) @@ -106,7 +106,7 @@ defmodule Nostrum.Api.Interaction do @doc """ Deletes the original interaction response. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec delete_response(User.id(), Interaction.token()) :: {:ok} | Api.error() def delete_response(id \\ Me.get().id, token) do Api.request(:delete, Constants.interaction_callback_original(id, token)) @@ -116,7 +116,7 @@ defmodule Nostrum.Api.Interaction do Same as `edit_response/3`, but directly takes the `t:Nostrum.Struct.Interaction.t/0` received from the gateway. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec edit_response(Interaction.t(), map()) :: {:ok, Message.t()} | Api.error() def edit_response(%Interaction{application_id: application_id, token: token}, response) do edit_response(application_id, token, response) @@ -127,7 +127,7 @@ defmodule Nostrum.Api.Interaction do Functions the same as `edit_webhook_message/3` """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec edit_response(User.id(), Interaction.token(), map()) :: {:ok, Message.t()} | Api.error() def edit_response(id \\ Me.get().id, token, response) do @@ -142,7 +142,7 @@ defmodule Nostrum.Api.Interaction do @doc """ Retrieves the original message of an interaction. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec original_response(Interaction.t()) :: Api.error() | {:ok, Message.t()} def original_response(%Interaction{application_id: application_id, token: token}) do original_response(application_id, token) @@ -151,7 +151,7 @@ defmodule Nostrum.Api.Interaction do @doc """ Retrieves the original message of an interaction. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec original_response(Interaction.t()) :: Api.error() | {:ok, Message.t()} def original_response(id \\ Me.get().id, token) do Api.request(:get, Constants.original_interaction_response(id, token)) diff --git a/lib/nostrum/api/scheduled_event.ex b/lib/nostrum/api/scheduled_event.ex index 6b0f42267..4bf3f8e11 100644 --- a/lib/nostrum/api/scheduled_event.ex +++ b/lib/nostrum/api/scheduled_event.ex @@ -27,7 +27,7 @@ defmodule Nostrum.Api.ScheduledEvent do An optional `reason` can be specified for the audit log. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec create(Guild.id(), AuditLogEntry.reason(), Api.options()) :: {:ok, ScheduledEvent.t()} | Api.error() def create(guild_id, reason \\ nil, options) @@ -54,7 +54,7 @@ defmodule Nostrum.Api.ScheduledEvent do @doc """ Delete a scheduled event for a guild. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec delete(Guild.id(), ScheduledEvent.id()) :: Api.error() | {:ok} def delete(guild_id, event_id) do @@ -64,7 +64,7 @@ defmodule Nostrum.Api.ScheduledEvent do @doc """ Get a scheduled event for a guild. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec get(Guild.id(), ScheduledEvent.id()) :: Api.error() | {:ok, ScheduledEvent.t()} def get(guild_id, event_id) do @@ -82,7 +82,7 @@ defmodule Nostrum.Api.ScheduledEvent do * `:before` (`t:Nostrum.Snowflake.t/0`) return only users before this user id, defaults to `nil` * `:after` (`t:Nostrum.Snowflake.t/0`) return only users after this user id, defaults to `nil` """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec users(Guild.id(), ScheduledEvent.id(), Api.options()) :: Api.error() | {:ok, [ScheduledEvent.User.t()]} def users(guild_id, event_id, params \\ []) do @@ -107,7 +107,7 @@ defmodule Nostrum.Api.ScheduledEvent do * `entity_metadata` with a `location` field must be provided * `scheduled_end_time` must be provided """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec modify( Guild.id(), ScheduledEvent.id(), diff --git a/lib/nostrum/api/sticker.ex b/lib/nostrum/api/sticker.ex index 076a4f874..6fdd0155f 100644 --- a/lib/nostrum/api/sticker.ex +++ b/lib/nostrum/api/sticker.ex @@ -34,7 +34,7 @@ defmodule Nostrum.Api.Sticker do Returns a `t:Nostrum.Struct.Sticker.t/0` on success. """ - @doc since: "0.10.0" + @doc since: "1.x.x" @spec create( Guild.id(), Sticker.name(), @@ -81,7 +81,7 @@ defmodule Nostrum.Api.Sticker do @doc ~S""" Delete a guild sticker with the specified ID. """ - @doc since: "0.10.0" + @doc since: "1.x.x" @spec delete(Guild.id(), Sticker.id()) :: {:ok} | Api.error() def delete(guild_id, sticker_id) do Api.request(:delete, Constants.guild_sticker(guild_id, sticker_id)) @@ -92,7 +92,7 @@ defmodule Nostrum.Api.Sticker do Returns a `t:Nostrum.Struct.Sticker.t/0`. """ - @doc since: "0.10.0" + @doc since: "1.x.x" @spec get(Sticker.id()) :: {:ok, Sticker.t()} | Api.error() def get(sticker_id) do Api.request(:get, Constants.sticker(sticker_id)) @@ -104,7 +104,7 @@ defmodule Nostrum.Api.Sticker do Returns a `t:Nostrum.Struct.Sticker.t/0`. """ - @doc since: "0.10.0" + @doc since: "1.x.x" @spec get(Guild.id(), Sticker.id()) :: Sticker.t() | Api.error() def get(guild_id, sticker_id) do Api.request(:get, Constants.guild_sticker(guild_id, sticker_id)) @@ -116,7 +116,7 @@ defmodule Nostrum.Api.Sticker do Returns a `t:Nostrum.Struct.Sticker.Pack.t/0`. """ - @doc since: "0.11.0" + @doc since: "1.x.x" @spec pack(Snowflake.t()) :: {:ok, Sticker.Pack.t()} | Api.error() def pack(id) do Api.request(:get, Constants.sticker_pack(id)) @@ -126,7 +126,7 @@ defmodule Nostrum.Api.Sticker do @doc ~S""" Get a list of available sticker packs. """ - @doc since: "0.10.0" + @doc since: "1.x.x" @spec packs() :: {:ok, [Sticker.Pack.t()]} | Api.error() def packs do Api.request(:get, Constants.sticker_packs()) @@ -142,7 +142,7 @@ defmodule Nostrum.Api.Sticker do Returns a list of `t:Nostrum.Struct.Sticker.t/0`. """ - @doc since: "0.10.0" + @doc since: "1.x.x" @spec list(Guild.id()) :: {:ok, [Sticker.t()]} | Api.error() def list(guild_id) do Api.request(:get, Constants.guild_stickers(guild_id)) @@ -160,7 +160,7 @@ defmodule Nostrum.Api.Sticker do Returns an updated sticker on update completion. """ - @doc since: "0.10.0" + @doc since: "1.x.x" @spec modify(Guild.id(), Sticker.id(), %{ name: Sticker.name() | nil, description: Sticker.description() | nil, diff --git a/lib/nostrum/api/thread.ex b/lib/nostrum/api/thread.ex index 99bc05f94..8aae6f9d9 100644 --- a/lib/nostrum/api/thread.ex +++ b/lib/nostrum/api/thread.ex @@ -15,7 +15,7 @@ defmodule Nostrum.Api.Thread do @doc """ Add a user to a thread, requires the ability to send messages in the thread. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec add_member(Channel.id(), User.id()) :: {:ok} | Api.error() def add_member(thread_id, user_id) do Api.request(:put, Constants.thread_member(thread_id, user_id)) @@ -24,7 +24,7 @@ defmodule Nostrum.Api.Thread do @doc """ Returns a thread member object for the specified user if they are a member of the thread """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec member(Channel.id(), User.id()) :: {:ok, ThreadMember.t()} | Api.error() def member(thread_id, user_id) do Api.request(:get, Constants.thread_member(thread_id, user_id)) @@ -36,7 +36,7 @@ defmodule Nostrum.Api.Thread do This endpoint is restricted according to whether the `GUILD_MEMBERS` privileged intent is enabled. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec members(Channel.id()) :: {:ok, [ThreadMember.t()]} | Api.error() def members(thread_id) do Api.request(:get, Constants.thread_members(thread_id)) @@ -46,7 +46,7 @@ defmodule Nostrum.Api.Thread do @doc """ Join an existing thread, requires that the thread is not archived. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec join(Channel.id()) :: {:ok} | Api.error() def join(thread_id) do Api.request(:put, Constants.thread_member_me(thread_id)) @@ -55,7 +55,7 @@ defmodule Nostrum.Api.Thread do @doc """ Leave a thread, requires that the thread is not archived. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec leave(Channel.id()) :: {:ok} | Api.error() def leave(thread_id) do Api.request(:delete, Constants.thread_member_me(thread_id)) @@ -68,7 +68,7 @@ defmodule Nostrum.Api.Thread do - `threads`: A list of channel objects. - `members`: A list of `ThreadMember` objects, one for each returned thread the current user has joined. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec list(Guild.id()) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()]}} | Api.error() def list(guild_id) do @@ -93,7 +93,7 @@ defmodule Nostrum.Api.Thread do @doc """ Same as `public_archived_threads/2`, but only returns private threads that the current user has joined. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec joined_private_archived_threads(Channel.id(), Api.options()) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} | Api.error() @@ -140,7 +140,7 @@ defmodule Nostrum.Api.Thread do @doc """ Same as `public_archived_threads/2`, but for private threads instead of public. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec private_archived_threads(Channel.id(), Api.options()) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} | Api.error() @@ -171,7 +171,7 @@ defmodule Nostrum.Api.Thread do - `before`: Returns threads before this timestamp, can be either a `DateTime` or [ISO8601 timestamp](`DateTime.to_iso8601/3`). - `limit`: Optional maximum number of threads to return. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec public_archived_threads(Channel.id(), Api.options()) :: {:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}} | Api.error() @@ -192,7 +192,7 @@ defmodule Nostrum.Api.Thread do Also requires the `MANAGE_THREADS` permission, or the creator of the thread if the thread is private. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec remove_member(Channel.id(), User.id()) :: {:ok} | Api.error() def remove_member(thread_id, user_id) do Api.request(:delete, Constants.thread_member(thread_id, user_id)) @@ -220,7 +220,7 @@ defmodule Nostrum.Api.Thread do - `invitable`: whether non-moderators can add other non-moderators to a thread; only available when creating a private thread defaults to `false`. - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec create(Channel.id(), thread_without_message_params, AuditLogEntry.reason()) :: {:ok, Channel.t()} | Api.error() def create(channel_id, options, reason \\ nil) do @@ -259,7 +259,7 @@ defmodule Nostrum.Api.Thread do At least one of `content`, `embeds`, `sticker_ids`, or `files` must be specified. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec create_in_forum(Channel.id(), map(), AuditLogEntry.reason()) :: {:ok, Channel.t()} | Api.error() def create_in_forum(channel_id, options, reason \\ nil) @@ -319,7 +319,7 @@ defmodule Nostrum.Api.Thread do - `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`. """ - @doc since: "0.5.1" + @doc since: "1.x.x" @spec create_with_message( Channel.id(), Message.id(), diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex index 8b107f227..1974ebfd5 100644 --- a/lib/nostrum/api/webhook.ex +++ b/lib/nostrum/api/webhook.ex @@ -64,7 +64,7 @@ defmodule Nostrum.Api.Webhook do args are the same as `execute/3`, however all fields are optional. """ - @doc since: "0.5.0" + @doc since: "1.x.x" @spec edit_message( Webhook.id(), Webhook.token(), @@ -209,7 +209,7 @@ defmodule Nostrum.Api.Webhook do @doc """ Retrieves the original message of a webhook. """ - @doc since: "0.7.0" + @doc since: "1.x.x" @spec get_message(Webhook.t(), Message.id()) :: Api.error() | {:ok, Message.t()} def get_message(webhook, message_id) do From b2d9789c02e233c5856d874785176d14ede387ac Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Wed, 30 Oct 2024 08:44:39 -0700 Subject: [PATCH 27/42] Add ATTACHMENT application command option type definition --- lib/nostrum/struct/application_command.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nostrum/struct/application_command.ex b/lib/nostrum/struct/application_command.ex index 88c7748c3..a75a947ba 100644 --- a/lib/nostrum/struct/application_command.ex +++ b/lib/nostrum/struct/application_command.ex @@ -54,10 +54,11 @@ defmodule Nostrum.Struct.ApplicationCommand do - `8` for `ROLE` - `9` for `MENTIONABLE` *Note*: Includes users and roles - `10` for `NUMBER` *Note*: This has the same limitations as `​INTEGER` + - `11` for `ATTACHMENT` You may use one of the `Nostrum.Constants.ApplicationCommandOptionType` methods. """ - @type command_option_type :: 1..10 + @type command_option_type :: 1..11 @typedoc """ This defines a command's parameters. Only valid for `CHAT_INPUT` commands. From 6a794033c050a83afa11d038683f08d9b48f2373 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Wed, 30 Oct 2024 08:48:42 -0700 Subject: [PATCH 28/42] Add PRIMARY_ENTRY_POINT constant for application commands --- lib/nostrum/constants.ex | 1 + lib/nostrum/struct/application_command.ex | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/nostrum/constants.ex b/lib/nostrum/constants.ex index d449a48a4..c524a2eb1 100644 --- a/lib/nostrum/constants.ex +++ b/lib/nostrum/constants.ex @@ -301,6 +301,7 @@ defmodule Nostrum.Constants do def chat_input, do: 1 def user, do: 2 def message, do: 3 + def primary_entry_point, do: 4 end defmodule ApplicationCommandOptionType do diff --git a/lib/nostrum/struct/application_command.ex b/lib/nostrum/struct/application_command.ex index a75a947ba..8209c6870 100644 --- a/lib/nostrum/struct/application_command.ex +++ b/lib/nostrum/struct/application_command.ex @@ -124,8 +124,9 @@ defmodule Nostrum.Struct.ApplicationCommand do - `1` for `ROLE` - `2` for `USER` - `3` for `CHANNEL` + - `4` for `PRIMARY_ENTRY_POINT` You can use one of the `Nostrum.Constants.ApplicationCommandPermissionType` methods. """ - @type application_command_permission_type :: 1..3 + @type application_command_permission_type :: 1..4 end From 677e6978830926b76f5c2b4f6ea9518d2b51ec54 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 11:57:03 -0700 Subject: [PATCH 29/42] Use alphabetical ordering of aliases --- lib/nostrum/api/application_command.ex | 6 +++--- lib/nostrum/api/channel.ex | 2 +- lib/nostrum/api/interaction.ex | 4 ++-- lib/nostrum/api/invite.ex | 4 ++-- lib/nostrum/api/message.ex | 2 +- lib/nostrum/api/poll.ex | 4 ++-- lib/nostrum/api/role.ex | 2 +- lib/nostrum/api/self.ex | 4 ++-- lib/nostrum/api/sticker.ex | 6 +++--- lib/nostrum/api/thread.ex | 6 +++--- lib/nostrum/api/user.ex | 2 +- lib/nostrum/api/webhook.ex | 6 +++--- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex index ecd299b1f..196b40f17 100644 --- a/lib/nostrum/api/application_command.ex +++ b/lib/nostrum/api/application_command.ex @@ -1,12 +1,12 @@ defmodule Nostrum.Api.ApplicationCommand do alias Nostrum.Api alias Nostrum.Api.Helpers + alias Nostrum.Cache.Me alias Nostrum.Constants alias Nostrum.Snowflake - alias Nostrum.Cache.Me - alias Nostrum.Struct.User - alias Nostrum.Struct.Guild alias Nostrum.Struct.ApplicationCommand + alias Nostrum.Struct.Guild + alias Nostrum.Struct.User @doc """ Edits command permissions for a specific command for your application in a guild. You can only add up to 10 permission overwrites for a command. diff --git a/lib/nostrum/api/channel.ex b/lib/nostrum/api/channel.ex index 4c915dbd2..3520b9eae 100644 --- a/lib/nostrum/api/channel.ex +++ b/lib/nostrum/api/channel.ex @@ -5,9 +5,9 @@ defmodule Nostrum.Api.Channel do alias Nostrum.Snowflake alias Nostrum.Struct.Channel alias Nostrum.Struct.Guild + alias Nostrum.Struct.Guild.AuditLogEntry alias Nostrum.Struct.Message alias Nostrum.Struct.Webhook - alias Nostrum.Struct.Guild.AuditLogEntry import Nostrum.Snowflake, only: [is_snowflake: 1] diff --git a/lib/nostrum/api/interaction.ex b/lib/nostrum/api/interaction.ex index ff0a1269e..a35f202c7 100644 --- a/lib/nostrum/api/interaction.ex +++ b/lib/nostrum/api/interaction.ex @@ -1,10 +1,10 @@ defmodule Nostrum.Api.Interaction do alias Nostrum.Api alias Nostrum.Api.Helpers - alias Nostrum.Constants + alias Nostrum.Api.Webhook alias Nostrum.Cache.Me + alias Nostrum.Constants alias Nostrum.Struct.Interaction - alias Nostrum.Api.Webhook alias Nostrum.Struct.Message alias Nostrum.Struct.User diff --git a/lib/nostrum/api/invite.ex b/lib/nostrum/api/invite.ex index bf43270eb..c5e0d658f 100644 --- a/lib/nostrum/api/invite.ex +++ b/lib/nostrum/api/invite.ex @@ -2,10 +2,10 @@ defmodule Nostrum.Api.Invite do alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants - alias Nostrum.Struct.Invite - alias Nostrum.Struct.Guild alias Nostrum.Struct.Channel + alias Nostrum.Struct.Guild alias Nostrum.Struct.Guild.AuditLogEntry + alias Nostrum.Struct.Invite import Nostrum.Snowflake, only: [is_snowflake: 1] diff --git a/lib/nostrum/api/message.ex b/lib/nostrum/api/message.ex index 5cb8f250a..3f8e95f65 100644 --- a/lib/nostrum/api/message.ex +++ b/lib/nostrum/api/message.ex @@ -2,9 +2,9 @@ defmodule Nostrum.Api.Message do alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants - alias Nostrum.Struct.Message alias Nostrum.Struct.Channel alias Nostrum.Struct.Emoji + alias Nostrum.Struct.Message alias Nostrum.Struct.User import Nostrum.Snowflake, only: [is_snowflake: 1] diff --git a/lib/nostrum/api/poll.ex b/lib/nostrum/api/poll.ex index 9cdbdfa54..aec0e8bf4 100644 --- a/lib/nostrum/api/poll.ex +++ b/lib/nostrum/api/poll.ex @@ -2,11 +2,11 @@ defmodule Nostrum.Api.Poll do alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants - alias Nostrum.Util - alias Nostrum.Struct.Message alias Nostrum.Struct.Channel + alias Nostrum.Struct.Message alias Nostrum.Struct.Message.Poll.Answer alias Nostrum.Struct.User + alias Nostrum.Util @doc ~S""" Expire (close voting on) a poll before the scheduled end time. diff --git a/lib/nostrum/api/role.ex b/lib/nostrum/api/role.ex index 27cbf0f2a..351cb27f2 100644 --- a/lib/nostrum/api/role.ex +++ b/lib/nostrum/api/role.ex @@ -2,9 +2,9 @@ defmodule Nostrum.Api.Role do alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants + alias Nostrum.Struct.Guild alias Nostrum.Struct.Guild.AuditLogEntry alias Nostrum.Struct.Guild.Role - alias Nostrum.Struct.Guild alias Nostrum.Struct.User import Nostrum.Snowflake, only: [is_snowflake: 1] diff --git a/lib/nostrum/api/self.ex b/lib/nostrum/api/self.ex index 742640821..2de885407 100644 --- a/lib/nostrum/api/self.ex +++ b/lib/nostrum/api/self.ex @@ -2,11 +2,11 @@ defmodule Nostrum.Api.Self do alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants - alias Nostrum.Struct.User - alias Nostrum.Struct.Channel alias Nostrum.Shard.Session alias Nostrum.Shard.Supervisor + alias Nostrum.Struct.Channel alias Nostrum.Struct.Guild + alias Nostrum.Struct.User @doc """ Gets the bot's OAuth2 application info. diff --git a/lib/nostrum/api/sticker.ex b/lib/nostrum/api/sticker.ex index 6fdd0155f..b11cd9a19 100644 --- a/lib/nostrum/api/sticker.ex +++ b/lib/nostrum/api/sticker.ex @@ -1,12 +1,12 @@ defmodule Nostrum.Api.Sticker do alias Nostrum.Api alias Nostrum.Api.Helpers - alias Nostrum.Util - alias Nostrum.Snowflake alias Nostrum.Constants - alias Nostrum.Struct.Sticker + alias Nostrum.Snowflake alias Nostrum.Struct.Guild alias Nostrum.Struct.Guild.AuditLogEntry + alias Nostrum.Struct.Sticker + alias Nostrum.Util @crlf "\r\n" diff --git a/lib/nostrum/api/thread.ex b/lib/nostrum/api/thread.ex index 8aae6f9d9..4b3d920ae 100644 --- a/lib/nostrum/api/thread.ex +++ b/lib/nostrum/api/thread.ex @@ -1,14 +1,14 @@ defmodule Nostrum.Api.Thread do alias Nostrum.Api alias Nostrum.Api.Helpers - alias Nostrum.Util alias Nostrum.Constants alias Nostrum.Struct.Channel alias Nostrum.Struct.Guild alias Nostrum.Struct.Guild.AuditLogEntry - alias Nostrum.Struct.User - alias Nostrum.Struct.ThreadMember alias Nostrum.Struct.Message + alias Nostrum.Struct.ThreadMember + alias Nostrum.Struct.User + alias Nostrum.Util import Api.Helpers, only: [has_files: 1] diff --git a/lib/nostrum/api/user.ex b/lib/nostrum/api/user.ex index c18bc2681..566b1162b 100644 --- a/lib/nostrum/api/user.ex +++ b/lib/nostrum/api/user.ex @@ -2,8 +2,8 @@ defmodule Nostrum.Api.User do alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants - alias Nostrum.Struct.User alias Nostrum.Struct.Channel + alias Nostrum.Struct.User import Nostrum.Snowflake, only: [is_snowflake: 1] diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex index 1974ebfd5..6a037884a 100644 --- a/lib/nostrum/api/webhook.ex +++ b/lib/nostrum/api/webhook.ex @@ -4,12 +4,12 @@ defmodule Nostrum.Api.Webhook do alias Nostrum.Constants alias Nostrum.Snowflake alias Nostrum.Struct.Channel - alias Nostrum.Struct.Webhook + alias Nostrum.Struct.Embed alias Nostrum.Struct.Guild.AuditLogEntry - alias Nostrum.Struct.Message alias Nostrum.Struct.Interaction + alias Nostrum.Struct.Message alias Nostrum.Struct.User - alias Nostrum.Struct.Embed + alias Nostrum.Struct.Webhook @doc """ Creates a webhook. From 81a487006a09e1384319ff1271c6e7fb44b3899d Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 12:17:47 -0700 Subject: [PATCH 30/42] Add module documentation for Discord API modules --- lib/nostrum/api/application_command.ex | 5 +++++ lib/nostrum/api/auto_moderation.ex | 5 +++++ lib/nostrum/api/channel.ex | 5 +++++ lib/nostrum/api/guild.ex | 5 +++++ lib/nostrum/api/interaction.ex | 5 +++++ lib/nostrum/api/invite.ex | 5 +++++ lib/nostrum/api/message.ex | 5 +++++ lib/nostrum/api/poll.ex | 5 +++++ lib/nostrum/api/role.ex | 5 +++++ lib/nostrum/api/scheduled_event.ex | 5 +++++ lib/nostrum/api/self.ex | 5 +++++ lib/nostrum/api/sticker.ex | 5 +++++ lib/nostrum/api/thread.ex | 5 +++++ lib/nostrum/api/user.ex | 5 +++++ lib/nostrum/api/webhook.ex | 5 +++++ 15 files changed, 75 insertions(+) diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex index 196b40f17..951062625 100644 --- a/lib/nostrum/api/application_command.ex +++ b/lib/nostrum/api/application_command.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.ApplicationCommand do + @moduledoc """ + Module for interacting with Discord's application commands. + + See: https://discord.com/developers/docs/interactions/application-commands + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Cache.Me diff --git a/lib/nostrum/api/auto_moderation.ex b/lib/nostrum/api/auto_moderation.ex index a54b28fdf..fe2844dd7 100644 --- a/lib/nostrum/api/auto_moderation.ex +++ b/lib/nostrum/api/auto_moderation.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.AutoModeration do + @moduledoc """ + Functions for interacting with the Discord API's auto-moderation endpoints. + + See: https://discord.com/developers/docs/resources/auto-moderation + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/channel.ex b/lib/nostrum/api/channel.ex index 3520b9eae..e7707d6d2 100644 --- a/lib/nostrum/api/channel.ex +++ b/lib/nostrum/api/channel.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Channel do + @moduledoc """ + Functions for interacting with the Discord API's channel endpoints. + + See: https://discord.com/developers/docs/resources/channel + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex index a8665237a..2fe46be7b 100644 --- a/lib/nostrum/api/guild.ex +++ b/lib/nostrum/api/guild.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Guild do + @moduledoc """ + Functions for interacting with the Discord API's guild endpoints. + + See: https://discord.com/developers/docs/resources/guild + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/interaction.ex b/lib/nostrum/api/interaction.ex index a35f202c7..12e0e4335 100644 --- a/lib/nostrum/api/interaction.ex +++ b/lib/nostrum/api/interaction.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Interaction do + @moduledoc """ + Functions for interacting with the Discord API's interaction endpoints. + + See: https://discord.com/developers/docs/interactions/overview + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Api.Webhook diff --git a/lib/nostrum/api/invite.ex b/lib/nostrum/api/invite.ex index c5e0d658f..ae3ab68f6 100644 --- a/lib/nostrum/api/invite.ex +++ b/lib/nostrum/api/invite.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Invite do + @moduledoc """ + Functions for interacting with the Discord API's invite endpoints. + + See: https://discord.com/developers/docs/resources/invite + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/message.ex b/lib/nostrum/api/message.ex index 3f8e95f65..b64d67f84 100644 --- a/lib/nostrum/api/message.ex +++ b/lib/nostrum/api/message.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Message do + @moduledoc """ + Module for interacting with the Discord API's message endpoints. + + See: https://discord.com/developers/docs/resources/message + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/poll.ex b/lib/nostrum/api/poll.ex index aec0e8bf4..5d11e03e8 100644 --- a/lib/nostrum/api/poll.ex +++ b/lib/nostrum/api/poll.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Poll do + @moduledoc """ + Module for interacting with the Discord API's poll endpoints. + + See: https://discord.com/developers/docs/resources/poll + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/role.ex b/lib/nostrum/api/role.ex index 351cb27f2..a3f480c0d 100644 --- a/lib/nostrum/api/role.ex +++ b/lib/nostrum/api/role.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Role do + @moduledoc """ + Module for interacting with the Discord API's role endpoints. + + See: https://discord.com/developers/docs/resources/guild#get-guild-roles + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/scheduled_event.ex b/lib/nostrum/api/scheduled_event.ex index 4bf3f8e11..16fc45097 100644 --- a/lib/nostrum/api/scheduled_event.ex +++ b/lib/nostrum/api/scheduled_event.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.ScheduledEvent do + @moduledoc """ + Module for interacting with the Discord API's scheduled event endpoints. + + See: https://discord.com/developers/docs/resources/guild-scheduled-event + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/self.ex b/lib/nostrum/api/self.ex index 2de885407..33ccba5d3 100644 --- a/lib/nostrum/api/self.ex +++ b/lib/nostrum/api/self.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Self do + @moduledoc """ + Module for interacting with the current user. + + See the endpoints containing @me in the Discord API documentation: https://discord.com/developers/docs/resources/user + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/sticker.ex b/lib/nostrum/api/sticker.ex index b11cd9a19..c0119330e 100644 --- a/lib/nostrum/api/sticker.ex +++ b/lib/nostrum/api/sticker.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Sticker do + @moduledoc """ + Functions for interacting with the Discord API's sticker endpoints. + + See: https://discord.com/developers/docs/resources/sticker + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/thread.ex b/lib/nostrum/api/thread.ex index 4b3d920ae..0b5e12fd6 100644 --- a/lib/nostrum/api/thread.ex +++ b/lib/nostrum/api/thread.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Thread do + @moduledoc """ + Functions for interacting with the Discord API's thread endpoints. + + Endpoints related to threads in the Discord Channels API: https://discord.com/developers/docs/resources/channel#start-thread-from-message + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/user.ex b/lib/nostrum/api/user.ex index 566b1162b..e623b4511 100644 --- a/lib/nostrum/api/user.ex +++ b/lib/nostrum/api/user.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.User do + @moduledoc """ + Functions for interacting with the Discord API's user endpoints. + + See: https://discord.com/developers/docs/resources/user + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex index 6a037884a..2f9219d29 100644 --- a/lib/nostrum/api/webhook.ex +++ b/lib/nostrum/api/webhook.ex @@ -1,4 +1,9 @@ defmodule Nostrum.Api.Webhook do + @moduledoc """ + Functions for interacting with the Discord API's webhook endpoints. + + See: https://discord.com/developers/docs/resources/webhook + """ alias Nostrum.Api alias Nostrum.Api.Helpers alias Nostrum.Constants From 7081a6de6a9174058b26a9028babea96ec26694f Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 12:18:26 -0700 Subject: [PATCH 31/42] Update docs and allowed trigger types for AutoModerationRule --- lib/nostrum/struct/auto_moderation_rule.ex | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/nostrum/struct/auto_moderation_rule.ex b/lib/nostrum/struct/auto_moderation_rule.ex index d47973d7e..811a3247a 100644 --- a/lib/nostrum/struct/auto_moderation_rule.ex +++ b/lib/nostrum/struct/auto_moderation_rule.ex @@ -51,14 +51,13 @@ defmodule Nostrum.Struct.AutoModerationRule do | value | type | max per guild | description | ---- | ---- | ----- | ----------- - |`1` | `​KEYWORD` | 3 | check if content contains words from a user defined list of keywords - | `2` | `HARMFUL_LINK` | 1 | check if the content contains any harmful links + |`1` | `​KEYWORD` | 6 | check if content contains words from a user defined list of keywords | `3` | `SPAM` | 1 | check if the content represents generic spam - | `4` | `KEYWORD_PRESET `| 1 | check if the content contains a list of discord defined keywords - - note: `HARMFUL_LINK` and `SPAM` are not yet offically released at the time of this writing. + | `4` | `KEYWORD_PRESET` | 1 | check if the content contains a list of discord defined keywords + | `5` | `MENTION_SPAM` | 1 | check if content contains more unique mentions than allowed + | `6` | `MEMBER_PROFILE` | 1 | check if member profile contains words from a user defined list of keywords """ - @type trigger_type :: 1..4 + @type trigger_type :: 1 | 3..6 @typedoc """ Values which represent the different presets defined by Discord @@ -66,7 +65,7 @@ defmodule Nostrum.Struct.AutoModerationRule do | value | type | description | ---- | ---- | ----------- |`1` | `PROFANITY` | Words which may be considered profane - | `2` | `HARMFUL_LINK` | Words that refer to sexually explicit behavior or activity + | `2` | `SEXUAL_CONTENT` | Words that refer to sexually explicit behavior or activity | `3` | `SLURS` | Personal insults or words that may be considered hate speech """ @type preset_values :: 1..3 From 798b8a6e9a2f83baf3c77be0945ac20dd5faa6d3 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 12:18:55 -0700 Subject: [PATCH 32/42] Update docs and allowed action types for AutoModerationRule.Action --- lib/nostrum/struct/auto_moderation_rule/action.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/nostrum/struct/auto_moderation_rule/action.ex b/lib/nostrum/struct/auto_moderation_rule/action.ex index 699983210..1031e11f5 100644 --- a/lib/nostrum/struct/auto_moderation_rule/action.ex +++ b/lib/nostrum/struct/auto_moderation_rule/action.ex @@ -20,8 +20,9 @@ defmodule Nostrum.Struct.AutoModerationRule.Action do |`1` | `BLOCK_MESSAGE` | Blocks the message from being created | `2` | `SEND_ALERT_MESSAGE` | Logs the content of the message in the specified channel | `3` | `TIMEOUT` | timeout a user for a specified duration + | `4` | `BLOCK_MEMBER_INTERACTION` | prevents a member from using text, voice, or other interactions """ - @type action_type :: 1..3 + @type action_type :: 1..4 @type metadata :: ActionMetadata.t() From 888b8e17f2954ed973f13739d1ce4ad507a268ad Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 12:19:12 -0700 Subject: [PATCH 33/42] Update docs naming to match discord api docs --- lib/nostrum/struct/auto_moderation_rule/trigger_metadata.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nostrum/struct/auto_moderation_rule/trigger_metadata.ex b/lib/nostrum/struct/auto_moderation_rule/trigger_metadata.ex index 8a7af1d06..2515da5aa 100644 --- a/lib/nostrum/struct/auto_moderation_rule/trigger_metadata.ex +++ b/lib/nostrum/struct/auto_moderation_rule/trigger_metadata.ex @@ -17,7 +17,7 @@ defmodule Nostrum.Struct.AutoModerationRule.TriggerMetadata do | value | type | description | ---- | ---- | ----------- | `1` | `PROFANITY` | Words which may be considered profane - | `2` | `HARMFUL_LINK` | Words that refer to sexually explicit behavior or activity + | `2` | `SEXUAL_CONTENT` | Words that refer to sexually explicit behavior or activity | `3` | `SLURS` | Personal insults or words that may be considered hate speech """ @type preset_value_metadata :: %__MODULE__{ From 5e1ba95e8d260ccf6b4c776754ab43342d25d3f1 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 12:28:20 -0700 Subject: [PATCH 34/42] Remove bypass and rebuild lockfile --- mix.exs | 3 +-- mix.lock | 9 --------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/mix.exs b/mix.exs index 08c5ce4c3..49cc1bedf 100644 --- a/mix.exs +++ b/mix.exs @@ -153,8 +153,7 @@ defmodule Nostrum.Mixfile do {:credo, "~> 1.7.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, {:benchee, "~> 1.1", only: :dev, runtime: false}, - {:recon, "~> 2.3", only: :dev, optional: true}, - {:bypass, "~> 2.1", only: :test} + {:recon, "~> 2.3", only: :dev, optional: true} ] end diff --git a/mix.lock b/mix.lock index 9fe23cd84..f7b56925d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,12 +1,8 @@ %{ "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, - "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "castle": {:hex, :castle, "0.3.0", "47b1a550b2348a6d7e60e43ded1df19dca601ed21ef6f267c3dbb1b3a301fbf5", [:mix], [{:forecastle, "~> 0.1.0", [hex: :forecastle, repo: "hexpm", optional: false]}], "hexpm", "dbdc1c171520c4591101938a3d342dec70d36b7f5b102a5c138098581e35fcef"}, "certifi": {:hex, :certifi, "2.13.0", "e52be248590050b2dd33b0bb274b56678f9068e67805dca8aa8b1ccdb016bbf6", [:rebar3], [], "hexpm", "8f3d9533a0f06070afdfd5d596b32e21c6580667a492891851b0e2737bc507a1"}, - "cowboy": {:hex, :cowboy, "2.11.0", "356bf784599cf6f2cdc6ad12fdcfb8413c2d35dab58404cf000e1feaed3f5645", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0fa395437f1b0e104e0e00999f39d2ac5f4082ac5049b67a5b6d56ecc31b1403"}, - "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, @@ -24,11 +20,6 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, } From 8cbd7d149c4a04582c23aee7f8eb3bb4ae3fe86d Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 12:34:36 -0700 Subject: [PATCH 35/42] Update voice API calls to use proper module functions --- lib/nostrum/voice.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nostrum/voice.ex b/lib/nostrum/voice.ex index a35d19fd4..12cccd697 100644 --- a/lib/nostrum/voice.ex +++ b/lib/nostrum/voice.ex @@ -161,7 +161,7 @@ defmodule Nostrum.Voice do ) end - Api.update_voice_state(guild_id, channel_id, self_mute, self_deaf) + Api.Self.update_voice_state(guild_id, channel_id, self_mute, self_deaf) end @doc """ @@ -171,7 +171,7 @@ defmodule Nostrum.Voice do """ @spec leave_channel(Guild.id()) :: no_return | :ok def leave_channel(guild_id) do - Api.update_voice_state(guild_id, nil) + Api.Self.update_voice_state(guild_id, nil) end @doc """ From af872413faba87c91af76bcce537c6b261ac1a20 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 12:41:22 -0700 Subject: [PATCH 36/42] alias Self and use in Voice module --- lib/nostrum/voice.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/nostrum/voice.ex b/lib/nostrum/voice.ex index 12cccd697..1ff0ea7cd 100644 --- a/lib/nostrum/voice.ex +++ b/lib/nostrum/voice.ex @@ -33,6 +33,7 @@ defmodule Nostrum.Voice do """ alias Nostrum.Api + alias Nostrum.Api.Self alias Nostrum.Struct.Channel alias Nostrum.Struct.Guild alias Nostrum.Struct.VoiceState @@ -161,7 +162,7 @@ defmodule Nostrum.Voice do ) end - Api.Self.update_voice_state(guild_id, channel_id, self_mute, self_deaf) + Self.update_voice_state(guild_id, channel_id, self_mute, self_deaf) end @doc """ @@ -171,7 +172,7 @@ defmodule Nostrum.Voice do """ @spec leave_channel(Guild.id()) :: no_return | :ok def leave_channel(guild_id) do - Api.Self.update_voice_state(guild_id, nil) + Self.update_voice_state(guild_id, nil) end @doc """ From 2d75ce407545ba9e492626ab3cb22394e92728f5 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 13:20:00 -0700 Subject: [PATCH 37/42] Update references to new API modules in docs and internal functions --- README.md | 4 ++-- examples/audio_player_example.ex | 7 ++++--- examples/cache.ex | 8 +++---- examples/event_consumer.ex | 6 +++--- guides/cheat-sheets/api.cheatmd | 20 +++++++++--------- guides/intro/api.md | 29 +------------------------- guides/intro/application_commands.md | 15 ++++++------- lib/nostrum/api.ex | 6 +++--- lib/nostrum/api/application_command.ex | 2 +- lib/nostrum/api/channel.ex | 2 +- lib/nostrum/api/user.ex | 4 ++-- lib/nostrum/consumer_group.ex | 6 +++--- lib/nostrum/struct/emoji.ex | 8 +++---- lib/nostrum/struct/guild/member.ex | 4 ++-- lib/nostrum/struct/guild/role.ex | 4 ++-- lib/nostrum/struct/message/poll.ex | 4 ++-- lib/nostrum/struct/user.ex | 4 ++-- lib/nostrum/voice.ex | 3 +-- test/nostrum/api/ratelimit_test.exs | 18 ++++++++-------- 19 files changed, 64 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index e4fad0d59..ca4cd840a 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,12 @@ for a full example. defmodule ExampleConsumer do use Nostrum.Consumer - alias Nostrum.Api + alias Nostrum.Api.Message def handle_event({:MESSAGE_CREATE, msg, _ws_state}) do case msg.content do "ping!" -> - Api.create_message(msg.channel_id, "I copy and pasted this code") + Message.create(msg.channel_id, "I copy and pasted this code") _ -> :ignore end diff --git a/examples/audio_player_example.ex b/examples/audio_player_example.ex index 9193134df..26e6f5502 100644 --- a/examples/audio_player_example.ex +++ b/examples/audio_player_example.ex @@ -18,7 +18,8 @@ end defmodule AudioPlayerConsumer do use Nostrum.Consumer - alias Nostrum.Api + alias Nostrum.Api.ApplicationCommand + alias Nostrum.Api.Interaction alias Nostrum.Cache.GuildCache alias Nostrum.Voice @@ -66,7 +67,7 @@ defmodule AudioPlayerConsumer do # with your guild_id as the argument def create_guild_commands(guild_id) do Enum.each(@commands, fn {name, description, options} -> - Api.create_guild_application_command(guild_id, %{ + ApplicationCommand.create_guild_command(guild_id, %{ name: name, description: description, options: options @@ -88,7 +89,7 @@ defmodule AudioPlayerConsumer do _ -> ":white_check_mark:" end - Api.create_interaction_response(interaction, %{type: 4, data: %{content: message}}) + Interaction.create_response(interaction, %{type: 4, data: %{content: message}}) end def handle_event({:VOICE_SPEAKING_UPDATE, payload, _ws_state}) do diff --git a/examples/cache.ex b/examples/cache.ex index f43c462a5..641adbb3a 100644 --- a/examples/cache.ex +++ b/examples/cache.ex @@ -31,7 +31,7 @@ end defmodule ExampleCommands do import Nostrum.Snowflake, only: [is_snowflake: 1] - alias Nostrum.Api + alias Nostrum.Api.Message alias Nostrum.Cache.{GuildCache, UserCache} alias Nostrum.Struct.User @@ -68,7 +68,7 @@ defmodule ExampleCommands do get_cached_with_fallback(user_id, &UserCache.get/1, &Api.get_user/1), {:ok, %{name: guild_name}} <- get_cached_with_fallback(guild_id, &GuildCache.get/1, &Api.get_guild/1) do - Api.create_message( + Message.create( channel_id, """ ID #{message_user_id} belongs to: #{User.full_name(user)} @@ -79,12 +79,12 @@ defmodule ExampleCommands do # Since we have multiple failure patterns from the combination of `Integer.parse/2` # and `is_snowflake/1`, we'll use the identifier as the term to match on instead. {_invalid_id, :parse_id} -> - Api.create_message(channel_id, "Make sure you entered a valid User ID") + Message.create(channel_id, "Make sure you entered a valid User ID") # The cache + API failure case. For now, lets just return the stringified failure # reason of whatever the API returned. Up to you if you want to make it all nice and pretty. {:error, reason} -> - Api.create_message(channel_id, "Failed to retrieve all required info: #{inspect(reason)}") + Message.create(channel_id, "Failed to retrieve all required info: #{inspect(reason)}") end end diff --git a/examples/event_consumer.ex b/examples/event_consumer.ex index cf53e2466..8501740d3 100644 --- a/examples/event_consumer.ex +++ b/examples/event_consumer.ex @@ -16,17 +16,17 @@ end defmodule ExampleConsumer do use Nostrum.Consumer - alias Nostrum.Api + alias Nostrum.Api.Message def handle_event({:MESSAGE_CREATE, msg, _ws_state}) do case msg.content do "!sleep" -> - Api.create_message(msg.channel_id, "Going to sleep...") + Message.create(msg.channel_id, "Going to sleep...") # This won't stop other events from being handled. Process.sleep(3000) "!ping" -> - Api.create_message(msg.channel_id, "pyongyang!") + Message.create(msg.channel_id, "pyongyang!") "!raise" -> # This won't crash the entire Consumer. diff --git a/guides/cheat-sheets/api.cheatmd b/guides/cheat-sheets/api.cheatmd index 773f4d918..66ebe3139 100644 --- a/guides/cheat-sheets/api.cheatmd +++ b/guides/cheat-sheets/api.cheatmd @@ -16,7 +16,7 @@ UTC time is: #{DateTime.to_iso8601(utc_now)} Atom table size is: #{atom_count} """ -Nostrum.Api.create_message(msg.channel_id, content) +Nostrum.Api.Message.create(msg.channel_id, content) ``` ### Sending a message with an embed @@ -35,7 +35,7 @@ embed = # set inline attribute to true |> put_field("Field 2", "More test", true) -Nostrum.Api.create_message(msg.channel_id, embeds: [embed]) +Nostrum.Api.Message.create(msg.channel_id, embeds: [embed]) ``` You can look at the documentation in `m:Nostrum.Struct.Embed#module-using-structs` for more advanced usage. @@ -43,7 +43,7 @@ You can look at the documentation in `m:Nostrum.Struct.Embed#module-using-struct ### Upload an attachment ```elixir -Nostrum.Api.create_message( +Nostrum.Api.Message.create( msg.channel_id, files: [ # file from filesystem @@ -59,7 +59,7 @@ Nostrum.Api.create_message( With a mention: ```elixir -Nostrum.Api.create_message( +Nostrum.Api.Message.create( msg.channel_id, content: "Hello!", message_reference: %{message_id: msg.id} @@ -69,7 +69,7 @@ Nostrum.Api.create_message( Without a mention: ```elixir -Nostrum.Api.create_message( +Nostrum.Api.Message.create( msg.channel_id, content: "Hello!", message_reference: %{message_id: msg.id}, @@ -88,14 +88,14 @@ poll = Poll.create_poll( |> Poll.put_answer("Yes!", default_emoji: "\u2705") |> Poll.put_answer("No!", default_emoji: "\u274C") -Api.create_message(channel_id, poll: poll) +Nostrum.Api.Message.create(channel_id, poll: poll) ``` ### React to a message Using a default emoji (unicode representation): ```elixir -Nostrum.Api.create_reaction( +Nostrum.Api.Message.react( msg.channel_id, msg.id, "👾" @@ -109,7 +109,7 @@ emoji = %Nostrum.Struct.Emoji{ id: 1228698654022434866 } -Nostrum.Api.create_reaction(msg.channel_id, msg.id, emoji) +Nostrum.Api.Message.react(msg.channel_id, msg.id, emoji) ``` ## Miscellaneous @@ -118,7 +118,7 @@ Nostrum.Api.create_reaction(msg.channel_id, msg.id, emoji) ### Update the bot status ```elixir -Nostrum.Api.update_status( +Nostrum.Api.Self.update_status( :dnd, "craigs cats", 3 # Watching status @@ -131,7 +131,7 @@ You can also update a single shard with `Nostrum.Api.update_shard_status/5`. ```elixir image = "data:image/png;base64,..." -Nostrum.Api.create_guild_emoji( +Nostrum.Api.Guild.create_emoji( msg.guild_id, name: "nostrum", image: image diff --git a/guides/intro/api.md b/guides/intro/api.md index 1b6e7ff4d..200ebd8e2 100644 --- a/guides/intro/api.md +++ b/guides/intro/api.md @@ -5,34 +5,7 @@ Discord's API. Method names are copied closely from the documentation to eliminate any confusion about what a method does, as well as to allow users to easily lookup the endpoints in the official API documentation. -For a full listing of method definitions, please see the `Nostrum.Api` module. - - -## Banged (`!`) API methods - -A lot of methods have a `banged` version of themselves. This is a common Elixir -idiom hailing from Elixir's style of failing fast. - -By default, the API method will return a tuple like one of the following: - -```elixir -# Success -{:ok, msg} = Nostrum.Api.create_message(179679229036724225, "456") - -# Failure -{:error, reason} = Nostrum.Api.create_message(123, "eat my shorts api") -``` - -A banged method, instead of returning an `error` tuple, will throw an error. If -successful, it will directly return the response with no `:ok` tuple. - -```elixir -# Success -msg = Nostrum.Api.create_message!(179679229036724225, "456") - -# Failure - Throws an error -Nostrum.Api.create_message!(123, "eat my shorts api") -``` +For a listing of method definitions, please see the submodules of `Nostrum.Api`. ## Abstractions diff --git a/guides/intro/application_commands.md b/guides/intro/application_commands.md index aa6cb9f82..4a70c53b3 100644 --- a/guides/intro/application_commands.md +++ b/guides/intro/application_commands.md @@ -62,10 +62,10 @@ command = %{ ``` To register this command on the guild, we simply pass it to -`Nostrum.Api.create_guild_application_command/2`: +`Nostrum.Api.ApplicationCommand.create_guild_command/2`: ```elixir -Nostrum.Api.create_guild_application_command(guild_id, command) +Nostrum.Api.ApplicationCommand.create_guild_command(guild_id, command) ``` You can register the command in the ``:READY`` gateway event handler. @@ -97,14 +97,15 @@ separate operation modes: ```elixir alias Nostrum.Api +alias Nostrum.Api.Role alias Nostrum.Struct.Interaction defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "assign"}]}} = interaction) do - Api.add_guild_member_role(interaction.guild_id, interaction.member.user_id, role_id) + Role.add_member(interaction.guild_id, interaction.member.user_id, role_id) end defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "remove"}]}} = interaction) do - Api.remove_guild_member_role(interaction.guild_id, interaction.member.user_id, role_id) + Role.remove_member(interaction.guild_id, interaction.member.user_id, role_id) end def handle_event({:INTERACTION_CREATE, %Interaction{data: %{name: "role"}} = interaction, _ws_state}) do @@ -118,18 +119,18 @@ that you would use for regular commands. ## Responding to interactions -To respond to interactions, use ``Nostrum.Api.create_interaction_response/2``: +To respond to interactions, use ``Nostrum.Api.Interaction.create_response/2``: ```elixir defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "assign"}]}} = interaction) do - Api.add_guild_member_role(interaction.guild_id, interaction.member.user_id, role_id) + Role.add_member(interaction.guild_id, interaction.member.user_id, role_id) response = %{ type: 4, # ChannelMessageWithSource data: %{ content: "role assigned" } } - Api.create_interaction_response(interaction, response) + Api.Interaction.create_response(interaction, response) end ``` diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index adca09ff0..052fae3f7 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -10,14 +10,14 @@ defmodule Nostrum.Api do ```elixir # Async Task t = Task.async fn -> - Nostrum.Api.get_channel_messages(12345678912345, :infinity, {}) + Nostrum.Api.Channel.messages(12345678912345, :infinity, {}) end messages = Task.await t # A lot of times we don't care about the return value of the function Task.start fn -> messages = ["in", "the", "end", "it", "doesn't", "even", "matter"] - Enum.each messages, &Nostrum.Api.create_message!(12345678912345, &1) + Enum.each messages, &Nostrum.Api.Message.create(12345678912345, &1) end ``` @@ -31,7 +31,7 @@ defmodule Nostrum.Api do **Example** ```elixir - messages = Nostrum.Api.get_pinned_messages!(12345678912345) + messages = Nostrum.Api.Channel.pinned_messages!(12345678912345) authors = Enum.map messages, fn msg -> diff --git a/lib/nostrum/api/application_command.ex b/lib/nostrum/api/application_command.ex index 951062625..531e2f837 100644 --- a/lib/nostrum/api/application_command.ex +++ b/lib/nostrum/api/application_command.ex @@ -142,7 +142,7 @@ defmodule Nostrum.Api.ApplicationCommand do ## Example ```elixir - Nostrum.Api.create_global_command( + Nostrum.Api.ApplicationCommand.create_global_command( %{name: "edit", description: "ed, man! man, ed", options: []} ) ``` diff --git a/lib/nostrum/api/channel.ex b/lib/nostrum/api/channel.ex index e7707d6d2..a6c52da82 100644 --- a/lib/nostrum/api/channel.ex +++ b/lib/nostrum/api/channel.ex @@ -29,7 +29,7 @@ defmodule Nostrum.Api.Channel do ## Examples ```elixir - Nostrum.Api.pin_message(43189401384091, 18743893102394) + Nostrum.Api.Channel.pin_message(43189401384091, 18743893102394) ``` """ @spec pin_message(Channel.id(), Message.id()) :: Api.error() | {:ok} diff --git a/lib/nostrum/api/user.ex b/lib/nostrum/api/user.ex index e623b4511..b4324635c 100644 --- a/lib/nostrum/api/user.ex +++ b/lib/nostrum/api/user.ex @@ -20,7 +20,7 @@ defmodule Nostrum.Api.User do ## Examples ```elixir - Nostrum.Api.create_dm(150061853001777154) + Nostrum.Api.User.create_dm(150061853001777154) {:ok, %Nostrum.Struct.Channel{type: 1}} ``` """ @@ -41,7 +41,7 @@ defmodule Nostrum.Api.User do ## Examples ```elixir - Nostrum.Api.create_group_dm(["6qrZcUqja7812RVdnEKjpzOL4CvHBFG"], %{41771983423143937 => "My Nickname"}) + Nostrum.Api.User.create_group_dm(["6qrZcUqja7812RVdnEKjpzOL4CvHBFG"], %{41771983423143937 => "My Nickname"}) {:ok, %Nostrum.Struct.Channel{type: 3}} ``` """ diff --git a/lib/nostrum/consumer_group.ex b/lib/nostrum/consumer_group.ex index 43745eb3b..14c352a3b 100644 --- a/lib/nostrum/consumer_group.ex +++ b/lib/nostrum/consumer_group.ex @@ -92,14 +92,14 @@ defmodule Nostrum.ConsumerGroup do alias Nostrum.Struct.User def command(%Message{author: %User{id: author_id}}) do - Api.create_message!(msg, "Reply 'y' in 5 seconds to confirm ordering a large burger menu.") + Api.Message.create(msg, "Reply 'y' in 5 seconds to confirm ordering a large burger menu.") ConsumerGroup.join() receive do {:event, {:MESSAGE_CREATE, %Message{author: %User{id: author_id}, content: "y"}, _}} -> - Api.create_message!(msg, "The large burger menu is coming.") + Api.Message.create(msg, "The large burger menu is coming.") after 5_000 -> - Api.create_message!(msg, "Too slow!") + Api.Message.create(msg, "Too slow!") end end end diff --git a/lib/nostrum/struct/emoji.ex b/lib/nostrum/struct/emoji.ex index 22fc7a310..37f829df6 100644 --- a/lib/nostrum/struct/emoji.ex +++ b/lib/nostrum/struct/emoji.ex @@ -9,11 +9,11 @@ defmodule Nostrum.Struct.Emoji do ```elixir emoji = %Nostrum.Struct.Emoji{id: 437093487582642177, name: "foxbot"} - Nostrum.Api.create_message!(184046599834435585, "#{emoji}") + Nostrum.Api.Message.create(184046599834435585, "#{emoji}") %Nostrum.Struct.Message{content: "<:foxbot:437093487582642177>"} emoji = %Nostrum.Struct.Emoji{id: 436885297037312001, name: "tealixir"} - Nostrum.Api.create_message!(280085880452939778, "#{Nostrum.Struct.Emoji.mention(emoji)}") + Nostrum.Api.Message.create(280085880452939778, "#{Nostrum.Struct.Emoji.mention(emoji)}") %Nostrum.Struct.Message{content: "<:tealixir:436885297037312001>"} ``` @@ -24,11 +24,11 @@ defmodule Nostrum.Struct.Emoji do ```elixir emoji = %Nostrum.Struct.Emoji{id: 436885297037312001, name: "tealixir"} - Nostrum.Api.create_reaction(381889573426429952, 436247584349356032, Nostrum.Struct.Emoji.api_name(emoji)) + Nostrum.Api.Message.react(381889573426429952, 436247584349356032, Nostrum.Struct.Emoji.api_name(emoji)) {:ok} emoji = %Nostrum.Struct.Emoji{id: 436189601820966923, name: "elixir"} - Nostrum.Api.create_reaction(381889573426429952, 436247584349356032, emoji) + Nostrum.Api.Message.react(381889573426429952, 436247584349356032, emoji) {:ok} ``` diff --git a/lib/nostrum/struct/guild/member.ex b/lib/nostrum/struct/guild/member.ex index 0701b478b..783a8af54 100644 --- a/lib/nostrum/struct/guild/member.ex +++ b/lib/nostrum/struct/guild/member.ex @@ -12,11 +12,11 @@ defmodule Nostrum.Struct.Guild.Member do ```elixir member = %Nostrum.Struct.Guild.Member{user_id: 120571255635181568} - Nostrum.Api.create_message!(184046599834435585, "#{member}") + Nostrum.Api.Message.create(184046599834435585, "#{member}") %Nostrum.Struct.Message{content: "<@120571255635181568>"} member = %Nostrum.Struct.Guild.Member{user_id: 89918932789497856} - Nostrum.Api.create_message!(280085880452939778, "#{Nostrum.Struct.Guild.Member.mention(member)}") + Nostrum.Api.Message.create(280085880452939778, "#{Nostrum.Struct.Guild.Member.mention(member)}") %Nostrum.Struct.Message{content: "<@89918932789497856>"} ``` """ diff --git a/lib/nostrum/struct/guild/role.ex b/lib/nostrum/struct/guild/role.ex index 511360b8a..48758486f 100644 --- a/lib/nostrum/struct/guild/role.ex +++ b/lib/nostrum/struct/guild/role.ex @@ -9,11 +9,11 @@ defmodule Nostrum.Struct.Guild.Role do ```elixir role = %Nostrum.Struct.Guild.Role{id: 431886897539973131} - Nostrum.Api.create_message!(184046599834435585, "#{role}") + Nostrum.Api.Message.create(184046599834435585, "#{role}") %Nostrum.Struct.Message{} role = %Nostrum.Struct.Guild.Role{id: 431884023535632398} - Nostrum.Api.create_message!(280085880452939778, "#{Nostrum.Struct.Guild.Role.mention(role)}") + Nostrum.Api.Message.create(280085880452939778, "#{Nostrum.Struct.Guild.Role.mention(role)}") %Nostrum.Struct.Message{} ``` """ diff --git a/lib/nostrum/struct/message/poll.ex b/lib/nostrum/struct/message/poll.ex index 8748bdd31..03a14bfc0 100644 --- a/lib/nostrum/struct/message/poll.ex +++ b/lib/nostrum/struct/message/poll.ex @@ -90,7 +90,7 @@ defmodule Nostrum.Struct.Message.Poll do @doc ~S""" Create a new poll struct. - Use `Nostrum.Api.create_message` to send it once you've populated it. + Use `Nostrum.Api.Message.create/2` to send it once you've populated it. Accepts a `question_text` parameter which is the string to use as the poll title. @@ -107,7 +107,7 @@ defmodule Nostrum.Struct.Message.Poll do |> Poll.put_answer("Yes!", default_emoji: "\u2705") # check mark emoji |> Poll.put_answer("No!", default_emoji: "\u274C") # cross emoji - Api.create_message(channel_id, poll: poll) + Nostrum.Api.Message.create(channel_id, poll: poll) ``` """ @spec create_poll(String.t(), duration: duration, allow_multiselect: allow_multiselect) :: t() diff --git a/lib/nostrum/struct/user.ex b/lib/nostrum/struct/user.ex index 8fd111e92..b61e74e1f 100644 --- a/lib/nostrum/struct/user.ex +++ b/lib/nostrum/struct/user.ex @@ -9,11 +9,11 @@ defmodule Nostrum.Struct.User do ```elixir user = %Nostrum.Struct.User{id: 120571255635181568} - Nostrum.Api.create_message!(184046599834435585, "#{user}") + Nostrum.Api.Message.create(184046599834435585, "#{user}") %Nostrum.Struct.Message{content: "<@120571255635181568>"} user = %Nostrum.Struct.User{id: 89918932789497856} - Nostrum.Api.create_message!(280085880452939778, "#{Nostrum.Struct.User.mention(user)}") + Nostrum.Api.Message.create(280085880452939778, "#{Nostrum.Struct.User.mention(user)}") %Nostrum.Struct.Message{content: "<@89918932789497856>"} ``` diff --git a/lib/nostrum/voice.ex b/lib/nostrum/voice.ex index 1ff0ea7cd..8ea04707f 100644 --- a/lib/nostrum/voice.ex +++ b/lib/nostrum/voice.ex @@ -32,7 +32,6 @@ defmodule Nostrum.Voice do - Send packets on your own time using `send_frames/2` """ - alias Nostrum.Api alias Nostrum.Api.Self alias Nostrum.Struct.Channel alias Nostrum.Struct.Guild @@ -168,7 +167,7 @@ defmodule Nostrum.Voice do @doc """ Leaves the voice channel of the given guild id. - This function is equivalent to calling `Nostrum.Api.update_voice_state(guild_id, nil)`. + This function is equivalent to calling `Nostrum.Api.Self.update_voice_state(guild_id, nil)`. """ @spec leave_channel(Guild.id()) :: no_return | :ok def leave_channel(guild_id) do diff --git a/test/nostrum/api/ratelimit_test.exs b/test/nostrum/api/ratelimit_test.exs index 5f47edac2..64cea137a 100644 --- a/test/nostrum/api/ratelimit_test.exs +++ b/test/nostrum/api/ratelimit_test.exs @@ -51,8 +51,8 @@ defmodule Nostrum.Api.RatelimitTest do 1..2 |> Task.async_stream( fn _ -> - with {:ok, _} <- Nostrum.Api.get_user(first), - {:ok, _} <- Nostrum.Api.get_user(second) do + with {:ok, _} <- Nostrum.Api.User.get(first), + {:ok, _} <- Nostrum.Api.User.get(second) do :ok else o -> @@ -69,7 +69,7 @@ defmodule Nostrum.Api.RatelimitTest do @tag disabled: true test "one route sync no 429" do - result = Enum.map(1..10, fn x -> Nostrum.Api.create_message(@test_channel, "#{x}") end) + result = Enum.map(1..10, fn x -> Nostrum.Api.Message.create(@test_channel, "#{x}") end) assert Enum.all?(result, fn x -> elem(x, 0) == :ok end) == true end @@ -77,7 +77,7 @@ defmodule Nostrum.Api.RatelimitTest do test "one route async no 429" do responses = 1..11 - |> Task.async_stream(&Nostrum.Api.create_message(@test_channel, "#{&1}"), timeout: 50000) + |> Task.async_stream(&Nostrum.Api.Message.create(@test_channel, "#{&1}"), timeout: 50000) |> Enum.to_list() assert Enum.all?(responses, fn {_, {k, _}} -> k == :ok end) == true @@ -88,7 +88,7 @@ defmodule Nostrum.Api.RatelimitTest do responses = 1..5 |> Task.async_stream( - fn _ -> Nostrum.Api.get_pinned_messages(@test_channel) end, + fn _ -> Nostrum.Api.Channel.pinned_messages(@test_channel) end, timeout: 50000 ) |> Enum.to_list() @@ -112,10 +112,10 @@ defmodule Nostrum.Api.RatelimitTest do 1..10 |> Task.async_stream( fn x -> - with {:ok, _} <- Nostrum.Api.get_guild(@test_guild), - {:ok, _} <- Nostrum.Api.create_message(@test_channel, "#{x}"), - {:ok, _} <- Nostrum.Api.get_channel_message(@test_channel, @test_message), - {:ok} <- Nostrum.Api.start_typing(@test_channel) do + with {:ok, _} <- Nostrum.Api.Guild.get(@test_guild), + {:ok, _} <- Nostrum.Api.Message.create(@test_channel, "#{x}"), + {:ok, _} <- Nostrum.Api.Message.get(@test_channel, @test_message), + {:ok} <- Nostrum.Api.Channel.start_typing(@test_channel) do :ok else _ -> From fceb566fe15038a825a8f96151602ee53de82f19 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Fri, 1 Nov 2024 13:23:26 -0700 Subject: [PATCH 38/42] Update references to new API modules in docs --- guides/intro/api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/intro/api.md b/guides/intro/api.md index 200ebd8e2..d451ff7ca 100644 --- a/guides/intro/api.md +++ b/guides/intro/api.md @@ -11,7 +11,7 @@ For a listing of method definitions, please see the submodules of `Nostrum.Api`. ## Abstractions When appropriate, some helpers are defined to make interacting with the API -simpler. An example of this is `Nostrum.Api.get_channel_messages/3`. By default +simpler. An example of this is `Nostrum.Api.Channel.messages/3`. By default this endpoint only allows the retrieval of `100` messages at a time. A general use case will have a user wanting more messages than that, thus nostrum handles the retrieval of any number of messages for the user. @@ -35,7 +35,7 @@ asynchronously or not, nostrum funnels all requests through the If you only want to use the REST portion of the provided API, the only process needed is the ratelimiter, which can be manually started by calling -`Nostrum.Api.Ratelimiter.start_link/1`. +`Nostrum.Api.Ratelimiter.start_link/1`. If you don't want to start nostrum, you can add `runtime: false` to the dependency options. If you're using `mix release`, all `runtime: false` deps From 3315284f61b154a8f0962839491791ed712a5642 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 5 Nov 2024 10:19:10 -0800 Subject: [PATCH 39/42] Move helper functions out of Nostrum.Api module --- lib/nostrum/api.ex | 119 +++-------------------------- lib/nostrum/api/channel.ex | 8 +- lib/nostrum/api/guild.ex | 20 ++--- lib/nostrum/api/helpers.ex | 101 ++++++++++++++++++++++++ lib/nostrum/api/interaction.ex | 6 +- lib/nostrum/api/invite.ex | 2 +- lib/nostrum/api/message.ex | 12 +-- lib/nostrum/api/role.ex | 10 +-- lib/nostrum/api/scheduled_event.ex | 4 +- lib/nostrum/api/sticker.ex | 4 +- lib/nostrum/api/thread.ex | 16 ++-- lib/nostrum/api/webhook.ex | 16 ++-- 12 files changed, 163 insertions(+), 155 deletions(-) diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 052fae3f7..8bd4df76d 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -47,6 +47,7 @@ defmodule Nostrum.Api do import Nostrum.Api.Helpers, only: [has_files: 1] + alias Nostrum.Api.Helpers alias Nostrum.Api.Ratelimiter alias Nostrum.Cache.Me @@ -2082,26 +2083,13 @@ defmodule Nostrum.Api do to: Nostrum.Api.AutoModeration, as: :delete_rule - @spec maybe_add_reason(String.t() | nil) :: list() - def maybe_add_reason(reason) do - maybe_add_reason(reason, [{"content-type", "application/json"}]) - end - - @spec maybe_add_reason(String.t() | nil, list()) :: list() - def maybe_add_reason(nil, headers) do - headers - end - - def maybe_add_reason(reason, headers) do - [{"x-audit-log-reason", reason} | headers] - end - @spec request(map()) :: {:ok} | {:ok, String.t()} | error def request(request) do Ratelimiter.queue(request) end - @spec request(atom(), String.t(), any, keyword() | map()) :: {:ok} | {:ok, String.t()} | error + @spec request(atom(), String.t(), any, keyword() | map()) :: + {:ok} | {:ok, String.t()} | error def request(method, route, body \\ "", params \\ []) def request(method, route, %{} = body, params) when has_files(body), @@ -2124,8 +2112,12 @@ defmodule Nostrum.Api do @spec request_multipart(atom(), String.t(), any, keyword() | map()) :: {:ok} | {:ok, String.t()} | error def request_multipart(method, route, body, params \\ []) do - boundary = generate_boundary() - {files, body} = combine_files(body) |> pop_files() + boundary = Helpers.generate_boundary() + + {files, body} = + Helpers.combine_files(body) + |> Helpers.pop_files() + json = Jason.encode_to_iodata!(body) %{ @@ -2141,45 +2133,11 @@ defmodule Nostrum.Api do |> request() end - # If `:embed` is present, prepend to `:embeds` for compatibility - def combine_embeds(%{embed: embed} = args), - do: Map.delete(args, :embed) |> Map.put(:embeds, [embed | args[:embeds] || []]) - - def combine_embeds(%{data: data} = args), do: %{args | data: combine_embeds(data)} - def combine_embeds(%{message: data} = args), do: %{args | message: combine_embeds(data)} - def combine_embeds(args), do: args - - # If `:file` is present, prepend to `:files` for compatibility - def combine_files(%{file: file} = args), - do: Map.delete(args, :file) |> Map.put(:files, [file | args[:files] || []]) - - def combine_files(%{data: data} = args), do: %{args | data: combine_files(data)} - def combine_files(%{message: data} = args), do: %{args | message: combine_files(data)} - def combine_files(args), do: args - - def pop_files(%{data: data} = args), - do: {data.files, %{args | data: Map.delete(data, :files)}} - - def pop_files(%{message: data} = args), - do: {data.files, %{args | message: Map.delete(data, :files)}} - - def pop_files(args), do: Map.pop!(args, :files) - @doc false def bangify({:error, error}), do: raise(error) def bangify({:ok, body}), do: body def bangify({:ok}), do: {:ok} - def prepare_allowed_mentions(options) do - with raw_options when raw_options != :all <- Map.get(options, :allowed_mentions, :all), - allowed_mentions when is_map(allowed_mentions) <- parse_allowed_mentions(raw_options) do - Map.put(options, :allowed_mentions, allowed_mentions) - else - _ -> - Map.delete(options, :allowed_mentions) - end - end - def create_multipart(files, json, boundary) do json_mime = MIME.type("json") json_size = :erlang.iolist_size(json) @@ -2229,63 +2187,4 @@ defmodule Nostrum.Api do end defp get_file_contents(%{body: body, name: name}), do: {body, name} - - def generate_boundary do - String.duplicate("-", 20) <> - "KraigieNostrumCat_" <> - Base.encode16(:crypto.strong_rand_bytes(10)) - end - - defp parse_allowed_mentions(:none), do: %{parse: []} - defp parse_allowed_mentions(:everyone), do: %{parse: [:everyone]} - - # Parse users - defp parse_allowed_mentions(:users), do: %{parse: [:users]} - defp parse_allowed_mentions({:users, users}) when is_list(users), do: %{users: users} - - # Parse roles - defp parse_allowed_mentions(:roles), do: %{parse: [:roles]} - defp parse_allowed_mentions({:roles, roles}) when is_list(roles), do: %{roles: roles} - - # Parse many - defp parse_allowed_mentions(options) when is_list(options) or is_map(options) do - options - |> Enum.map(&parse_allowed_mentions/1) - |> Enum.reduce(fn a, b -> - Map.merge(a, b, fn - key, parse_a, parse_b when key in [:parse, :users, :roles] -> - Enum.uniq(parse_a ++ parse_b) - - _k, _v1, v2 -> - v2 - end) - end) - |> Map.put_new(:parse, []) - end - - # ignore - defp parse_allowed_mentions(options), do: options - - @spec maybe_convert_date_time(options(), atom()) :: options() - def maybe_convert_date_time(options, key) when is_map(options) do - case options do - %{^key => %DateTime{} = date_time} -> - timestamp = DateTime.to_iso8601(date_time) - %{options | key => timestamp} - - _ -> - options - end - end - - def maybe_convert_date_time(options, key) when is_list(options) do - case Keyword.get(options, key) do - %DateTime{} = date_time -> - timestamp = DateTime.to_iso8601(date_time) - Keyword.put(options, key, timestamp) - - _ -> - options - end - end end diff --git a/lib/nostrum/api/channel.ex b/lib/nostrum/api/channel.ex index a6c52da82..aa13dc392 100644 --- a/lib/nostrum/api/channel.ex +++ b/lib/nostrum/api/channel.ex @@ -156,7 +156,7 @@ defmodule Nostrum.Api.Channel do route: Constants.channel(channel_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Channel}) @@ -175,7 +175,7 @@ defmodule Nostrum.Api.Channel do route: Constants.channel_permission(channel_id, overwrite_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -228,7 +228,7 @@ defmodule Nostrum.Api.Channel do route: Constants.channel_permission(channel_id, overwrite_id), body: permission_info, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -396,7 +396,7 @@ defmodule Nostrum.Api.Channel do route: Constants.channel(channel_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Channel}) diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex index 2fe46be7b..7c3deb56b 100644 --- a/lib/nostrum/api/guild.ex +++ b/lib/nostrum/api/guild.ex @@ -95,7 +95,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_prune(guild_id), body: "", params: [days: days], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode() @@ -115,7 +115,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_ban(guild_id, user_id), body: %{delete_message_days: days_to_delete}, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -159,7 +159,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_emojis(guild_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Emoji}) @@ -220,7 +220,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_emoji(guild_id, emoji_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -567,7 +567,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild(guild_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Guild}) @@ -631,7 +631,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_emoji(guild_id, emoji_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Emoji}) @@ -701,7 +701,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_member(guild_id, user_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) |> Helpers.handle_request_with_decode({:struct, Member}) end @@ -734,7 +734,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_roles(guild_id), body: positions, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:list, {:struct, Role}}) @@ -762,7 +762,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_ban(guild_id, user_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -791,7 +791,7 @@ defmodule Nostrum.Api.Guild do route: Constants.guild_member(guild_id, user_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end diff --git a/lib/nostrum/api/helpers.ex b/lib/nostrum/api/helpers.ex index 64ab3bdb9..08dd4ec1d 100644 --- a/lib/nostrum/api/helpers.ex +++ b/lib/nostrum/api/helpers.ex @@ -22,4 +22,105 @@ defmodule Nostrum.Api.Helpers do {:ok, convert} end + + @spec maybe_add_reason(String.t() | nil, list()) :: list() + def maybe_add_reason(reason, headers \\ []) + def maybe_add_reason(nil, headers), do: headers + + def maybe_add_reason(reason, headers) do + [{"x-audit-log-reason", reason} | headers] + end + + @spec maybe_convert_date_time(keyword | map, atom()) :: keyword | map + def maybe_convert_date_time(options, key) when is_map(options) do + case options do + %{^key => %DateTime{} = date_time} -> + timestamp = DateTime.to_iso8601(date_time) + %{options | key => timestamp} + + _ -> + options + end + end + + def maybe_convert_date_time(options, key) when is_list(options) do + case Keyword.get(options, key) do + %DateTime{} = date_time -> + timestamp = DateTime.to_iso8601(date_time) + Keyword.put(options, key, timestamp) + + _ -> + options + end + end + + # If `:embed` is present, prepend to `:embeds` for compatibility + def combine_embeds(%{embed: embed} = args), + do: Map.delete(args, :embed) |> Map.put(:embeds, [embed | args[:embeds] || []]) + + def combine_embeds(%{data: data} = args), do: %{args | data: combine_embeds(data)} + def combine_embeds(%{message: data} = args), do: %{args | message: combine_embeds(data)} + def combine_embeds(args), do: args + + # If `:file` is present, prepend to `:files` for compatibility + def combine_files(%{file: file} = args), + do: Map.delete(args, :file) |> Map.put(:files, [file | args[:files] || []]) + + def combine_files(%{data: data} = args), do: %{args | data: combine_files(data)} + def combine_files(%{message: data} = args), do: %{args | message: combine_files(data)} + def combine_files(args), do: args + + def pop_files(%{data: data} = args), + do: {data.files, %{args | data: Map.delete(data, :files)}} + + def pop_files(%{message: data} = args), + do: {data.files, %{args | message: Map.delete(data, :files)}} + + def pop_files(args), do: Map.pop!(args, :files) + + def generate_boundary do + String.duplicate("-", 20) <> + "KraigieNostrumCat_" <> + Base.encode16(:crypto.strong_rand_bytes(10)) + end + + def prepare_allowed_mentions(options) do + with raw_options when raw_options != :all <- Map.get(options, :allowed_mentions, :all), + allowed_mentions when is_map(allowed_mentions) <- parse_allowed_mentions(raw_options) do + Map.put(options, :allowed_mentions, allowed_mentions) + else + _ -> + Map.delete(options, :allowed_mentions) + end + end + + defp parse_allowed_mentions(:none), do: %{parse: []} + defp parse_allowed_mentions(:everyone), do: %{parse: [:everyone]} + + # Parse users + defp parse_allowed_mentions(:users), do: %{parse: [:users]} + defp parse_allowed_mentions({:users, users}) when is_list(users), do: %{users: users} + + # Parse roles + defp parse_allowed_mentions(:roles), do: %{parse: [:roles]} + defp parse_allowed_mentions({:roles, roles}) when is_list(roles), do: %{roles: roles} + + # Parse many + defp parse_allowed_mentions(options) when is_list(options) or is_map(options) do + options + |> Enum.map(&parse_allowed_mentions/1) + |> Enum.reduce(fn a, b -> + Map.merge(a, b, fn + key, parse_a, parse_b when key in [:parse, :users, :roles] -> + Enum.uniq(parse_a ++ parse_b) + + _k, _v1, v2 -> + v2 + end) + end) + |> Map.put_new(:parse, []) + end + + # ignore + defp parse_allowed_mentions(options), do: options end diff --git a/lib/nostrum/api/interaction.ex b/lib/nostrum/api/interaction.ex index 12e0e4335..af5d0876e 100644 --- a/lib/nostrum/api/interaction.ex +++ b/lib/nostrum/api/interaction.ex @@ -72,7 +72,8 @@ defmodule Nostrum.Api.Interaction do Api.request( :post, Constants.interaction_callback(id, token), - Api.combine_embeds(response) |> Api.combine_files() + Helpers.combine_embeds(response) + |> Helpers.combine_files() ) end @@ -139,7 +140,8 @@ defmodule Nostrum.Api.Interaction do Api.request( :patch, Constants.interaction_callback_original(id, token), - Api.combine_embeds(response) |> Api.combine_files() + Helpers.combine_embeds(response) + |> Helpers.combine_files() ) |> Helpers.handle_request_with_decode({:struct, Message}) end diff --git a/lib/nostrum/api/invite.ex b/lib/nostrum/api/invite.ex index ae3ab68f6..1aa68905d 100644 --- a/lib/nostrum/api/invite.ex +++ b/lib/nostrum/api/invite.ex @@ -122,7 +122,7 @@ defmodule Nostrum.Api.Invite do route: Constants.channel_invites(channel_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Invite}) diff --git a/lib/nostrum/api/message.ex b/lib/nostrum/api/message.ex index b64d67f84..7e7670c02 100644 --- a/lib/nostrum/api/message.ex +++ b/lib/nostrum/api/message.ex @@ -102,9 +102,9 @@ defmodule Nostrum.Api.Message do def create(channel_id, options) when is_snowflake(channel_id) and is_map(options) do prepared_options = - Api.prepare_allowed_mentions(options) - |> Api.combine_embeds() - |> Api.combine_files() + Helpers.prepare_allowed_mentions(options) + |> Helpers.combine_embeds() + |> Helpers.combine_files() Api.request(:post, Constants.channel_messages(channel_id), prepared_options) |> Helpers.handle_request_with_decode({:struct, Message}) @@ -304,9 +304,9 @@ defmodule Nostrum.Api.Message do def edit(channel_id, message_id, %{} = options) when is_snowflake(channel_id) and is_snowflake(message_id) do prepared_options = - Api.prepare_allowed_mentions(options) - |> Api.combine_embeds() - |> Api.combine_files() + Helpers.prepare_allowed_mentions(options) + |> Helpers.combine_embeds() + |> Helpers.combine_files() Api.request(:patch, Constants.channel_message(channel_id, message_id), prepared_options) |> Helpers.handle_request_with_decode({:struct, Message}) diff --git a/lib/nostrum/api/role.ex b/lib/nostrum/api/role.ex index a3f480c0d..fb93f78f6 100644 --- a/lib/nostrum/api/role.ex +++ b/lib/nostrum/api/role.ex @@ -29,7 +29,7 @@ defmodule Nostrum.Api.Role do route: Constants.guild_member_role(guild_id, user_id, role_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -71,7 +71,7 @@ defmodule Nostrum.Api.Role do route: Constants.guild_roles(guild_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Role}) @@ -101,7 +101,7 @@ defmodule Nostrum.Api.Role do route: Constants.guild_role(guild_id, role_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -143,7 +143,7 @@ defmodule Nostrum.Api.Role do route: Constants.guild_role(guild_id, role_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Role}) @@ -164,7 +164,7 @@ defmodule Nostrum.Api.Role do route: Constants.guild_member_role(guild_id, user_id, role_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end end diff --git a/lib/nostrum/api/scheduled_event.ex b/lib/nostrum/api/scheduled_event.ex index 16fc45097..fa71f4afd 100644 --- a/lib/nostrum/api/scheduled_event.ex +++ b/lib/nostrum/api/scheduled_event.ex @@ -51,7 +51,7 @@ defmodule Nostrum.Api.ScheduledEvent do route: Constants.guild_scheduled_events(guild_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) |> Helpers.handle_request_with_decode({:struct, ScheduledEvent}) end @@ -135,7 +135,7 @@ defmodule Nostrum.Api.ScheduledEvent do route: Constants.guild_scheduled_event(guild_id, event_id), body: prepared_options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) |> Helpers.handle_request_with_decode({:struct, ScheduledEvent}) end diff --git a/lib/nostrum/api/sticker.ex b/lib/nostrum/api/sticker.ex index c0119330e..d42391c8a 100644 --- a/lib/nostrum/api/sticker.ex +++ b/lib/nostrum/api/sticker.ex @@ -55,12 +55,12 @@ defmodule Nostrum.Api.Sticker do tags: tags } - boundary = Api.generate_boundary() + boundary = Helpers.generate_boundary() multipart = Api.create_multipart([], Jason.encode_to_iodata!(opts), boundary) headers = - Api.maybe_add_reason(reason, [ + Helpers.maybe_add_reason(reason, [ {"content-type", "multipart/form-data; boundary=#{boundary}"} ]) diff --git a/lib/nostrum/api/thread.ex b/lib/nostrum/api/thread.ex index 0b5e12fd6..bfaa875e5 100644 --- a/lib/nostrum/api/thread.ex +++ b/lib/nostrum/api/thread.ex @@ -234,7 +234,7 @@ defmodule Nostrum.Api.Thread do route: Constants.thread_without_message(channel_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) |> Helpers.handle_request_with_decode({:struct, Channel}) end @@ -272,12 +272,16 @@ defmodule Nostrum.Api.Thread do def create_in_forum(channel_id, %{message: data} = body, reason) when has_files(data) do # done this way to avoid breaking changes to support audit log reasons in multipart requests - boundary = Api.generate_boundary() - {files, json} = Api.combine_files(body) |> Api.pop_files() + boundary = Helpers.generate_boundary() + + {files, json} = + Helpers.combine_files(body) + |> Helpers.pop_files() + body = Jason.encode_to_iodata!(json) headers = - Api.maybe_add_reason(reason, [ + Helpers.maybe_add_reason(reason, [ {"content-type", "multipart/form-data; boundary=#{boundary}"} ]) @@ -298,7 +302,7 @@ defmodule Nostrum.Api.Thread do route: Constants.thread_without_message(channel_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) |> Helpers.handle_request_with_decode({:struct, Channel}) end @@ -338,7 +342,7 @@ defmodule Nostrum.Api.Thread do route: Constants.thread_with_message(channel_id, message_id), body: options, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) |> Helpers.handle_request_with_decode({:struct, Channel}) end diff --git a/lib/nostrum/api/webhook.ex b/lib/nostrum/api/webhook.ex index 2f9219d29..c944cd28c 100644 --- a/lib/nostrum/api/webhook.ex +++ b/lib/nostrum/api/webhook.ex @@ -40,7 +40,7 @@ defmodule Nostrum.Api.Webhook do route: Constants.webhooks_channel(channel_id), body: args, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode() @@ -60,7 +60,7 @@ defmodule Nostrum.Api.Webhook do route: Constants.webhook(webhook_id), body: "", params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) }) end @@ -81,7 +81,8 @@ defmodule Nostrum.Api.Webhook do Api.request( :patch, Constants.webhook_message_edit(webhook_id, webhook_token, message_id), - Api.combine_embeds(args) |> Api.combine_files() + Helpers.combine_embeds(args) + |> Helpers.combine_files() ) |> Helpers.handle_request_with_decode({:struct, Message}) end @@ -183,7 +184,7 @@ defmodule Nostrum.Api.Webhook do def execute(webhook_id, webhook_token, args, wait) do {thread_id, args} = Map.pop(args, :thread_id) - args = Api.prepare_allowed_mentions(args) + args = Helpers.prepare_allowed_mentions(args) params = if is_nil(thread_id), @@ -193,7 +194,8 @@ defmodule Nostrum.Api.Webhook do Api.request( :post, Constants.webhook_token(webhook_id, webhook_token), - Api.combine_embeds(args) |> Api.combine_files(), + Helpers.combine_embeds(args) + |> Helpers.combine_files(), params ) |> Helpers.handle_request_with_decode({:struct, Message}) @@ -263,7 +265,7 @@ defmodule Nostrum.Api.Webhook do route: Constants.webhook(webhook_id), body: args, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode({:struct, Webhook}) @@ -298,7 +300,7 @@ defmodule Nostrum.Api.Webhook do route: Constants.webhook_token(webhook_id, webhook_token), body: args, params: [], - headers: Api.maybe_add_reason(reason) + headers: Helpers.maybe_add_reason(reason) } |> Api.request() |> Helpers.handle_request_with_decode() From 4eb1a42b5e1200e57cace4d05ea6b2433062a4ed Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 5 Nov 2024 10:56:05 -0800 Subject: [PATCH 40/42] Properly reference `maybe_convert_date_time` --- lib/nostrum/api/guild.ex | 2 +- lib/nostrum/api/scheduled_event.ex | 8 ++++---- lib/nostrum/api/thread.ex | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex index 7c3deb56b..1f72dfe1b 100644 --- a/lib/nostrum/api/guild.ex +++ b/lib/nostrum/api/guild.ex @@ -694,7 +694,7 @@ defmodule Nostrum.Api.Guild do when is_snowflake(guild_id) and is_snowflake(user_id) do options = options - |> Api.maybe_convert_date_time(:communication_disabled_until) + |> Helpers.maybe_convert_date_time(:communication_disabled_until) Api.request(%{ method: :patch, diff --git a/lib/nostrum/api/scheduled_event.ex b/lib/nostrum/api/scheduled_event.ex index fa71f4afd..ca28e8819 100644 --- a/lib/nostrum/api/scheduled_event.ex +++ b/lib/nostrum/api/scheduled_event.ex @@ -43,8 +43,8 @@ defmodule Nostrum.Api.ScheduledEvent do def create(guild_id, reason, %{} = options) do options = options - |> Api.maybe_convert_date_time(:scheduled_start_time) - |> Api.maybe_convert_date_time(:scheduled_end_time) + |> Helpers.maybe_convert_date_time(:scheduled_start_time) + |> Helpers.maybe_convert_date_time(:scheduled_end_time) Api.request(%{ method: :post, @@ -127,8 +127,8 @@ defmodule Nostrum.Api.ScheduledEvent do def modify(guild_id, event_id, reason, options) when is_map(options) do prepared_options = options - |> Api.maybe_convert_date_time(:scheduled_start_time) - |> Api.maybe_convert_date_time(:scheduled_end_time) + |> Helpers.maybe_convert_date_time(:scheduled_start_time) + |> Helpers.maybe_convert_date_time(:scheduled_end_time) Api.request(%{ method: :patch, diff --git a/lib/nostrum/api/thread.ex b/lib/nostrum/api/thread.ex index bfaa875e5..f5a4a82c0 100644 --- a/lib/nostrum/api/thread.ex +++ b/lib/nostrum/api/thread.ex @@ -115,7 +115,7 @@ defmodule Nostrum.Api.Thread do end defp list_archived_threads(route, options) do - options = options |> Api.maybe_convert_date_time(:before) + options = options |> Helpers.maybe_convert_date_time(:before) res = Api.request(%{ From 58440b8ebf5de4eeb56021bf51abb2fb4b31440e Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Tue, 5 Nov 2024 11:10:32 -0800 Subject: [PATCH 41/42] Use proper alias for ConsumerGroup example --- lib/nostrum/consumer_group.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/nostrum/consumer_group.ex b/lib/nostrum/consumer_group.ex index 14c352a3b..0f24aa7a1 100644 --- a/lib/nostrum/consumer_group.ex +++ b/lib/nostrum/consumer_group.ex @@ -86,20 +86,20 @@ defmodule Nostrum.ConsumerGroup do ```elixir defmodule MyBot.Command - alias Nostrum.Api + alias Nostrum.Api.Message alias Nostrum.ConsumerGroup alias Nostrum.Struct.Message alias Nostrum.Struct.User def command(%Message{author: %User{id: author_id}}) do - Api.Message.create(msg, "Reply 'y' in 5 seconds to confirm ordering a large burger menu.") + Message.create(msg, "Reply 'y' in 5 seconds to confirm ordering a large burger menu.") ConsumerGroup.join() receive do {:event, {:MESSAGE_CREATE, %Message{author: %User{id: author_id}, content: "y"}, _}} -> - Api.Message.create(msg, "The large burger menu is coming.") + Message.create(msg, "The large burger menu is coming.") after 5_000 -> - Api.Message.create(msg, "Too slow!") + Message.create(msg, "Too slow!") end end end From d4058b83935586aba1831cfd856dfda340b2a719 Mon Sep 17 00:00:00 2001 From: Kyle Boe Date: Mon, 11 Nov 2024 20:51:51 -0800 Subject: [PATCH 42/42] Move Role API endpoints to Guild module Also updates relevant docs --- guides/intro/application_commands.md | 7 +- lib/nostrum/api.ex | 30 ++--- lib/nostrum/api/guild.ex | 155 ++++++++++++++++++++++++ lib/nostrum/api/role.ex | 170 --------------------------- 4 files changed, 173 insertions(+), 189 deletions(-) delete mode 100644 lib/nostrum/api/role.ex diff --git a/guides/intro/application_commands.md b/guides/intro/application_commands.md index 4a70c53b3..9452c8070 100644 --- a/guides/intro/application_commands.md +++ b/guides/intro/application_commands.md @@ -97,15 +97,14 @@ separate operation modes: ```elixir alias Nostrum.Api -alias Nostrum.Api.Role alias Nostrum.Struct.Interaction defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "assign"}]}} = interaction) do - Role.add_member(interaction.guild_id, interaction.member.user_id, role_id) + Guild.add_member_role(interaction.guild_id, interaction.member.user_id, role_id) end defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "remove"}]}} = interaction) do - Role.remove_member(interaction.guild_id, interaction.member.user_id, role_id) + Guild.remove_member_role(interaction.guild_id, interaction.member.user_id, role_id) end def handle_event({:INTERACTION_CREATE, %Interaction{data: %{name: "role"}} = interaction, _ws_state}) do @@ -123,7 +122,7 @@ To respond to interactions, use ``Nostrum.Api.Interaction.create_response/2``: ```elixir defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "assign"}]}} = interaction) do - Role.add_member(interaction.guild_id, interaction.member.user_id, role_id) + Guild.add_member_role(interaction.guild_id, interaction.member.user_id, role_id) response = %{ type: 4, # ChannelMessageWithSource data: %{ diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex index 8bd4df76d..2d1c47bce 100644 --- a/lib/nostrum/api.ex +++ b/lib/nostrum/api.ex @@ -1014,19 +1014,19 @@ defmodule Nostrum.Api do @deprecated """ Calling `Nostrum.Api` functions directly will be removed in v1.0 - Use `Nostrum.Api.Role.add_member/4` directly instead. + Use `Nostrum.Api.Guild.add_member_role/4` directly instead. """ defdelegate add_guild_member_role(guild_id, user_id, role_id, reason \\ nil), - to: Nostrum.Api.Role, - as: :add_member + to: Nostrum.Api.Guild, + as: :add_member_role @deprecated """ Calling `Nostrum.Api` functions directly will be removed in v1.0 - Use `Nostrum.Api.Role.remove_member/4` directly instead. + Use `Nostrum.Api.Guild.remove_member_role/4` directly instead. """ defdelegate remove_guild_member_role(guild_id, user_id, role_id, reason \\ nil), - to: Nostrum.Api.Role, - as: :remove_member + to: Nostrum.Api.Guild, + as: :remove_member_role @deprecated """ Calling `Nostrum.Api` functions directly will be removed in v1.0 @@ -1098,11 +1098,11 @@ defmodule Nostrum.Api do @deprecated """ Calling `Nostrum.Api` functions directly will be removed in v1.0 - Use `Nostrum.Api.Role.create/3` directly instead. + Use `Nostrum.Api.Guild.create_role/3` directly instead. """ defdelegate create_guild_role(guild_id, options, reason \\ nil), - to: Nostrum.Api.Role, - as: :create + to: Nostrum.Api.Guild, + as: :create_role @doc ~S""" Same as `create_guild_role/2`, but raises `Nostrum.Error.ApiError` in case of failure. @@ -1138,11 +1138,11 @@ defmodule Nostrum.Api do @deprecated """ Calling `Nostrum.Api` functions directly will be removed in v1.0 - Use `Nostrum.Api.Role.modify/4` directly instead. + Use `Nostrum.Api.Guild.modify_role/4` directly instead. """ defdelegate modify_guild_role(guild_id, role_id, options, reason \\ nil), - to: Nostrum.Api.Role, - as: :modify + to: Nostrum.Api.Guild, + as: :modify_role @doc ~S""" Same as `modify_guild_role/3`, but raises `Nostrum.Error.ApiError` in case of failure. @@ -1157,11 +1157,11 @@ defmodule Nostrum.Api do @deprecated """ Calling `Nostrum.Api` functions directly will be removed in v1.0 - Use `Nostrum.Api.Role.delete/3` directly instead. + Use `Nostrum.Api.Guild.delete_role/3` directly instead. """ defdelegate delete_guild_role(guild_id, role_id, reason \\ nil), - to: Nostrum.Api.Role, - as: :delete + to: Nostrum.Api.Guild, + as: :delete_role @doc ~S""" Same as `delete_guild_role/2`, but raises `Nostrum.Error.ApiError` in case of failure. diff --git a/lib/nostrum/api/guild.ex b/lib/nostrum/api/guild.ex index 1f72dfe1b..f6477a5a3 100644 --- a/lib/nostrum/api/guild.ex +++ b/lib/nostrum/api/guild.ex @@ -69,6 +69,25 @@ defmodule Nostrum.Api.Guild do |> Helpers.handle_request_with_decode({:struct, Member}) end + @doc """ + Adds a role to a member. + + Role to add is specified by `role_id`. + User to add role to is specified by `guild_id` and `user_id`. + An optional `reason` can be given for the audit log. + """ + @spec add_member_role(Guild.id(), User.id(), Role.id(), AuditLogEntry.reason()) :: + Api.error() | {:ok} + def add_member_role(guild_id, user_id, role_id, reason \\ nil) do + Api.request(%{ + method: :put, + route: Constants.guild_member_role(guild_id, user_id, role_id), + body: "", + params: [], + headers: Helpers.maybe_add_reason(reason) + }) + end + @doc """ Begins a guild prune to prune members within `days`. @@ -182,6 +201,51 @@ defmodule Nostrum.Api.Guild do Api.request(:post, Constants.guild_integrations(guild_id), options) end + @doc ~S""" + Creates a guild role. + + An optional reason for the audit log can be provided via `reason`. + + This endpoint requires the `MANAGE_ROLES` permission. It fires a + `t:Nostrum.Consumer.guild_role_create/0` event. + + If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - name of the role (default: "new role") + * `:permissions` (integer) - bitwise of the enabled/disabled permissions (default: @everyone perms) + * `:color` (integer) - RGB color value (default: 0) + * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar (default: false) + * `:mentionable` (boolean) - whether the role should be mentionable (default: false) + * `:icon` (string) - URL role icon (default: `nil`) + * `:unicode_emoji` (string) - standard unicode character emoji role icon (default: `nil`) + + ## Examples + + ```elixir + Nostrum.Api.Guild.create_role(41771983423143937, name: "nostrum-club", hoist: true) + ``` + """ + @spec create_role(Guild.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Role.t()} + def create_role(guild_id, options, reason \\ nil) + + def create_role(guild_id, options, reason) when is_list(options), + do: create_role(guild_id, Map.new(options), reason) + + def create_role(guild_id, %{} = options, reason) when is_snowflake(guild_id) do + %{ + method: :post, + route: Constants.guild_roles(guild_id), + body: options, + params: [], + headers: Helpers.maybe_add_reason(reason) + } + |> Api.request() + |> Helpers.handle_request_with_decode({:struct, Role}) + end + @doc ~S""" Deletes a guild. @@ -234,6 +298,34 @@ defmodule Nostrum.Api.Guild do Api.request(:delete, Constants.guild_integration(guild_id, integration_id)) end + @doc ~S""" + Deletes a role from a guild. + + An optional `reason` can be specified for the audit log. + + This endpoint requires the `MANAGE_ROLES` permission. It fires a + `t:Nostrum.Consumer.guild_role_delete/0` event. + + If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Examples + + ```elixir + Nostrum.Api.Guild.delete_role(41771983423143937, 392817238471936) + ``` + """ + @spec delete_role(Guild.id(), Role.id(), AuditLogEntry.reason()) :: Api.error() | {:ok} + def delete_role(guild_id, role_id, reason \\ nil) + when is_snowflake(guild_id) and is_snowflake(role_id) do + Api.request(%{ + method: :delete, + route: Constants.guild_role(guild_id, role_id), + body: "", + params: [], + headers: Helpers.maybe_add_reason(reason) + }) + end + @doc ~S""" Gets a guild. @@ -369,6 +461,69 @@ defmodule Nostrum.Api.Guild do |> Helpers.handle_request_with_decode() end + @doc ~S""" + Modifies a guild role. + + This endpoint requires the `MANAGE_ROLES` permission. It fires a + `t:Nostrum.Consumer.guild_role_update/0` event. + + An optional `reason` can be specified for the audit log. + + If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. + + ## Options + + * `:name` (string) - name of the role + * `:permissions` (integer) - bitwise of the enabled/disabled permissions + * `:color` (integer) - RGB color value (default: 0) + * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar + * `:mentionable` (boolean) - whether the role should be mentionable + + ## Examples + + ```elixir + Nostrum.Api.Guild.modify_role(41771983423143937, 392817238471936, hoist: false, name: "foo-bar") + ``` + """ + @spec modify_role(Guild.id(), Role.id(), Api.options(), AuditLogEntry.reason()) :: + Api.error() | {:ok, Role.t()} + def modify_role(guild_id, role_id, options, reason \\ nil) + + def modify_role(guild_id, role_id, options, reason) when is_list(options), + do: modify_role(guild_id, role_id, Map.new(options), reason) + + def modify_role(guild_id, role_id, %{} = options, reason) + when is_snowflake(guild_id) and is_snowflake(role_id) do + %{ + method: :patch, + route: Constants.guild_role(guild_id, role_id), + body: options, + params: [], + headers: Helpers.maybe_add_reason(reason) + } + |> Api.request() + |> Helpers.handle_request_with_decode({:struct, Role}) + end + + @doc """ + Removes a role from a member. + + Role to remove is specified by `role_id`. + User to remove role from is specified by `guild_id` and `user_id`. + An optional `reason` can be given for the audit log. + """ + @spec remove_member_role(Guild.id(), User.id(), Role.id(), AuditLogEntry.reason()) :: + Api.error() | {:ok} + def remove_member_role(guild_id, user_id, role_id, reason \\ nil) do + Api.request(%{ + method: :delete, + route: Constants.guild_member_role(guild_id, user_id, role_id), + body: "", + params: [], + headers: Helpers.maybe_add_reason(reason) + }) + end + @doc ~S""" Gets a guild's roles. diff --git a/lib/nostrum/api/role.ex b/lib/nostrum/api/role.ex deleted file mode 100644 index fb93f78f6..000000000 --- a/lib/nostrum/api/role.ex +++ /dev/null @@ -1,170 +0,0 @@ -defmodule Nostrum.Api.Role do - @moduledoc """ - Module for interacting with the Discord API's role endpoints. - - See: https://discord.com/developers/docs/resources/guild#get-guild-roles - """ - alias Nostrum.Api - alias Nostrum.Api.Helpers - alias Nostrum.Constants - alias Nostrum.Struct.Guild - alias Nostrum.Struct.Guild.AuditLogEntry - alias Nostrum.Struct.Guild.Role - alias Nostrum.Struct.User - - import Nostrum.Snowflake, only: [is_snowflake: 1] - - @doc """ - Adds a role to a member. - - Role to add is specified by `role_id`. - User to add role to is specified by `guild_id` and `user_id`. - An optional `reason` can be given for the audit log. - """ - @spec add_member(Guild.id(), User.id(), Role.id(), AuditLogEntry.reason()) :: - Api.error() | {:ok} - def add_member(guild_id, user_id, role_id, reason \\ nil) do - Api.request(%{ - method: :put, - route: Constants.guild_member_role(guild_id, user_id, role_id), - body: "", - params: [], - headers: Helpers.maybe_add_reason(reason) - }) - end - - @doc ~S""" - Creates a guild role. - - An optional reason for the audit log can be provided via `reason`. - - This endpoint requires the `MANAGE_ROLES` permission. It fires a - `t:Nostrum.Consumer.guild_role_create/0` event. - - If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - name of the role (default: "new role") - * `:permissions` (integer) - bitwise of the enabled/disabled permissions (default: @everyone perms) - * `:color` (integer) - RGB color value (default: 0) - * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar (default: false) - * `:mentionable` (boolean) - whether the role should be mentionable (default: false) - * `:icon` (string) - URL role icon (default: `nil`) - * `:unicode_emoji` (string) - standard unicode character emoji role icon (default: `nil`) - - ## Examples - - ```elixir - Nostrum.Api.Role.create(41771983423143937, name: "nostrum-club", hoist: true) - ``` - """ - @spec create(Guild.id(), Api.options(), AuditLogEntry.reason()) :: Api.error() | {:ok, Role.t()} - def create(guild_id, options, reason \\ nil) - - def create(guild_id, options, reason) when is_list(options), - do: create(guild_id, Map.new(options), reason) - - def create(guild_id, %{} = options, reason) when is_snowflake(guild_id) do - %{ - method: :post, - route: Constants.guild_roles(guild_id), - body: options, - params: [], - headers: Helpers.maybe_add_reason(reason) - } - |> Api.request() - |> Helpers.handle_request_with_decode({:struct, Role}) - end - - @doc ~S""" - Deletes a role from a guild. - - An optional `reason` can be specified for the audit log. - - This endpoint requires the `MANAGE_ROLES` permission. It fires a - `t:Nostrum.Consumer.guild_role_delete/0` event. - - If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Examples - - ```elixir - Nostrum.Api.Role.delete(41771983423143937, 392817238471936) - ``` - """ - @spec delete(Guild.id(), Role.id(), AuditLogEntry.reason()) :: Api.error() | {:ok} - def delete(guild_id, role_id, reason \\ nil) - when is_snowflake(guild_id) and is_snowflake(role_id) do - Api.request(%{ - method: :delete, - route: Constants.guild_role(guild_id, role_id), - body: "", - params: [], - headers: Helpers.maybe_add_reason(reason) - }) - end - - @doc ~S""" - Modifies a guild role. - - This endpoint requires the `MANAGE_ROLES` permission. It fires a - `t:Nostrum.Consumer.guild_role_update/0` event. - - An optional `reason` can be specified for the audit log. - - If successful, returns `{:ok, role}`. Otherwise, returns a `t:Nostrum.Api.error/0`. - - ## Options - - * `:name` (string) - name of the role - * `:permissions` (integer) - bitwise of the enabled/disabled permissions - * `:color` (integer) - RGB color value (default: 0) - * `:hoist` (boolean) - whether the role should be displayed separately in the sidebar - * `:mentionable` (boolean) - whether the role should be mentionable - - ## Examples - - ```elixir - Nostrum.Api.Role.modify(41771983423143937, 392817238471936, hoist: false, name: "foo-bar") - ``` - """ - @spec modify(Guild.id(), Role.id(), Api.options(), AuditLogEntry.reason()) :: - Api.error() | {:ok, Role.t()} - def modify(guild_id, role_id, options, reason \\ nil) - - def modify(guild_id, role_id, options, reason) when is_list(options), - do: modify(guild_id, role_id, Map.new(options), reason) - - def modify(guild_id, role_id, %{} = options, reason) - when is_snowflake(guild_id) and is_snowflake(role_id) do - %{ - method: :patch, - route: Constants.guild_role(guild_id, role_id), - body: options, - params: [], - headers: Helpers.maybe_add_reason(reason) - } - |> Api.request() - |> Helpers.handle_request_with_decode({:struct, Role}) - end - - @doc """ - Removes a role from a member. - - Role to remove is specified by `role_id`. - User to remove role from is specified by `guild_id` and `user_id`. - An optional `reason` can be given for the audit log. - """ - @spec remove_member(Guild.id(), User.id(), Role.id(), AuditLogEntry.reason()) :: - Api.error() | {:ok} - def remove_member(guild_id, user_id, role_id, reason \\ nil) do - Api.request(%{ - method: :delete, - route: Constants.guild_member_role(guild_id, user_id, role_id), - body: "", - params: [], - headers: Helpers.maybe_add_reason(reason) - }) - end -end