Repository: Kraigie/nostrum
Pull request #348 (2021-10-12 20:56:47)

Steps towards 0.5-rc
- [x] Update docs to use application commands instead of slash commands
- [x] Replace `:poison` with `:jason` as the JSON library
- [x] Update `@spec` for some Api functions to be more descriptive than `map()` 
- [x] Inject `member.user` into `:user` on the `Interaction` struct
- [x] Document slash command autocomplete

Maybe?
- [x] Update all calls to `Jason.encode` to instead be `Jason.encode_to_iodata` and modify `@spec` accordingly
- [x] Rework `Nostrum.Api.create_multipart_body` etc to use iolists

Copied from the docs for Jason:
>This function should be preferred to `encode/2`, if the generated JSON will be handed over to one of the IO functions or sent over the socket. The Erlang runtime is able to leverage vectorised writes and avoid allocating a continuous buffer for the whole resulting string, lowering memory use and increasing performance.



Patch:
diff --git a/docs/static/Slash commands.md b/docs/static/Application Commands.md
similarity index 91%
rename from docs/static/Slash commands.md
rename to docs/static/Application Commands.md
index 57b9b5eb..1a318860 100644
--- a/docs/static/Slash commands.md
+++ b/docs/static/Application Commands.md
@@ -1,18 +1,18 @@
-# Slash commands
+# Application commands
 
 Discord allows you to create commands for your bot that work within the slash
-command menu.
+command menu or via the context menu.
 
-Using nostrum, you can create, receive, and respond to slash commands invoked
+Using nostrum, you can create, receive, and respond to application commands invoked
 by Discord users.
 
 ## Permissions required
 
 Quoting the [official Discord
-documentation](https://discord.com/developers/docs/interactions/slash-commands#interaction):
+documentation](https://discord.com/developers/docs/interactions/application-commands#authorizing-your-application):
 
-> In order to make Slash Commands work within a guild, the guild must authorize
-> your application with the applications.commands scope. The bot scope is not
+> In order to make Commands work within a guild, the guild must authorize
+> your application with the `applications.commands` scope. The `bot` scope is not
 > enough.
 
 ## Getting started
diff --git a/lib/nostrum/api.ex b/lib/nostrum/api.ex
index e198e2c8..ebc856b4 100644
--- a/lib/nostrum/api.ex
+++ b/lib/nostrum/api.ex
@@ -47,14 +47,27 @@ defmodule Nostrum.Api do
 
   alias Nostrum.Cache.Me
   alias Nostrum.{Constants, Snowflake, Util}
-  alias Nostrum.Struct.{Channel, Embed, Emoji, Guild, Interaction, Invite, Message, User, Webhook}
+
+  alias Nostrum.Struct.{
+    ApplicationCommand,
+    Channel,
+    Embed,
+    Emoji,
+    Guild,
+    Interaction,
+    Invite,
+    Message,
+    User,
+    Webhook
+  }
+
   alias Nostrum.Struct.Guild.{AuditLog, AuditLogEntry, Member, Role}
   alias Nostrum.Shard.{Session, Supervisor}
 
   @typedoc """
   Represents a failed response from the API.
 
-  This occurs when hackney or HTTPoison fail, or when the API doesn't respond with `200` or `204`.
+  This occurs when `:gun` fails, or when the API doesn't respond with `200` or `204`.
   """
   @type error :: {:error, Nostrum.Error.ApiError.t()}
 
@@ -275,7 +288,7 @@ defmodule Nostrum.Api do
     payload_json =
       options
       |> Map.delete(:files)
-      |> Poison.encode!()
+      |> Jason.encode_to_iodata!()
 
     boundary = generate_boundary()
 
@@ -1912,7 +1925,7 @@ defmodule Nostrum.Api do
       method: :put,
       route: Constants.guild_ban(guild_id, user_id),
       body: %{delete_message_days: days_to_delete},
-      options: [],
+      params: [],
       headers: maybe_add_reason(reason)
     })
   end
@@ -2954,7 +2967,7 @@ defmodule Nostrum.Api do
 
   ## Return value
   A list of ``ApplicationCommand``s on success. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
+  https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure
 
   ## Example
 
@@ -2993,7 +3006,7 @@ defmodule Nostrum.Api do
 
   ## Return value
   The created command. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#create-global-application-command
+  https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
 
   ## Example
 
