Repository: hikari-py/hikari
Pull request #720 (2021-08-06 06:06:49)

Implement Slash Command Permissions
### Summary
Implement Slash Command Permissions

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.

### Related issues
~~This is blocked by #701~~

FasterSpeeding: Add relay methods to Command class

Patch:
diff --git a/README.md b/README.md
index 0f686ee5..0959289d 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,7 @@ implementing your own command handler.
 Hikari does not include a command framework by default, so you will want to pick a third party library to do it:
 
 - [`lightbulb`](https://github.com/tandemdude/hikari-lightbulb) - a simple and easy to use command framework for Hikari.
+- [`tanjun`](https://github.com/FasterSpeeding/Tanjun) - a flexible command framework designed to extend Hikari.
 
 ---
 
diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py
index 8cdc40cd..418004af 100644
--- a/hikari/api/entity_factory.py
+++ b/hikari/api/entity_factory.py
@@ -988,6 +988,38 @@ def deserialize_command(
             the integration.
         """
 
+    @abc.abstractmethod
+    def deserialize_guild_command_permissions(
+        self, payload: data_binding.JSONObject
+    ) -> commands.GuildCommandPermissions:
+        """Parse a raw payload from Discord into guild command permissions object.
+
+        Parameters
+        ----------
+        payload : hikari.internal.data_binding.JSONObject
+            The JSON payload to deserialize.
+
+        Returns
+        -------
+        hikari.commands.GuildCommandPermissions
+            The deserialized guild command permissions object.
+        """
+
+    @abc.abstractmethod
+    def serialize_command_permission(self, permission: commands.CommandPermission) -> data_binding.JSONObject:
+        """Serialize a command permission object to a json serializable dict.
+
+        Parameters
+        ----------
+        permission: hikari.commands.CommandPermission
+            The command permission object to serialize.
+
+        Returns
+        -------
+        hikari.internal.data_binding.JSONObject
+            The serialized representation of the command permission.
+        """
+
     @abc.abstractmethod
     def deserialize_partial_interaction(self, payload: data_binding.JSONObject) -> base_interactions.PartialInteraction:
         """Parse a raw payload from Discord into a partial interaction object.
diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py
index c37f187b..e01efd91 100644
--- a/hikari/api/event_factory.py
+++ b/hikari/api/event_factory.py
@@ -466,69 +466,6 @@ def deserialize_presence_update_event(
     # INTERACTION EVENTS #
     ######################
 
-    @abc.abstractmethod
-    def deserialize_command_create_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandCreateEvent:
-        """Parse a raw payload from Discord into a command create event object.
-
-        Parameters
-        ----------
-        shard : hikari.api.shard.GatewayShard
-            The shard that emitted this event.
-        payload : hikari.internal.data_binding.JSONObject
-            The dict payload to parse.
-
-        Returns
-        -------
-        hikari.events.interaction_events.CommandCreateEvent
-            The parsed command create event object.
-        """
-
-    @abc.abstractmethod
-    def deserialize_command_update_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandUpdateEvent:
-        """Parse a raw payload from Discord into a command update event object.
-
-        Parameters
-        ----------
-        shard : hikari.api.shard.GatewayShard
-            The shard that emitted this event.
-        payload : hikari.internal.data_binding.JSONObject
-            The dict payload to parse.
-
-        Returns
-        -------
-        hikari.events.interaction_events.CommandUpdateEvent
-            The parsed command update event object.
-        """
-
-    @abc.abstractmethod
-    def deserialize_command_delete_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandDeleteEvent:
-        """Parse a raw payload from Discord into a command delete event object.
-
-        Parameters
-        ----------
-        shard : hikari.api.shard.GatewayShard
-            The shard that emitted this event.
-        payload : hikari.internal.data_binding.JSONObject
-            The dict payload to parse.
-
-        Returns
-        -------
-        hikari.events.interaction_events.CommandDeleteEvent
-            The parsed command delete event object.
-        """
-
     @abc.abstractmethod
     def deserialize_interaction_create_event(
         self,
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 368807b9..dbb7828c 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -6232,6 +6232,7 @@ async def create_application_command(
         guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
         *,
         options: undefined.UndefinedOr[typing.Sequence[commands.CommandOption]] = undefined.UNDEFINED,
+        default_permission: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
     ) -> commands.Command:
         r"""Create an application command.
 
@@ -6253,6 +6254,11 @@ async def create_application_command(
             a global command rather than a guild specific one.
         options : hikari.undefined.UndefinedOr[typing.Sequence[hikari.commands.CommandOption]]
             A sequence of up to 10 options for this command.
+        default_permission : hikari.undefined.UndefinedOr[builtins.bool]
+            Whether this command should be enabled by default (without any
+            permissions) when added to a guild.
+
+            Defaults to `builtins.True`.
 
         Returns
         -------
@@ -6454,6 +6460,203 @@ async def delete_application_command(
             If an internal error occurs on Discord while handling the request.
         """
 
+    @abc.abstractmethod
+    async def fetch_application_guild_commands_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+    ) -> typing.Sequence[commands.GuildCommandPermissions]:
+        """Fetch the command permissions registered in a guild.
+
+        Parameters
+        ----------
+        application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
+            Object or ID of the application to fetch the command permissions for.
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            Object or ID of the guild to fetch the command permissions for.
+
+        Returns
+        -------
+        typing.Sequence[hikari.commands.GuildCommandPermissions]
+            Sequence of the guild command permissions set for the specified guild.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you cannot access the provided application's commands or guild.
+        hikari.errors.NotFoundError
+            If the provided application isn't found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+
+    @abc.abstractmethod
+    async def fetch_application_command_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+        command: snowflakes.SnowflakeishOr[commands.Command],
+    ) -> commands.GuildCommandPermissions:
+        """Fetch the permissions registered for a specific command in a guild.
+
+        Parameters
+        ----------
+        application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
+            Object or ID of the application to fetch the command permissions for.
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            Object or ID of the guild to fetch the command permissions for.
+        command: hikari.snowflakes.SnowflakeishOr[hikari.commands.Command]
+            Objecr or ID of the command to fetch the command permissions for.
+
+        Returns
+        -------
+        hikari.commands.GuildCommandPermissions
+            Object of the command permissions set for the specified command.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you cannot access the provided application's commands or guild.
+        hikari.errors.NotFoundError
+            If the provided application or command isn't found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+
+    @abc.abstractmethod
+    async def set_application_guild_commands_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+        permissions: typing.Mapping[
+            snowflakes.SnowflakeishOr[commands.Command], typing.Sequence[commands.CommandPermission]
+        ],
+    ) -> typing.Sequence[commands.GuildCommandPermissions]:
+        """Set permissions in a guild for multiple commands.
+
+        !!! note
+            This overwrites any previously set permissions for the specified
+            commands.
+
+        Parameters
+        ----------
+        application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
+            Object or ID of the application to set the command permissions for.
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            Object or ID of the guild to set the command permissions for.
+        permissions : typing.Mapping[hikari.snowflakes.SnowflakeishOr[hikari.commands.Command], typing.Sequence[hikari.commands.CommandPermission]]
+            Mapping of objects and/or IDs of commands to sequences of the commands
+            to set for the specified guild.
+
+            !!! warning
+                Only a maximum of up to 10 permissions can be set per command.
+
+        Returns
+        -------
+        typing.Sequence[hikari.commands.GuildCommandPermissions]
+            Sequence of the set guild command permissions.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you cannot access the provided application's commands or guild.
+        hikari.errors.NotFoundError
+            If the provided application or command isn't found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """  # noqa: E501 - Line too long
+
+    @abc.abstractmethod
+    async def set_application_command_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+        command: snowflakes.SnowflakeishOr[commands.Command],
+        permissions: typing.Sequence[commands.CommandPermission],
+    ) -> commands.GuildCommandPermissions:
+        """Set permissions for a specific command.
+
+        !!! note
+            This overwrites any previously set permissions.
+
+        Parameters
+        ----------
+        application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
+            Object or ID of the application to set the command permissions for.
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            Object or ID of the guild to set the command permissions for.
+        command : hikari.snowflakes.SnowflakeishOr[hikari.commands.Command]
+            Object or ID of the command to set the permissions for.
+        permissions : typing.Sequence[hikari.commands.CommandPermission]
+            Sequence of up to 10 of the permission objects to set.
+
+        Returns
+        -------
+        hikari.commands.GuildCommandPermissions
+            Object of the set permissions.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you cannot access the provided application's commands or guild.
+        hikari.errors.NotFoundError
+            If the provided application or command isn't found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+
     @abc.abstractmethod
     def interaction_deferred_builder(
         self, type: typing.Union[base_interactions.ResponseType, int], /
diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py
index 493ddc23..6f83725f 100644
--- a/hikari/api/special_endpoints.py
+++ b/hikari/api/special_endpoints.py
@@ -914,6 +914,19 @@ def id(self) -> undefined.UndefinedOr[snowflakes.Snowflake]:
             The ID of this command if set.
         """
 
+    @property
+    @abc.abstractmethod
+    def default_permission(self) -> undefined.UndefinedOr[bool]:
+        """Whether the command should be enabled by default (without any permissions).
+
+        Defaults to `builtins.bool`.
+
+        Returns
+        -------
+        undefined.UndefinedOr[builtins.bool]
+            Whether the command should be enabled by default (without any permissions).
+        """
+
     @abc.abstractmethod
     def set_id(self: _T, id_: undefined.UndefinedOr[snowflakes.Snowflakeish], /) -> _T:
         """Set the ID of this command.
@@ -929,6 +942,21 @@ def set_id(self: _T, id_: undefined.UndefinedOr[snowflakes.Snowflakeish], /) ->
             Object of this command builder.
         """
 
+    @abc.abstractmethod
+    def set_default_permission(self: _T, state: undefined.UndefinedOr[bool], /) -> _T:
+        """Whether this command should be enabled by default (without any permissions).
+
+        Parameters
+        ----------
+        state : hikari.undefined.UndefinedOr[builtins.bool]
+            Whether this command should be enabled by default.
+
+        Returns
+        -------
+        CommandBuilder
+            Object of this command builder for chained calls.
+        """
+
     @abc.abstractmethod
     def add_option(self: _T, option: commands.CommandOption) -> _T:
         """Add an option to this command.
diff --git a/hikari/commands.py b/hikari/commands.py
index 928558da..0f38242a 100644
--- a/hikari/commands.py
+++ b/hikari/commands.py
@@ -27,6 +27,9 @@
     "Command",
     "CommandChoice",
     "CommandOption",
+    "CommandPermission",
+    "CommandPermissionType",
+    "GuildCommandPermissions",
     "OptionType",
 ]
 
@@ -40,6 +43,9 @@
 from hikari.internal import attr_extensions
 from hikari.internal import enums
 
+if typing.TYPE_CHECKING:
+    from hikari import guilds
+
 
 @typing.final
 class OptionType(int, enums.Enum):
@@ -59,6 +65,7 @@ class OptionType(int, enums.Enum):
 
     This is range limited between -2^53 and 2^53.
     """
+
     BOOLEAN = 5
     """Denotes a command option where the value will be a bool."""
 
@@ -161,6 +168,13 @@ class Command(snowflakes.Unique):
     options: typing.Optional[typing.Sequence[CommandOption]] = attr.field(eq=False, hash=False, repr=False)
     """Sequence of up to (and including) 25 of the options for this command."""
 
+    default_permission: bool = attr.field(eq=False, hash=False, repr=True)
+    """Whether the command is enabled by default when added to a guild.
+
+    Defaults to `builtins.True`. This behaviour is overridden by command
+    permissions.
+    """
+
     guild_id: typing.Optional[snowflakes.Snowflake] = attr.field(eq=False, hash=False, repr=False)
     """ID of the guild this command is in.
 
@@ -172,7 +186,7 @@ async def fetch_self(self) -> Command:
 
         Returns
         -------
-        hikari.commands.Command
+        Command
             Object of the fetched command.
 
         Raises
@@ -222,13 +236,13 @@ async def edit(
         description : hikari.undefined.UndefinedOr[builtins.str]
             The description to set for the command. Leave as `hikari.undefined.UNDEFINED`
             to not change.
-        options : hikari.undefined.UndefinedOr[typing.Sequence[hikari.commands.CommandOption]]
+        options : hikari.undefined.UndefinedOr[typing.Sequence[CommandOption]]
             A sequence of up to 10 options to set for this command. Leave this as
             `hikari.undefined.UNDEFINED` to not change.
 
         Returns
         -------
-        hikari.commands.Command
+        Command
             The edited command object.
 
         Raises
@@ -292,3 +306,133 @@ async def delete(self) -> None:
         await self.app.rest.delete_application_command(
             self.application_id, self.id, undefined.UNDEFINED if self.guild_id is None else self.guild_id
         )
+
+    async def fetch_guild_permissions(
+        self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild], /
+    ) -> GuildCommandPermissions:
+        """Fetch the permissions registered for this command in a specific guild.
+
+        Parameters
+        ----------
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            Object or ID of the guild to fetch the command permissions for.
+
+        Returns
+        -------
+        GuildCommandPermissions
+            Object of the command permissions set for the specified command.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you cannot access the provided application's commands or guild.
+        hikari.errors.NotFoundError
+            If the provided application or command isn't found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.fetch_application_command_permissions(
+            application=self.application_id, command=self.id, guild=guild
+        )
+
+    async def set_guild_permissions(
+        self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild], permissions: typing.Sequence[CommandPermission]
+    ) -> GuildCommandPermissions:
+        """Set permissions for this command in a specific guild.
+
+        !!! note
+            This overwrites any previously set permissions.
+
+        Parameters
+        ----------
+        guild : hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialGuild]]
+            Object or ID of the guild to set the command permissions in.
+        permissions : typing.Sequence[CommandPermission]
+            Sequence of up to 10 of the permission objects to set.
+
+        Returns
+        -------
+        GuildCommandPermissions
+            Object of the set permissions.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you cannot access the provided application's commands or guild.
+        hikari.errors.NotFoundError
+            If the provided application or command isn't found.
+        hikari.errors.UnauthorizedError
+            If you are unauthorized to make the request (invalid/missing token).
+        hikari.errors.RateLimitTooLongError
+            Raised in the event that a rate limit occurs that is
+            longer than `max_rate_limit` when making a request.
+        hikari.errors.RateLimitedError
+            Usually, Hikari will handle and retry on hitting
+            rate-limits automatically. This includes most bucket-specific
+            rate-limits and global rate-limits. In some rare edge cases,
+            however, Discord implements other undocumented rules for
+            rate-limiting, such as limits per attribute. These cannot be
+            detected or handled normally by Hikari due to their undocumented
+            nature, and will trigger this exception if they occur.
+        hikari.errors.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        return await self.app.rest.set_application_command_permissions(
+            application=self.application_id, command=self.id, guild=guild, permissions=permissions
+        )
+
+
+class CommandPermissionType(int, enums.Enum):
+    """The type of entity a command permission targets."""
+
+    ROLE = 1
+    """A command permission which toggles access for a specific role."""
+
+    USER = 2
+    """A command permission which toggles access for a specific user."""
+
+
+@attr_extensions.with_copy
+@attr.define(kw_only=True, weakref_slot=False)
+class CommandPermission:
+    """Representation of a permission which enables or disables a command for a user or role."""
+
+    id: snowflakes.Snowflake = attr.field(converter=snowflakes.Snowflake)
+    """Id of the role or user this permission changes the permission's state for."""
+
+    type: typing.Union[CommandPermissionType, int] = attr.field(converter=CommandPermissionType)
+    """The entity this permission overrides the command's state for."""
+
+    has_access: bool = attr.field()
+    """Whether this permission marks the target entity as having access to the command."""
+
+
+@attr_extensions.with_copy
+@attr.define(kw_only=True, weakref_slot=False)
+class GuildCommandPermissions:
+    """Representation of the permissions set for a command within a guild."""
+
+    application_id: snowflakes.Snowflake = attr.field()
+    """ID of the application the relevant command belongs to."""
+
+    command_id: snowflakes.Snowflake = attr.field()
+    """ID of the command these permissions are for."""
+
+    guild_id: snowflakes.Snowflake = attr.field()
+    """ID of the guild these permissions are in."""
+
+    permissions: typing.Sequence[CommandPermission] = attr.field()
+    """Sequence of up to (and including) 10 of the command permissions set in this guild."""
diff --git a/hikari/config.py b/hikari/config.py
index 351c6d78..53c64f05 100644
--- a/hikari/config.py
+++ b/hikari/config.py
@@ -450,9 +450,17 @@ class CacheSettings:
     """
 
     max_messages: int = attr.field(default=300)
-    """The max number of messages to store in the cache at once.
+    """The maximum number of messages to store in the cache at once.
 
     This will have no effect if the messages cache is not enabled.
 
     Defaults to `300`.
     """
+
+    max_dm_channel_ids: int = attr.field(default=50)
+    """The maximum number of channel IDs to store in the cache at once.
+
+    This will have no effect if the channel IDs cache is not enabled.
+
+    Defaults to `50`.
+    """
diff --git a/hikari/events/interaction_events.py b/hikari/events/interaction_events.py
index 11d169a3..be84606d 100644
--- a/hikari/events/interaction_events.py
+++ b/hikari/events/interaction_events.py
@@ -24,14 +24,9 @@
 from __future__ import annotations
 
 __all__: typing.List[str] = [
-    "CommandEvent",
-    "CommandCreateEvent",
-    "CommandUpdateEvent",
-    "CommandDeleteEvent",
     "InteractionCreateEvent",
 ]
 
-import abc
 import typing
 
 import attr
@@ -40,83 +35,11 @@
 from hikari.internal import attr_extensions
 
 if typing.TYPE_CHECKING:
-    from hikari import commands
     from hikari import traits
     from hikari.api import shard as gateway_shard
     from hikari.interactions import base_interactions
 
 
-class CommandEvent(shard_events.ShardEvent, abc.ABC):
-    """Base class of events fired for changes to application commands."""
-
-    @property
-    def app(self) -> traits.RESTAware:
-        # <<inherited docstring from Event>>.
-        return self.command.app
-
-    @property
-    @abc.abstractmethod
-    def command(self) -> commands.Command:
-        """Object of the command this event is for.
-
-        Returns
-        -------
-        hikari.commands.Command
-            The command this event is for.
-        """
-
-
-@attr_extensions.with_copy
-@attr.define(kw_only=True, weakref_slot=False)
-class CommandCreateEvent(CommandEvent):
-    """Event fired when a command is created relevant to the current bot.
-
-    !!! note
-        This includes applications created by other bots which share a guild
-        with the current bot.
-    """
-
-    shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
-    # <<docstring inherited from ShardEvent>>.
-
-    command: commands.Command = attr.field(repr=True)
-    # <<inherited docstring from CommandEvent>>.
-
-
-@attr_extensions.with_copy
-@attr.define(kw_only=True, weakref_slot=False)
-class CommandUpdateEvent(CommandEvent):
-    """Event fired when a command is updated relevant to the current bot.
-
-    !!! note
-        This includes applications created by other bots which share a guild
-        with the current bot.
-    """
-
-    shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
-    # <<docstring inherited from ShardEvent>>.
-
-    command: commands.Command = attr.field(repr=True)
-    # <<inherited docstring from CommandEvent>>.
-
-
-@attr_extensions.with_copy
-@attr.define(kw_only=True, weakref_slot=False)
-class CommandDeleteEvent(CommandEvent):
-    """Event fired when a command is deleted relevant to the current bot.
-
-    !!! note
-        This includes applications created by other bots which share a guild
-        with the current bot.
-    """
-
-    shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True})
-    # <<docstring inherited from ShardEvent>>.
-
-    command: commands.Command = attr.field(repr=True)
-    # <<inherited docstring from CommandEvent>>.
-
-
 @attr_extensions.with_copy
 @attr.define(kw_only=True, weakref_slot=False)
 class InteractionCreateEvent(shard_events.ShardEvent):