@@ -3003,8 +3016,10 @@ defmodule Nostrum.Api do
   )
   ```
   """
-  @spec create_global_application_command(map()) :: {:ok, map()} | error
-  @spec create_global_application_command(User.id(), map()) :: {:ok, map()} | error
+  @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
     request(:post, Constants.global_application_commands(application_id), command)
     |> handle_request_with_decode
@@ -3023,10 +3038,17 @@ defmodule Nostrum.Api do
 
   ## Return value
   The updated command. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#edit-global-application-command
-  """
-  @spec edit_global_application_command(Snowflake.t(), map()) :: {:ok, map()} | error
-  @spec edit_global_application_command(User.id(), Snowflake.t(), map()) :: {:ok, map()} | error
+  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,
@@ -3068,11 +3090,14 @@ defmodule Nostrum.Api do
 
   ## Return value
   Updated list of global application commands. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#bulk-overwrite-global-application-commands
+  https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands
   """
   @doc since: "0.5.0"
-  @spec bulk_overwrite_global_application_commands([map()]) :: {:ok, [map()]} | error
-  @spec bulk_overwrite_global_application_commands(User.id(), [map()]) :: {:ok, [map()]} | error
+  @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
     request(:put, Constants.global_application_commands(application_id), commands)
     |> handle_request_with_decode
@@ -3089,7 +3114,7 @@ defmodule Nostrum.Api do
 
   ## Return value
   A list of ``ApplicationCommand``s on success. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#applicationcommand
+  https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure
   """
   @spec get_guild_application_commands(Guild.id()) :: {:ok, [map()]} | error
   @spec get_guild_application_commands(User.id(), Guild.id()) :: {:ok, [map()]} | error
@@ -3111,10 +3136,15 @@ defmodule Nostrum.Api do
 
   ## Return value
   The created command. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#create-guild-application-command
+  https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command
   """
-  @spec create_guild_application_command(Guild.id(), map()) :: {:ok, map()} | error
-  @spec create_guild_application_command(User.id(), Guild.id(), map()) :: {:ok, map()} | error
+  @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,
@@ -3138,10 +3168,19 @@ defmodule Nostrum.Api do
 
   ## Return value
   The updated command. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#edit-guild-application-command
+  https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command
   """
-  @spec edit_guild_application_command(Guild.id(), Snowflake.t(), map()) :: {:ok, map()} | error
-  @spec edit_guild_application_command(User.id(), Guild.id(), Snowflake.t(), map()) ::
+  @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,
@@ -3192,11 +3231,15 @@ defmodule Nostrum.Api do
 
   ## Return value
   Updated list of guild application commands. See the official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#bulk-overwrite-guild-application-commands
+  https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands
   """
   @doc since: "0.5.0"
-  @spec bulk_overwrite_guild_application_commands(Guild.id(), [map()]) :: {:ok, [map()]} | error
-  @spec bulk_overwrite_guild_application_commands(User.id(), Guild.id(), [map()]) ::
+  @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,
@@ -3228,7 +3271,7 @@ defmodule Nostrum.Api do
   ## 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/slash-commands#interaction-interaction-response)
+  - `response`: An [`InteractionResponse`](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object)
     object. See the linked documentation.
 
   ## Example
@@ -3295,7 +3338,7 @@ defmodule Nostrum.Api do
   - `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/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure).
+  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_application_command_permissions(Guild.id()) :: {:ok, [map()]} | error
@@ -3318,7 +3361,7 @@ defmodule Nostrum.Api do
   - `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/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure).
+  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_application_command_permissions(Guild.id(), Snowflake.t()) ::
@@ -3345,15 +3388,19 @@ defmodule Nostrum.Api do
     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/slash-commands#application-command-permissions-object-application-command-permissions-structure)
+  - `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/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure).
+  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_application_command_permissions(Guild.id(), Snowflake.t(), [map()]) ::
+  @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(), [map()]) ::
+  @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,
@@ -3379,15 +3426,25 @@ defmodule Nostrum.Api do
     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/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure) with `id` and `permissions`. You can add up to 10 overwrites per command.