diff --git a/hikari/impl/cache.py b/hikari/impl/cache.py
index cd9f598f..0b694d1b 100644
--- a/hikari/impl/cache.py
+++ b/hikari/impl/cache.py
@@ -82,10 +82,10 @@ class CacheImpl(cache.MutableCache):
         "_settings",
     )
 
-    _dm_channel_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, snowflakes.Snowflake]
     # For the sake of keeping things clean, the annotations are being kept separate from the assignment here.
     _me: typing.Optional[users.OwnUser]
     _emoji_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, cache_utility.KnownCustomEmojiData]
+    _dm_channel_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, snowflakes.Snowflake]
     _guild_channel_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, channels.GuildChannel]
     _guild_entries: collections.ExtendedMutableMapping[snowflakes.Snowflake, cache_utility.GuildRecord]
     _invite_entries: collections.ExtendedMutableMapping[str, cache_utility.InviteData]
@@ -104,7 +104,6 @@ class CacheImpl(cache.MutableCache):
 
     def __init__(self, app: traits.RESTAware, settings: config.CacheSettings) -> None:
         self._app = app
-        self._me = None
         self._settings = settings
         self._create_cache()
 
@@ -113,7 +112,8 @@ def settings(self) -> config.CacheSettings:
         return self._settings
 
     def _create_cache(self) -> None:
-        self._dm_channel_entries = collections.FreezableDict()
+        self._me = None
+        self._dm_channel_entries = collections.LimitedCapacityCacheMap(limit=self._settings.max_dm_channel_ids)
         self._emoji_entries = collections.FreezableDict()
         self._guild_channel_entries = collections.FreezableDict()
         self._guild_entries = collections.FreezableDict()
@@ -142,21 +142,33 @@ def clear(self) -> None:
         self._create_cache()
 
     def clear_dm_channel_ids(self) -> cache.CacheView[snowflakes.Snowflake, snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return cache_utility.EmptyCacheView()
+
         result = self._dm_channel_entries
-        self._dm_channel_entries = collections.FreezableDict()
+        self._dm_channel_entries = collections.LimitedCapacityCacheMap(limit=self._settings.max_dm_channel_ids)
         return cache_utility.CacheMappingView(result)
 
     def delete_dm_channel_id(
         self, user: snowflakes.SnowflakeishOr[users.PartialUser], /
     ) -> typing.Optional[snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return None
+
         return self._dm_channel_entries.pop(snowflakes.Snowflake(user), None)
 
     def get_dm_channel_id(
         self, user: snowflakes.SnowflakeishOr[users.PartialUser], /
     ) -> typing.Optional[snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return None
+
         return self._dm_channel_entries.get(snowflakes.Snowflake(user))
 
     def get_dm_channel_ids_view(self) -> cache.CacheView[snowflakes.Snowflake, snowflakes.Snowflake]:
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return cache_utility.EmptyCacheView()
+
         return cache_utility.CacheMappingView(self._dm_channel_entries.freeze())
 
     def set_dm_channel_id(
@@ -165,9 +177,10 @@ def set_dm_channel_id(
         channel: snowflakes.SnowflakeishOr[channels.PartialChannel],
         /,
     ) -> None:
-        user = snowflakes.Snowflake(user)
-        if self._is_cache_enabled_for(config.CacheComponents.EMOJIS) and user in self._user_entries:
-            self._dm_channel_entries[user] = snowflakes.Snowflake(channel)
+        if not self._is_cache_enabled_for(config.CacheComponents.DM_CHANNEL_IDS):
+            return None
+
+        self._dm_channel_entries[snowflakes.Snowflake(user)] = snowflakes.Snowflake(channel)
 
     def _build_emoji(
         self,
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 80e6ae74..c6c1dd8d 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1681,9 +1681,31 @@ def deserialize_command(
             name=payload["name"],
             description=payload["description"],
             options=options,
+            default_permission=payload.get("default_permission", True),
             guild_id=guild_id,
         )
 
+    def deserialize_guild_command_permissions(
+        self, payload: data_binding.JSONObject
+    ) -> commands.GuildCommandPermissions:
+        permissions = [
+            commands.CommandPermission(
+                id=snowflakes.Snowflake(perm["id"]),
+                type=commands.CommandPermissionType(perm["type"]),
+                has_access=perm["permission"],
+            )
+            for perm in payload["permissions"]
+        ]
+        return commands.GuildCommandPermissions(
+            application_id=snowflakes.Snowflake(payload["application_id"]),
+            command_id=snowflakes.Snowflake(payload["id"]),
+            guild_id=snowflakes.Snowflake(payload["guild_id"]),
+            permissions=permissions,
+        )
+
+    def serialize_command_permission(self, permission: commands.CommandPermission) -> data_binding.JSONObject:
+        return {"id": str(permission.id), "type": permission.type, "permission": permission.has_access}
+
     def deserialize_partial_interaction(self, payload: data_binding.JSONObject) -> base_interactions.PartialInteraction:
         return base_interactions.PartialInteraction(
             app=self._app,
diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py
index 2d876e29..80a23b24 100644
--- a/hikari/impl/event_factory.py
+++ b/hikari/impl/event_factory.py
@@ -344,33 +344,6 @@ def deserialize_presence_update_event(
     # INTERACTION EVENTS #
     ######################
 
-    def deserialize_command_create_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandCreateEvent:
-        return interaction_events.CommandCreateEvent(
-            shard=shard, command=self._app.entity_factory.deserialize_command(payload)
-        )
-
-    def deserialize_command_update_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandUpdateEvent:
-        return interaction_events.CommandUpdateEvent(
-            shard=shard, command=self._app.entity_factory.deserialize_command(payload)
-        )
-
-    def deserialize_command_delete_event(
-        self,
-        shard: gateway_shard.GatewayShard,
-        payload: data_binding.JSONObject,
-    ) -> interaction_events.CommandDeleteEvent:
-        return interaction_events.CommandDeleteEvent(
-            shard=shard, command=self._app.entity_factory.deserialize_command(payload)
-        )
-
     def deserialize_interaction_create_event(
         self,
         shard: gateway_shard.GatewayShard,
diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py
index ef10e8af..8c36fb5b 100644
--- a/hikari/impl/event_manager.py
+++ b/hikari/impl/event_manager.py
@@ -489,24 +489,6 @@ async def on_webhooks_update(self, shard: gateway_shard.GatewayShard, payload: d
         """See https://discord.com/developers/docs/topics/gateway#webhooks-update for more info."""
         await self.dispatch(self._event_factory.deserialize_webhook_update_event(shard, payload))
 
-    async def on_application_command_create(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
-    ) -> None:
-        """See https://discord.com/developers/docs/topics/gateway#application-command-create for more info."""
-        await self.dispatch(self._event_factory.deserialize_command_create_event(shard, payload))
-
-    async def on_application_command_update(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
-    ) -> None:
-        """See https://discord.com/developers/docs/topics/gateway#application-command-update for more info."""
-        await self.dispatch(self._event_factory.deserialize_command_update_event(shard, payload))
-
-    async def on_application_command_delete(
-        self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject
-    ) -> None:
-        """See https://discord.com/developers/docs/topics/gateway#application-command-delete for more info."""
-        await self.dispatch(self._event_factory.deserialize_command_delete_event(shard, payload))
-
     async def on_interaction_create(self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject) -> None:
         """See https://discord.com/developers/docs/topics/gateway#interaction-create for more info."""
         await self.dispatch(self._event_factory.deserialize_interaction_create_event(shard, payload))
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 8ddcc613..e745ef0b 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -2959,6 +2959,7 @@ async def create_application_command(
         guild: undefined.UndefinedOr[snowflakes.SnowflakeishOr[guilds.PartialGuild]] = undefined.UNDEFINED,
         *,
         options: undefined.UndefinedOr[typing.Sequence[commands.CommandOption]] = undefined.UNDEFINED,
+        default_permission: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
     ) -> commands.Command:
         if guild is undefined.UNDEFINED:
             route = routes.POST_APPLICATION_COMMAND.compile(application=application)
@@ -2970,6 +2971,7 @@ async def create_application_command(
         body.put("name", name)
         body.put("description", description)
         body.put_array("options", options, conversion=self._entity_factory.serialize_command_option)
+        body.put("default_permission", default_permission)
 
         response = await self._request(route, json=body)
         assert isinstance(response, dict)
@@ -3039,6 +3041,67 @@ async def delete_application_command(
 
         await self._request(route)
 
+    async def fetch_application_guild_commands_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+    ) -> typing.Sequence[commands.GuildCommandPermissions]:
+        route = routes.GET_APPLICATION_GUILD_COMMANDS_PERMISSIONS.compile(application=application, guild=guild)
+        response = await self._request(route)
+        assert isinstance(response, list)
+        return [self._entity_factory.deserialize_guild_command_permissions(payload) for payload in response]
+
+    async def fetch_application_command_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+        command: snowflakes.SnowflakeishOr[commands.Command],
+    ) -> commands.GuildCommandPermissions:
+        route = routes.GET_APPLICATION_COMMAND_PERMISSIONS.compile(
+            application=application, guild=guild, command=command
+        )
+        response = await self._request(route)
+        assert isinstance(response, dict)
+        return self._entity_factory.deserialize_guild_command_permissions(response)
+
+    async def set_application_guild_commands_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+        permissions: typing.Mapping[
+            snowflakes.SnowflakeishOr[commands.Command], typing.Sequence[commands.CommandPermission]
+        ],
+    ) -> typing.Sequence[commands.GuildCommandPermissions]:
+        route = routes.PUT_APPLICATION_GUILD_COMMANDS_PERMISSIONS.compile(application=application, guild=guild)
+        body = [
+            {
+                "id": str(snowflakes.Snowflake(command)),
+                "permissions": [self._entity_factory.serialize_command_permission(permission) for permission in perms],
+            }
+            for command, perms in permissions.items()
+        ]
+        response = await self._request(route, json=body)
+
+        assert isinstance(response, list)
+        return [self._entity_factory.deserialize_guild_command_permissions(payload) for payload in response]
+
+    async def set_application_command_permissions(
+        self,
+        application: snowflakes.SnowflakeishOr[guilds.PartialApplication],
+        guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
+        command: snowflakes.SnowflakeishOr[commands.Command],
+        permissions: typing.Sequence[commands.CommandPermission],
+    ) -> commands.GuildCommandPermissions:
+        route = routes.PUT_APPLICATION_COMMAND_PERMISSIONS.compile(
+            application=application, guild=guild, command=command
+        )
+        body = data_binding.JSONObjectBuilder()
+        body.put_array("permissions", permissions, conversion=self._entity_factory.serialize_command_permission)
+        response = await self._request(route, json=body)
+
+        assert isinstance(response, dict)
+        return self._entity_factory.deserialize_guild_command_permissions(response)
+
     def interaction_deferred_builder(
         self, type_: typing.Union[base_interactions.ResponseType, int], /
     ) -> special_endpoints.InteractionDeferredBuilder:
diff --git a/hikari/impl/special_endpoints.py b/hikari/impl/special_endpoints.py
index 4ba32c04..81e5cadb 100644
--- a/hikari/impl/special_endpoints.py
+++ b/hikari/impl/special_endpoints.py
@@ -876,6 +876,7 @@ class CommandBuilder(special_endpoints.CommandBuilder):
 
     # Key-word only not-required arguments.
     _id: undefined.UndefinedOr[snowflakes.Snowflake] = attr.field(default=undefined.UNDEFINED, kw_only=True)
+    _default_permission: undefined.UndefinedOr[bool] = attr.field(default=undefined.UNDEFINED, kw_only=True)
 
     # Non-arguments.
     _options: typing.List[commands.CommandOption] = attr.field(factory=list, init=False)
@@ -888,6 +889,10 @@ def description(self) -> str:
     def id(self) -> undefined.UndefinedOr[snowflakes.Snowflake]:
         return self._id
 
+    @property
+    def default_permission(self) -> undefined.UndefinedOr[bool]:
+        return self._default_permission
+
     @property
     def options(self) -> typing.Sequence[commands.CommandOption]:
         return self._options.copy()
@@ -904,11 +909,15 @@ def set_id(self: _CommandBuilderT, id_: undefined.UndefinedOr[snowflakes.Snowfla
         self._id = snowflakes.Snowflake(id_) if id_ is not undefined.UNDEFINED else undefined.UNDEFINED
         return self
 
-    def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_binding.JSONObject:
-        options = [entity_factory.serialize_command_option(option) for option in self._options]
-        data: data_binding.JSONObject = {"name": self._name, "description": self._description, "options": options}
-
-        if self.id is not undefined.UNDEFINED:
-            data["id"] = str(self.id)
+    def set_default_permission(self: _CommandBuilderT, state: undefined.UndefinedOr[bool], /) -> _CommandBuilderT:
+        self._default_permission = state
+        return self
 
+    def build(self, entity_factory: entity_factory_.EntityFactory, /) -> data_binding.JSONObject:
+        data = data_binding.JSONObjectBuilder()
+        data["name"] = self._name
+        data["description"] = self._description
+        data.put_array("options", self._options, conversion=entity_factory.serialize_command_option)
+        data.put_snowflake("id", self._id)
+        data.put("default_permission", self._default_permission)
         return data
diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py
index b40a7ff6..3f1438e7 100644
--- a/hikari/internal/routes.py
+++ b/hikari/internal/routes.py
@@ -480,6 +480,19 @@ def compile_to_file(
     DELETE, "/applications/{application}/guilds/{guild}/commands/{command}"
 )
 
+GET_APPLICATION_GUILD_COMMANDS_PERMISSIONS: typing.Final[Route] = Route(
+    GET, "/applications/{application}/guilds/{guild}/commands/permissions"
+)
+GET_APPLICATION_COMMAND_PERMISSIONS: typing.Final[Route] = Route(
+    GET, "/applications/{application}/guilds/{guild}/commands/{command}/permissions"
+)
+PUT_APPLICATION_COMMAND_PERMISSIONS: typing.Final[Route] = Route(
+    PUT, "/applications/{application}/guilds/{guild}/commands/{command}/permissions"
+)
+PUT_APPLICATION_GUILD_COMMANDS_PERMISSIONS: typing.Final[Route] = Route(
+    PUT, "/applications/{application}/guilds/{guild}/commands/permissions"
+)
+
 # Interactions
 # For these endpoints "webhook" is the application ID.
 GET_INTERACTION_RESPONSE: typing.Final[Route] = Route(GET, "/webhooks/{webhook}/{token}/messages/@original")


Test patch:
diff --git a/tests/hikari/events/test_interaction_events.py b/tests/hikari/events/test_interaction_events.py
index 379dc0b0..c32b33db 100644
--- a/tests/hikari/events/test_interaction_events.py
+++ b/tests/hikari/events/test_interaction_events.py
@@ -23,14 +23,6 @@
 import mock
 
 from hikari.events import interaction_events
-from tests.hikari import hikari_test_helpers
-
-
-class TestCommandEvent:
-    def test_app_property(self):
-        mock_event = hikari_test_helpers.mock_class_namespace(interaction_events.CommandEvent, command=mock.Mock())()
-
-        assert mock_event.app is mock_event.command.app
 
 
 class TestInteractionCreateEvent:
diff --git a/tests/hikari/impl/test_cache.py b/tests/hikari/impl/test_cache.py
index 8a252338..ae35630e 100644
--- a/tests/hikari/impl/test_cache.py
+++ b/tests/hikari/impl/test_cache.py
@@ -129,18 +129,6 @@ def test_set_dm_channel_id(self, cache_impl):
 
         assert cache_impl._dm_channel_entries == {43123123: 12222}
 
-    def test_set_dm_channel_id_when_user_not_known(self, cache_impl):
-        cache_impl.set_dm_channel_id(StubModel(43123123), StubModel(12222))
-
-        assert cache_impl._dm_channel_entries == {}
-
-    def test_set_dm_channel_id_when_not_enabled(self, cache_impl):
-        cache_impl._user_entries = collections.FreezableDict({43123123: object()})
-        cache_impl._settings.components = 0
-        cache_impl.set_dm_channel_id(StubModel(43123123), StubModel(12222))
-
-        assert cache_impl._dm_channel_entries == {}
-
     def test__build_emoji(self, cache_impl):
         mock_user = mock.MagicMock(users.User)
         emoji_data = cache_utilities.KnownCustomEmojiData(
@@ -2666,6 +2654,11 @@ def test_update_message_for_unknown_partial_message(self, cache_impl):
     @pytest.mark.parametrize(
         ("name", "component", "expected"),
         [
+            ("clear_dm_channel_ids", config.CacheComponents.DM_CHANNEL_IDS, cache_utilities.EmptyCacheView()),
+            ("delete_dm_channel_id", config.CacheComponents.DM_CHANNEL_IDS, None),
+            ("get_dm_channel_id", config.CacheComponents.DM_CHANNEL_IDS, None),
+            ("get_dm_channel_ids_view", config.CacheComponents.DM_CHANNEL_IDS, cache_utilities.EmptyCacheView()),
+            ("set_dm_channel_id", config.CacheComponents.DM_CHANNEL_IDS, None),
             ("clear_emojis", config.CacheComponents.EMOJIS, cache_utilities.EmptyCacheView()),
             ("clear_emojis_for_guild", config.CacheComponents.EMOJIS, cache_utilities.EmptyCacheView()),
             ("clear_guild_channels", config.CacheComponents.GUILD_CHANNELS, cache_utilities.EmptyCacheView()),
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index b209eb30..2453449f 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -2698,6 +2698,7 @@ def command_payload(self):
             "guild_id": "49949494",
             "name": "good name",
             "description": "very good description",
+            "default_permission": False,
             "options": [
                 {
                     "type": 1,
@@ -2725,6 +2726,7 @@ def test_deserialize_command(self, entity_factory_impl, mock_app, command_payloa
         assert command.guild_id == 49949494
         assert command.name == "good name"
         assert command.description == "very good description"
+        assert command.default_permission is False
 
         # CommandOption
         assert len(command.options) == 1
@@ -2768,7 +2770,7 @@ def test_deserialize_command_with_passed_through_guild_id(self, entity_factory_i
 
         assert command.guild_id == 123123
 
-    def test_deserialize_command_with_null_values(self, entity_factory_impl):
+    def test_deserialize_command_with_null_and_unset_values(self, entity_factory_impl):
         payload = {
             "id": "1231231231",
             "application_id": "12354123",
@@ -2781,8 +2783,42 @@ def test_deserialize_command_with_null_values(self, entity_factory_impl):
         command = entity_factory_impl.deserialize_command(payload)
 
         assert command.options is None
+        assert command.default_permission is True
         assert isinstance(command, commands.Command)
 
+    @pytest.fixture()
+    def guild_command_permissions_payload(self):
+        return {
+            "id": "123321",
+            "application_id": "431321123",
+            "guild_id": "323223322332",
+            "permissions": [{"id": "22222", "type": 1, "permission": True}],
+        }
+
+    def test_deserialize_guild_command_permissions(self, entity_factory_impl, guild_command_permissions_payload):
+        command = entity_factory_impl.deserialize_guild_command_permissions(guild_command_permissions_payload)
+
+        assert command.command_id == 123321
+        assert command.application_id == 431321123
+        assert command.guild_id == 323223322332
+
+        # CommandPermission
+        assert len(command.permissions) == 1
+        permission = command.permissions[0]
+        assert permission.id == 22222
+        assert permission.type is commands.CommandPermissionType.ROLE
+        assert permission.has_access is True
+        assert isinstance(permission, commands.CommandPermission)
+
+    def test_serialize_command_permission(self, entity_factory_impl):
+        command = commands.CommandPermission(type=commands.CommandPermissionType.ROLE, has_access=True, id=123321)
+
+        assert entity_factory_impl.serialize_command_permission(command) == {
+            "type": 1,
+            "id": "123321",
+            "permission": True,
+        }
+
     @pytest.fixture()
     def partial_interaction_payload(self):
         return {
diff --git a/tests/hikari/impl/test_event_factory.py b/tests/hikari/impl/test_event_factory.py
index 6389c39a..a6727b53 100644
--- a/tests/hikari/impl/test_event_factory.py
+++ b/tests/hikari/impl/test_event_factory.py
@@ -449,36 +449,6 @@ def test_deserialize_presence_update_event_with_partial_user_object(self, event_
     # INTERACTION EVENTS #
     ######################
 
-    def test_deserialize_command_create_event(self, event_factory, mock_app, mock_shard):
-        payload = {"id": "123"}
-
-        result = event_factory.deserialize_command_create_event(mock_shard, payload)
-
-        mock_app.entity_factory.deserialize_command.assert_called_once_with(payload)
-        assert result.shard is mock_shard
-        assert result.command is mock_app.entity_factory.deserialize_command.return_value
-        assert isinstance(result, interaction_events.CommandCreateEvent)
-
-    def test_deserialize_command_update_event(self, event_factory, mock_app, mock_shard):
-        payload = {"id": "12344"}
-
-        result = event_factory.deserialize_command_update_event(mock_shard, payload)
-
-        mock_app.entity_factory.deserialize_command.assert_called_once_with(payload)
-        assert result.shard is mock_shard
-        assert result.command is mock_app.entity_factory.deserialize_command.return_value
-        assert isinstance(result, interaction_events.CommandUpdateEvent)
-
-    def test_deserialize_command_delete_event(self, event_factory, mock_app, mock_shard):
-        payload = {"id": "1561232344"}
-
-        result = event_factory.deserialize_command_delete_event(mock_shard, payload)
-
-        mock_app.entity_factory.deserialize_command.assert_called_once_with(payload)
-        assert result.shard is mock_shard
-        assert result.command is mock_app.entity_factory.deserialize_command.return_value
-        assert isinstance(result, interaction_events.CommandDeleteEvent)
-
     def test_deserialize_interaction_create_event(self, event_factory, mock_app, mock_shard):
         payload = {"id": "1561232344"}
 
diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py
index 62374556..8ec18f8d 100644
--- a/tests/hikari/impl/test_event_manager.py
+++ b/tests/hikari/impl/test_event_manager.py
@@ -1045,33 +1045,6 @@ async def test_on_webhooks_update(self, event_manager, shard, event_factory):
         event_factory.deserialize_webhook_update_event.assert_called_once_with(shard, payload)
         event_manager.dispatch.assert_awaited_once_with(event)
 
-    @pytest.mark.asyncio()
-    async def test_on_application_command_create(self, event_manager, shard, event_factory):
-        payload = {"id": "4544333334dd44"}
-
-        await event_manager.on_application_command_create(shard, payload)
-
-        event_factory.deserialize_command_create_event.assert_called_once_with(shard, payload)
-        event_manager.dispatch.assert_awaited_once_with(event_factory.deserialize_command_create_event.return_value)
-
-    @pytest.mark.asyncio()
-    async def test_on_application_command_update(self, event_manager, shard, event_factory):
-        payload = {"id": "454433333444"}
-
-        await event_manager.on_application_command_update(shard, payload)
-
-        event_factory.deserialize_command_update_event.assert_called_once_with(shard, payload)
-        event_manager.dispatch.assert_awaited_once_with(event_factory.deserialize_command_update_event.return_value)
-
-    @pytest.mark.asyncio()
-    async def test_on_application_command_delete(self, event_manager, shard, event_factory):
-        payload = {"id": "4544444"}
-
-        await event_manager.on_application_command_delete(shard, payload)
-
-        event_factory.deserialize_command_delete_event.assert_called_once_with(shard, payload)
-        event_manager.dispatch.assert_awaited_once_with(event_factory.deserialize_command_delete_event.return_value)
-
     @pytest.mark.asyncio()
     async def test_on_interaction_create(self, event_manager, shard, event_factory):
         payload = {"id": "123"}
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index f99ea9bb..8ca109d2 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -3537,6 +3537,67 @@ async def test_delete_application_command_without_guild(self, rest_client):
 
         rest_client._request.assert_awaited_once_with(expected_route)
 
+    async def test_fetch_application_guild_commands_permissions(self, rest_client):
+        expected_route = routes.GET_APPLICATION_GUILD_COMMANDS_PERMISSIONS.compile(application=321431, guild=54123)
+        mock_command_payload = object()
+        rest_client._request = mock.AsyncMock(return_value=[mock_command_payload])
+
+        result = await rest_client.fetch_application_guild_commands_permissions(321431, 54123)
+
+        assert result == [rest_client._entity_factory.deserialize_guild_command_permissions.return_value]
+        rest_client._entity_factory.deserialize_guild_command_permissions.assert_called_once_with(mock_command_payload)
+        rest_client._request.assert_awaited_once_with(expected_route)
+
+    async def test_fetch_application_command_permissions(self, rest_client):
+        expected_route = routes.GET_APPLICATION_COMMAND_PERMISSIONS.compile(
+            application=543421, guild=123321321, command=543123
+        )
+        mock_command_payload = {"id": "9393939393"}
+        rest_client._request = mock.AsyncMock(return_value=mock_command_payload)
+
+        result = await rest_client.fetch_application_command_permissions(543421, 123321321, 543123)
+
+        assert result is rest_client._entity_factory.deserialize_guild_command_permissions.return_value
+        rest_client._entity_factory.deserialize_guild_command_permissions.assert_called_once_with(mock_command_payload)
+        rest_client._request.assert_awaited_once_with(expected_route)
+
+    async def test_set_application_guild_commands_permissions(self, rest_client):
+        expected_route = routes.PUT_APPLICATION_GUILD_COMMANDS_PERMISSIONS.compile(application=321123, guild=542123)
+        mock_command_payload = object()
+        mock_permission = object()
+        rest_client._request = mock.AsyncMock(return_value=[mock_command_payload])
+
+        result = await rest_client.set_application_guild_commands_permissions(
+            321123, 542123, {564123123: [mock_permission]}
+        )
+
+        assert result == [rest_client._entity_factory.deserialize_guild_command_permissions.return_value]
+        rest_client._entity_factory.serialize_command_permission.assert_called_once_with(mock_permission)
+        rest_client._entity_factory.deserialize_guild_command_permissions.assert_called_once_with(mock_command_payload)
+        rest_client._request.assert_awaited_once_with(
+            expected_route,
+            json=[
+                {
+                    "id": "564123123",
+                    "permissions": [rest_client._entity_factory.serialize_command_permission.return_value],
+                }
+            ],
+        )
+
+    async def test_set_application_command_permissions(self, rest_client):
+        route = routes.PUT_APPLICATION_COMMAND_PERMISSIONS.compile(application=2321, guild=431, command=666666)
+        mock_permission = object()
+        mock_command_payload = {"id": "29292929"}
+        rest_client._request = mock.AsyncMock(return_value=mock_command_payload)
+
+        result = await rest_client.set_application_command_permissions(2321, 431, 666666, [mock_permission])
+
+        assert result is rest_client._entity_factory.deserialize_guild_command_permissions.return_value
+        rest_client._entity_factory.deserialize_guild_command_permissions.assert_called_once_with(mock_command_payload)
+        rest_client._request.assert_awaited_once_with(
+            route, json={"permissions": [rest_client._entity_factory.serialize_command_permission.return_value]}
+        )
+
     def test_interaction_deferred_builder(self, rest_client):
         result = rest_client.interaction_deferred_builder(5)
 
diff --git a/tests/hikari/impl/test_special_endpoints.py b/tests/hikari/impl/test_special_endpoints.py
index 21ef13a2..d0d2b7d5 100644
--- a/tests/hikari/impl/test_special_endpoints.py
+++ b/tests/hikari/impl/test_special_endpoints.py
@@ -187,10 +187,20 @@ def test_id_property(self):
 
         assert builder.id == 3212123
 
+    def test_default_permission(self):
+        builder = special_endpoints.CommandBuilder("oksksksk", "kfdkodfokfd").set_default_permission(True)
+
+        assert builder.default_permission is True
+
     def test_build_with_optional_data(self):
         mock_entity_factory = mock.Mock()
         mock_option = object()
-        builder = special_endpoints.CommandBuilder("we are number", "one").add_option(mock_option).set_id(3412312)
+        builder = (
+            special_endpoints.CommandBuilder("we are number", "one")
+            .add_option(mock_option)
+            .set_id(3412312)
+            .set_default_permission(False)
+        )
 
         result = builder.build(mock_entity_factory)
 
@@ -198,6 +208,7 @@ def test_build_with_optional_data(self):
         assert result == {
             "name": "we are number",
             "description": "one",
+            "default_permission": False,
             "options": [mock_entity_factory.serialize_command_option.return_value],
             "id": "3412312",
         }
@@ -205,6 +216,6 @@ def test_build_with_optional_data(self):
     def test_build_without_optional_data(self):
         builder = special_endpoints.CommandBuilder("we are numberr", "oner")
 
-        result = builder.build(object())
+        result = builder.build(mock.Mock())
 
         assert result == {"name": "we are numberr", "description": "oner", "options": []}
diff --git a/tests/hikari/test_commands.py b/tests/hikari/test_commands.py
index 4b27d832..9e0716ad 100644
--- a/tests/hikari/test_commands.py
+++ b/tests/hikari/test_commands.py
@@ -44,6 +44,7 @@ def mock_command(self, mock_app):
             description="very descript",
             options=[],
             guild_id=snowflakes.Snowflake(31231235),
+            default_permission=False,
         )
 
     @pytest.mark.asyncio()
@@ -115,3 +116,23 @@ async def test_delete_when_guild_id_is_none(self, mock_command, mock_app):
         await mock_command.delete()
 
         mock_app.rest.delete_application_command.assert_awaited_once_with(65234123, 34123123, undefined.UNDEFINED)
+
+    @pytest.mark.asyncio()
+    async def test_fetch_guild_permissions(self, mock_command, mock_app):
+        result = await mock_command.fetch_guild_permissions(123321)
+
+        assert result is mock_app.rest.fetch_application_command_permissions.return_value
+        mock_app.rest.fetch_application_command_permissions.assert_awaited_once_with(
+            application=mock_command.application_id, guild=123321, command=mock_command.id
+        )
+
+    @pytest.mark.asyncio()
+    async def test_set_guild_permissions(self, mock_command, mock_app):
+        mock_permissions = object()
+
+        result = await mock_command.set_guild_permissions(312123, mock_permissions)
+
+        assert result is mock_app.rest.set_application_command_permissions.return_value
+        mock_app.rest.set_application_command_permissions.assert_awaited_once_with(
+            application=mock_command.application_id, guild=312123, command=mock_command.id, permissions=mock_permissions
+        )