+  - `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/slash-commands#application-command-permissions-object-guild-application-command-permissions-structure).
+  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_application_command_permissions(Guild.id(), [map()]) ::
+  @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(), [map()]) ::
+  @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,
@@ -3475,7 +3532,7 @@ defmodule Nostrum.Api do
   end
 
   defp handle_request_with_decode(response)
-  defp handle_request_with_decode({:ok, body}), do: {:ok, Poison.decode!(body, keys: :atoms)}
+  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)
@@ -3486,7 +3543,7 @@ defmodule Nostrum.Api do
   defp handle_request_with_decode({:ok, body}, type) do
     convert =
       body
-      |> Poison.decode!(keys: :atoms)
+      |> Jason.decode!(keys: :atoms)
       |> Util.cast(type)
 
     {:ok, convert}
@@ -3514,23 +3571,31 @@ defmodule Nostrum.Api do
 
   defp create_multipart_body(files, json, boundary) do
     json_mime = MIME.type("json")
-    json_size = byte_size(json)
+    json_size = :erlang.iolist_size(json)
 
     file_parts =
       files
       |> Enum.with_index(1)
-      |> Enum.reduce(~s|--#{boundary}#{@crlf}|, fn {f, i}, acc ->
-        acc <>
-          create_file_part_for_multipart(f, i) <>
-          ~s|#{@crlf}--#{boundary}#{@crlf}|
+      |> Enum.reduce([~s|--#{boundary}#{@crlf}|], fn {f, i}, acc ->
+        [
+          acc
+          | [
+              create_file_part_for_multipart(f, i),
+              ~s|#{@crlf}--#{boundary}#{@crlf}|
+            ]
+        ]
       end)
 
-    file_parts <>
-      ~s|content-length: #{json_size}#{@crlf}| <>
-      ~s|content-type: #{json_mime}#{@crlf}| <>
-      ~s|content-disposition: form-data; name="payload_json"#{@crlf}#{@crlf}| <>
-      json <>
-      ~s|#{@crlf}--#{boundary}--#{@crlf}|
+    [
+      file_parts
+      | [
+          ~s|content-length: #{json_size}#{@crlf}|,
+          ~s|content-type: #{json_mime}#{@crlf}|,
+          ~s|content-disposition: form-data; name="payload_json"#{@crlf}#{@crlf}|,
+          json,
+          ~s|#{@crlf}--#{boundary}--#{@crlf}|
+        ]
+    ]
   end
 
   defp create_multipart_body(%{content: content, tts: tts, file: file}, boundary) do
@@ -3539,17 +3604,19 @@ defmodule Nostrum.Api do
     tts_mime = MIME.type("")
     tts_size = byte_size(tts)
 
-    ~s|--#{boundary}#{@crlf}| <>
-      ~s|content-length: #{file_size}#{@crlf}| <>
-      ~s|content-type: #{file_mime}#{@crlf}| <>
-      ~s|content-disposition: form-data; name="file"; filename="#{file}"#{@crlf}#{@crlf}| <>
-      content <>
-      ~s|#{@crlf}--#{boundary}#{@crlf}| <>
-      ~s|content-length: #{tts_size}#{@crlf}| <>
-      ~s|content-type: #{tts_mime}#{@crlf}| <>
-      ~s|content-disposition: form-data; name="tts"#{@crlf}#{@crlf}| <>
-      tts <>
+    [
+      ~s|--#{boundary}#{@crlf}|,
+      ~s|content-length: #{file_size}#{@crlf}|,
+      ~s|content-type: #{file_mime}#{@crlf}|,
+      ~s|content-disposition: form-data; name="file"; filename="#{file}"#{@crlf}#{@crlf}|,
+      content,
+      ~s|#{@crlf}--#{boundary}#{@crlf}|,
+      ~s|content-length: #{tts_size}#{@crlf}|,
+      ~s|content-type: #{tts_mime}#{@crlf}|,
+      ~s|content-disposition: form-data; name="tts"#{@crlf}#{@crlf}|,
+      tts,
       ~s|#{@crlf}--#{boundary}--#{@crlf}|
+    ]
   end
 
   def create_file_part_for_multipart(file, index) do
@@ -3558,10 +3625,12 @@ defmodule Nostrum.Api do
     file_mime = MIME.from_path(name)
     file_size = byte_size(body)
 
-    ~s|content-length: #{file_size}#{@crlf}| <>
-      ~s|content-type: #{file_mime}#{@crlf}| <>
-      ~s|content-disposition: form-data; name="file#{index}"; filename="#{name}"#{@crlf}#{@crlf}| <>
+    [
+      ~s|content-length: #{file_size}#{@crlf}|,
+      ~s|content-type: #{file_mime}#{@crlf}|,
+      ~s|content-disposition: form-data; name="file#{index}"; filename="#{name}"#{@crlf}#{@crlf}|,
       body
+    ]
   end
 
   defp get_file_contents(path) when is_binary(path) do
diff --git a/lib/nostrum/api/base.ex b/lib/nostrum/api/base.ex
index f1ec0b7e..772b21ca 100644
--- a/lib/nostrum/api/base.ex
+++ b/lib/nostrum/api/base.ex
@@ -39,7 +39,7 @@ defmodule Nostrum.Api.Base do
 
   def process_request_body(""), do: ""
   def process_request_body({:multipart, content}), do: content
-  def process_request_body(body), do: Poison.encode!(body)
+  def process_request_body(body), do: Jason.encode_to_iodata!(body)
 
   def process_request_headers(headers) do
     user_agent = [
diff --git a/lib/nostrum/api/ratelimiter.ex b/lib/nostrum/api/ratelimiter.ex
index ecbadc43..0cc0e4e8 100644
--- a/lib/nostrum/api/ratelimiter.ex
+++ b/lib/nostrum/api/ratelimiter.ex
@@ -192,7 +192,7 @@ defmodule Nostrum.Api.Ratelimiter do
         {:ok}
 
       {:ok, {status, _, body}} ->
-        {:error, %ApiError{status_code: status, response: Poison.decode!(body, keys: :atoms)}}
+        {:error, %ApiError{status_code: status, response: Jason.decode!(body, keys: :atoms)}}
     end
   end
 end
diff --git a/lib/nostrum/error/api_error.ex b/lib/nostrum/error/api_error.ex
index fb11d864..0292e8b8 100644
--- a/lib/nostrum/error/api_error.ex
+++ b/lib/nostrum/error/api_error.ex
@@ -2,7 +2,7 @@ defmodule Nostrum.Error.ApiError do
   @moduledoc """
   Represents a failed response from the API.
 
-  This occurs when hackney or HTTPoison fail, or when the API doesn't respond with `200` or `204`.
+  This occurs when `:gun` fails, or when the API doesn't respond with `200` or `204`.
   This should only occur when using the banged API methods.
   """
 
diff --git a/lib/nostrum/shard/payload.ex b/lib/nostrum/shard/payload.ex
index 25e1c46b..ea73e19a 100644
--- a/lib/nostrum/shard/payload.ex
+++ b/lib/nostrum/shard/payload.ex
@@ -81,7 +81,9 @@ defmodule Nostrum.Shard.Payload do
   defp build_payload(data, opcode_name) do
     opcode = Constants.opcode_from_name(opcode_name)
 
+    # term_to_iovec is the same as term_to_binary except it instead returns an iolist
+    # a safe optimization since :gun.ws_send accepts an iolist
     %{"op" => opcode, "d" => data}
-    |> :erlang.term_to_binary()
+    |> :erlang.term_to_iovec()
   end
 end
diff --git a/lib/nostrum/struct/application_command.ex b/lib/nostrum/struct/application_command.ex
new file mode 100644
index 00000000..a85e811a
--- /dev/null
+++ b/lib/nostrum/struct/application_command.ex
@@ -0,0 +1,120 @@
+defmodule Nostrum.Struct.ApplicationCommand do
+  alias Nostrum.Snowflake
+
+  @moduledoc """
+  typespecs for creating Application Commands
+
+  Official reference:
+  https://discord.com/developers/docs/interactions/application-commands
+  """
+
+  @typedoc """
+  The name of the command, subcommand, or command_option, it must be between 1 and 32 characters in length and match the following regex `^[\w-]{1,32}$`
+  Only `USER` and `MESSAGE` commands may include uppercase letters and spaces.
+  """
+  @type command_name :: String.t()
+
+  @typedoc """
+  The command, subcommand, or options's description, for `CHAT_INPUT` commands it must be between 1 and 100 characters in length.
+  For `USER` and `MESSAGE` commands it must be an empty string.
+  """
+  @type command_description :: String.t()
+
+  @typedoc """
+  The type of application command you wish to create
+  - `1` for `CHAT_INPUT`, regular slash commands (default)
+  - `2` for `USER`, right-click menu commands on a specific user
+  - `3` for `MESSAGE`, right-click menu commands on a specific message
+  """
+  @type command_type :: pos_integer()
+
+  @typedoc """
+  If you specify choices for a command those become the only valid options for the user to select from.
+  """
+  @type command_choice :: %{
+          name: String.t(),
+          value: String.t() | number()
+        }
+
+  @typedoc """
+  Indicates what type of argument the command expects.
+
+  - `1` for `SUB_COMMAND`
+  - `2` for `SUB_COMMAND_GROUP`
+  - `3` for `STRING`
+  - `4` for `INTEGER` *Note*: due to API limitations they can only be between -2^53 and 2^53
+  - `5` for `BOOLEAN`
+  - `6` for `USER`
+  - `7` for `CHANNEL`
+  - `8` for `ROLE`
+  - `9` for `MENTIONABLE` *Note*: Includes users and roles
+  - `10` for `NUMBER` *Note*: This has the same limitations as `INTEGER`
+  """
+  @type command_option_type :: 1..10
+
+  @typedoc """
+  This defines a commands parameters, only valid for `CHAT_INPUT` commands.
+
+  ## Notes
+   - required parameters on a command must precede optional ones
+   - for subcommands and subcommand groups, `:options` are it's parameters
+   - `:options` and `:choices` are mutually exclusive
+   - `:autocomplete` may not be set to true if `:choices` is present
+   - if `:type` is 7 then `:channel_types` can be a list of allowed [channel types](https://discord.com/developers/docs/resources/channel#channel-object-channel-types)
+
+  """
+  @type command_option :: %{
+          required(:name) => command_name(),
+          required(:description) => command_description(),
+          required(:type) => command_option_type(),
+          optional(:required) => boolean(),
+          optional(:choices) => [command_choice()],
+          optional(:options) => [command_option()],
+          optional(:channel_types) => [pos_integer()],
+          optional(:autocomplete) => boolean()
+        }
+
+  @typedoc """
+  This defines the map for creating an application command.
+
+  `:default_permission` is for if the command is enabled for all users by default
+
+  For more information see [the official documentation](https://discord.com/developers/docs/interactions/application-commands)
+  """
+  @type application_command_map :: %{
+          required(:name) => command_name(),
+          required(:description) => command_description(),
+          optional(:type) => command_type(),
+          optional(:default_permission) => boolean(),
+          optional(:options) => [command_option()]
+        }
+
+  @typedoc """
+  When editing an existing application command, only the following fields may be updated,
+  missing fields will remain unchanged.
+  """
+  @type application_command_edit_map :: %{
+          optional(:name) => command_name(),
+          optional(:description) => command_description(),
+          optional(:options) => [command_option()],
+          optional(:default_permission) => boolean()
+        }
+
+  @typedoc """
+  For editing the permissions for an application command
+   - `:id` is the id of the role or user
+   - `:type` is the type of the id, either `role` or `user`
+   - `:allow` is whether the role or user should be allowed to use the command
+  """
+  @type application_command_permissions :: %{
+          id: Snowflake.t(),
+          type: application_command_permission_type(),
+          permission: boolean()
+        }
+
+  @typedoc """
+  - `1` for `ROLE`
+  - `2` for `USER`
+  """
+  @type application_command_permission_type :: pos_integer()
+end
diff --git a/lib/nostrum/struct/application_command_interaction_data.ex b/lib/nostrum/struct/application_command_interaction_data.ex
index 6bde74de..da438c36 100644
--- a/lib/nostrum/struct/application_command_interaction_data.ex
+++ b/lib/nostrum/struct/application_command_interaction_data.ex
@@ -66,7 +66,7 @@ defmodule Nostrum.Struct.ApplicationCommandInteractionData do
   Used as part of `t:Nostrum.Struct.Interaction.t/0`.
 
   Official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata
+  https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object
   """
   @type t :: %__MODULE__{
           id: id,
diff --git a/lib/nostrum/struct/application_command_interaction_data_option.ex b/lib/nostrum/struct/application_command_interaction_data_option.ex
index 238f9355..924836f4 100644
--- a/lib/nostrum/struct/application_command_interaction_data_option.ex
+++ b/lib/nostrum/struct/application_command_interaction_data_option.ex
@@ -9,7 +9,7 @@ defmodule Nostrum.Struct.ApplicationCommandInteractionDataOption do
   alias Nostrum.Struct.User
   alias Nostrum.Util
 
-  defstruct [:name, :type, :value, :options]
+  defstruct [:name, :type, :value, :options, :focused]
 
   @typedoc "Parameter name"
   @type name :: String.t()
@@ -17,11 +17,11 @@ defmodule Nostrum.Struct.ApplicationCommandInteractionDataOption do
   @typedoc """
   The application command option type.
 
-  See https://discord.com/developers/docs/interactions/slash-commands#application-command-object-application-command-option-type
+  See https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type
   for more details.
   """
   @typedoc since: "0.5.0"
-  @type type :: 1..9
+  @type type :: 1..10
 
   @typedoc """
   Parameter value.
@@ -40,10 +40,22 @@ defmodule Nostrum.Struct.ApplicationCommandInteractionDataOption do
   - For `t:type/0` of `8`, this will be a `t:Nostrum.Struct.Guild.Role.id/0`. The
     corresponding role can be looked up in
     `t:Nostrum.Struct.ApplicationCommandInteractionData.resolved/0`.
+  - For `t:type/0` of `9`, this will be a `t:Nostrum.Struct.User.id/0` or `t:Nostrum.Struct.Guild.Role.id/0`. The
+    corresponding user or role can be looked up in
+    `t:Nostrum.Struct.ApplicationCommandInteractionData.resolved/0`.
+  - For `t:type/0` of `10`, this will be a `t:number/0`.
 
   Mutually exclusive with `options`. If `options` is not `nil`, this will be `nil`.
   """
-  @type value :: String.t() | integer() | boolean() | User.id() | Channel.id() | Role.id() | nil
+  @type value ::
+          String.t()
+          | integer()
+          | boolean()
+          | number()
+          | User.id()
+          | Channel.id()
+          | Role.id()
+          | nil
 
   @typedoc """
   Parameter options for subcommands.
@@ -52,15 +64,21 @@ defmodule Nostrum.Struct.ApplicationCommandInteractionDataOption do
   """
   @type options :: [__MODULE__.t()] | nil
 
+  @typedoc """
+  Whether this parameter is focused for `autocomplete` interactions.
+  """
+  @type focused :: boolean() | nil
+
   @typedoc "Command interaction data struct"
   @type t :: %__MODULE__{
           name: name,
           type: type,
           value: value,
-          options: options
+          options: options,
+          focused: focused
         }
 
-  defp parse_value(type, value) when type in [6, 7, 8], do: Util.cast(value, Snowflake)
+  defp parse_value(type, value) when type in [6, 7, 8, 9], do: Util.cast(value, Snowflake)
   defp parse_value(_type, value), do: value
 
   @doc false
@@ -70,7 +88,8 @@ defmodule Nostrum.Struct.ApplicationCommandInteractionDataOption do
       name: map.name,
       type: map.type,
       value: parse_value(map.type, map[:value]),
-      options: Util.cast(map[:options], {:list, {:struct, __MODULE__}})
+      options: Util.cast(map[:options], {:list, {:struct, __MODULE__}}),
+      focused: map[:focused]
     }
   end
 end
diff --git a/lib/nostrum/struct/embed.ex b/lib/nostrum/struct/embed.ex
index c1fe7e0e..5ad17460 100644
--- a/lib/nostrum/struct/embed.ex
+++ b/lib/nostrum/struct/embed.ex
@@ -78,7 +78,7 @@ defmodule Nostrum.Struct.Embed do
 
   alias Nostrum.Struct.Embed.{Author, Field, Footer, Image, Provider, Thumbnail, Video}
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :title,
@@ -102,7 +102,7 @@ defmodule Nostrum.Struct.Embed do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/embed/author.ex b/lib/nostrum/struct/embed/author.ex
index d1e46616..53193a60 100644
--- a/lib/nostrum/struct/embed/author.ex
+++ b/lib/nostrum/struct/embed/author.ex
@@ -4,7 +4,7 @@ defmodule Nostrum.Struct.Embed.Author do
   """
 
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :name,
@@ -19,7 +19,7 @@ defmodule Nostrum.Struct.Embed.Author do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/embed/field.ex b/lib/nostrum/struct/embed/field.ex
index 93babe14..a40abe70 100644
--- a/lib/nostrum/struct/embed/field.ex
+++ b/lib/nostrum/struct/embed/field.ex
@@ -4,7 +4,7 @@ defmodule Nostrum.Struct.Embed.Field do
   """
 
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :name,
@@ -18,7 +18,7 @@ defmodule Nostrum.Struct.Embed.Field do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/embed/footer.ex b/lib/nostrum/struct/embed/footer.ex
index 5c1f233b..52696021 100644
--- a/lib/nostrum/struct/embed/footer.ex
+++ b/lib/nostrum/struct/embed/footer.ex
@@ -4,7 +4,7 @@ defmodule Nostrum.Struct.Embed.Footer do
   """
 
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :text,
@@ -18,7 +18,7 @@ defmodule Nostrum.Struct.Embed.Footer do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/embed/image.ex b/lib/nostrum/struct/embed/image.ex
index 6cf847c3..ae7c2807 100644
--- a/lib/nostrum/struct/embed/image.ex
+++ b/lib/nostrum/struct/embed/image.ex
@@ -4,7 +4,7 @@ defmodule Nostrum.Struct.Embed.Image do
   """
 
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :url,
@@ -19,7 +19,7 @@ defmodule Nostrum.Struct.Embed.Image do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/embed/provider.ex b/lib/nostrum/struct/embed/provider.ex
index 83fdc746..5cfeda6d 100644
--- a/lib/nostrum/struct/embed/provider.ex
+++ b/lib/nostrum/struct/embed/provider.ex
@@ -4,7 +4,7 @@ defmodule Nostrum.Struct.Embed.Provider do
   """
 
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :name,
@@ -17,7 +17,7 @@ defmodule Nostrum.Struct.Embed.Provider do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/embed/thumbnail.ex b/lib/nostrum/struct/embed/thumbnail.ex
index ef0cadb9..e8446d7d 100644
--- a/lib/nostrum/struct/embed/thumbnail.ex
+++ b/lib/nostrum/struct/embed/thumbnail.ex
@@ -4,7 +4,7 @@ defmodule Nostrum.Struct.Embed.Thumbnail do
   """
 
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :url,
@@ -19,7 +19,7 @@ defmodule Nostrum.Struct.Embed.Thumbnail do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/embed/video.ex b/lib/nostrum/struct/embed/video.ex
index ec75522c..4431fc6c 100644
--- a/lib/nostrum/struct/embed/video.ex
+++ b/lib/nostrum/struct/embed/video.ex
@@ -4,7 +4,7 @@ defmodule Nostrum.Struct.Embed.Video do
   """
 
   alias Nostrum.Util
-  alias Poison.Encoder
+  alias Jason.{Encode, Encoder}
 
   defstruct [
     :url,
@@ -18,7 +18,7 @@ defmodule Nostrum.Struct.Embed.Video do
       |> Map.from_struct()
       |> Enum.filter(fn {_, v} -> v != nil end)
       |> Map.new()
-      |> Encoder.encode(options)
+      |> Encode.map(options)
     end
   end
 
diff --git a/lib/nostrum/struct/guild/unavailable_guild.ex b/lib/nostrum/struct/guild/unavailable_guild.ex
index 02518ce2..ce53d397 100644
--- a/lib/nostrum/struct/guild/unavailable_guild.ex
+++ b/lib/nostrum/struct/guild/unavailable_guild.ex
@@ -14,7 +14,7 @@ defmodule Nostrum.Struct.Guild.UnavailableGuild do
           unavailable: unavailable
         }
 
-  @derive [Poison.Encoder]
+  @derive [Jason.Encoder]
   defstruct [
     :id,
     :unavailable
diff --git a/lib/nostrum/struct/interaction.ex b/lib/nostrum/struct/interaction.ex
index 52a2b7c2..266d1eb6 100644
--- a/lib/nostrum/struct/interaction.ex
+++ b/lib/nostrum/struct/interaction.ex
@@ -1,6 +1,6 @@
 defmodule Nostrum.Struct.Interaction do
-  @moduledoc "Slash command invocation struct."
-  # https://discord.com/developers/docs/interactions/slash-commands#interaction
+  @moduledoc "Application command and Component invocation struct."
+  # https://discord.com/developers/docs/interactions/application-commands#interaction
 
   alias Nostrum.Snowflake
   alias Nostrum.Struct.ApplicationCommandInteractionData
@@ -39,13 +39,15 @@ defmodule Nostrum.Struct.Interaction do
 
   - `1` for *Ping*
   - `2` for *ApplicationCommand*
+  - `3` for *MessageComponent*
+  - `4` for *ApplicationCommandAutocomplete*
   """
-  @type type :: pos_integer()
+  @type type :: 1..4
 
   @typedoc """
   Invocation data.
 
-  Only present for *ApplicationCommand* interactions, that is, `type=2`.
+  Only present for *ApplicationCommand* and *MessageComponent* interactions, that is, `type=2` or `type=3`.
   """
   @type data :: ApplicationCommandInteractionData.t() | nil
 
@@ -58,7 +60,7 @@ defmodule Nostrum.Struct.Interaction do
   @typedoc "Member information about the invoker, if invoked on a guild"
   @type member :: Member.t() | nil
 
-  @typedoc "User object for the invoking user, if invoked via a DM"
+  @typedoc "User object for the invoking user, will be a copy of `member.user` if invoked in a guild"
   @typedoc since: "0.5.0"
   @type user :: User.t() | nil
 
@@ -81,10 +83,10 @@ defmodule Nostrum.Struct.Interaction do
   @type message :: Message.t() | nil
 
   @typedoc """
-  A command invocation for slash commands.
+  A command invocation for Application Commands or Components.
 
   Official reference:
-  https://discord.com/developers/docs/interactions/slash-commands#interaction
+  https://discord.com/developers/docs/interactions/application-commands
   """
   @type t :: %__MODULE__{
           id: id,
@@ -111,7 +113,7 @@ defmodule Nostrum.Struct.Interaction do
       guild_id: map[:guild_id],
       channel_id: map[:channel_id],
       member: Util.cast(map[:member], {:struct, Member}),
-      user: Util.cast(map[:user], {:struct, User}),
+      user: Util.cast(map[:user] || map[:member][:user], {:struct, User}),
       token: map[:token],
       version: map[:version],
       message: Util.cast(map[:message], {:struct, Message})
diff --git a/lib/nostrum/struct/webhook.ex b/lib/nostrum/struct/webhook.ex
index ef15f6b3..bc4a8635 100644
--- a/lib/nostrum/struct/webhook.ex
+++ b/lib/nostrum/struct/webhook.ex
@@ -36,7 +36,7 @@ defmodule Nostrum.Struct.Webhook do
           token: token
         }
 
-  @derive [Poison.Encoder]
+  @derive [Jason.Encoder]
   defstruct [
     :id,
     :guild_id,
diff --git a/lib/nostrum/util.ex b/lib/nostrum/util.ex
index 05b96b3f..d9bcafaf 100644
--- a/lib/nostrum/util.ex
+++ b/lib/nostrum/util.ex
@@ -9,53 +9,6 @@ defmodule Nostrum.Util do
 
   require Logger
 
-  @doc """
-  Helper for defining all the methods used for struct and encoding transformations.
-
-  ## Example
-  ``` Elixir
-  Nostrum.Util.nostrum_struct(%{
-    author: User,
-    mentions: [User],
-    mention_roles: [User],
-    embeds: [Embed]
-  })
-  ```
-  """
-  defmacro nostrum_struct(body) do
-    quote do
-      @derive [Poison.Encoder]
-      defstruct Map.keys(unquote(body))
-
-      def p_encode do
-        encoded =
-          for {k, v} <- unquote(body), v != nil, into: %{} do
-            case v do
-              [v] -> {k, [v.p_encode]}
-              v -> {k, v.p_encode}
-            end
-          end
-
-        struct(__ENV__.module, encoded)
-      end
-
-      def to_struct(map) do
-        alias Nostrum.Util
-
-        new_map =
-          for {k, v} <- unquote(body), into: %{} do
-            case v do
-              nil -> {k, Map.get(map, k)}
-              [v] -> {k, Util.enum_to_struct(Map.get(map, k), v)}
-              v -> {k, apply(v, :to_struct, [Map.get(map, k)])}
-            end
-          end
-
-        struct(__ENV__.module, new_map)
-      end
-    end
-  end
-
   @doc """
   Returns the number of milliseconds since unix epoch.
   """
@@ -150,7 +103,7 @@ defmodule Nostrum.Util do
         raise(Nostrum.Error.ApiError, status_code: code, message: message)
 
       {:ok, body} ->
-        body = Poison.decode!(body)
+        body = Jason.decode!(body)
 
         "wss://" <> url = body["url"]
         shards = if body["shards"], do: body["shards"], else: 1
diff --git a/lib/nostrum/voice/payload.ex b/lib/nostrum/voice/payload.ex
index 4bbc4a1a..4d92dc4b 100644
--- a/lib/nostrum/voice/payload.ex
+++ b/lib/nostrum/voice/payload.ex
@@ -86,6 +86,6 @@ defmodule Nostrum.Voice.Payload do
     opcode = Constants.voice_opcode_from_name(opcode_name)
 
     %{op: opcode, d: data}
-    |> Poison.encode!()
+    |> Jason.encode_to_iodata!()
   end
 end
diff --git a/lib/nostrum/voice/session.ex b/lib/nostrum/voice/session.ex
index 48a7a92d..e85e5a6d 100644
--- a/lib/nostrum/voice/session.ex
+++ b/lib/nostrum/voice/session.ex
@@ -89,10 +89,8 @@ defmodule Nostrum.Voice.Session do
   end
 
   def handle_info({:gun_ws, _worker, stream, {:text, frame}}, state) do
-    payload =
-      frame
-      |> :erlang.iolist_to_binary()
-      |> Poison.decode!()
+    # Jason.decode calls iodata_to_binary internally
+    payload = Jason.decode!(frame)
 
     from_handle =
       payload["op"]
diff --git a/mix.exs b/mix.exs
index f799808a..3cab624d 100644
--- a/mix.exs
+++ b/mix.exs
@@ -47,12 +47,12 @@ defmodule Nostrum.Mixfile do
     [
       "docs/static/Intro.md",
       "docs/static/API.md",
+      "docs/static/Application Commands.md",
       "docs/static/State.md",
       "docs/static/Events.md",
       "docs/static/Consumers.md",
       "docs/static/Voice.md",
-      "docs/static/Gateway Intents.md",
-      "docs/static/Slash commands.md"
+      "docs/static/Gateway Intents.md"
     ]
   end
 
@@ -90,7 +90,7 @@ defmodule Nostrum.Mixfile do
 
   defp deps do
     [
-      {:poison, "~> 3.0"},
+      {:jason, "~> 1.2"},
       {:gun, "== 2.0.1", hex: :remedy_gun},
       {:certifi, "~> 2.8"},
       {:kcl, "~> 1.4"},
diff --git a/mix.lock b/mix.lock
index 28498cee..ea97d870 100644
--- a/mix.lock
+++ b/mix.lock
@@ -21,7 +21,6 @@
   "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
   "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
   "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
-  "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
   "poly1305": {:hex, :poly1305, "1.0.3", "9ec6b5b3b96d96b15d4f2b8a3629aad2a2f5fe82f4ea0f13e5a3512737af4ee7", [:mix], [{:chacha20, "~> 1.0", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 1.0", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm", "fbe549d59e74e7cde680e7ae6baf7fe2b5f9053a84c1b5c866f703d0651d6b22"},
   "porcelain": {:hex, :porcelain, "2.0.3", "2d77b17d1f21fed875b8c5ecba72a01533db2013bd2e5e62c6d286c029150fdc", [:mix], [], "hexpm", "dc996ab8fadbc09912c787c7ab8673065e50ea1a6245177b0c24569013d23620"},
   "recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"},


Test patch:
diff --git a/test/nostrum/struct/interaction_test.exs b/test/nostrum/struct/interaction_test.exs
index 1eb7b864..065b2b24 100644
--- a/test/nostrum/struct/interaction_test.exs
+++ b/test/nostrum/struct/interaction_test.exs
@@ -96,7 +96,7 @@ defmodule Nostrum.Struct.InteractionTest do
                member: %Member{
                  user: %User{}
                },
-               user: nil,
+               user: %User{},
                token: "bob",
                type: 2,
                version: 1