Repository: hikari-py/hikari
Pull request #1621 (2023-05-25 11:22:45)

Add new interaction channel attribute
### 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.


davfsa: I am not sure whether to get rid of `BaseInteraction.get_channel` and `BaseInteraction.fetch_channel`, as they are a bit replaced here. The only difference is the more concrete type

Patch:
diff --git a/changes/1621.breaking.md b/changes/1621.breaking.md
new file mode 100644
index 00000000..40485385
--- /dev/null
+++ b/changes/1621.breaking.md
@@ -0,0 +1,1 @@
+Remove `PartialInteraction.get_channel` and `PartialInteraction.fetch_channel`. You can directly use `PartialInteraction.channel` instead
diff --git a/changes/1621.feature.md b/changes/1621.feature.md
new file mode 100644
index 00000000..f7ac1026
--- /dev/null
+++ b/changes/1621.feature.md
@@ -0,0 +1,5 @@
+Several new `PartialInteraction` features:
+  - Add new `PartialInteraction.channel` attribute
+  - Moved common interaction fields to `PartialInteraction`
+  - `app_permissions` is now available for all interaction types
+  - Add missing fields to `InteractionChannel`
diff --git a/examples/simple_dashboard.py b/examples/simple_dashboard.py
deleted file mode 100644
index 9d0924a9..00000000
--- a/examples/simple_dashboard.py
+++ /dev/null
@@ -1,117 +0,0 @@
-# Hikari Examples - A collection of examples for Hikari.
-#
-# To the extent possible under law, the author(s) have dedicated all copyright
-# and related and neighboring rights to this software to the public domain worldwide.
-# This software is distributed without any warranty.
-#
-# You should have received a copy of the CC0 Public Domain Dedication along with this software.
-# If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
-"""A simple bot to demonstrate how to use rillrate with hikari to make a web dashboard for the bot.
-
-Just visit `http://localhost:6361/ui/` to explore your dashboard!
-"""
-
-from __future__ import annotations
-
-import logging
-import os
-
-import rillrate
-from rillrate import prime as rr_prime
-
-import hikari
-
-PREFIX = ","
-
-# Name used to group dashboards.
-# You could have multiple packages for different applications, such as a package for the bot
-# dashboards, and another package for a web server running alongside the bot.
-PACKAGE = "Rillrate Example"
-# Dashboards are a part inside of package, they can be used to group different types of
-# dashboards that you may want to use, like a dashboard for system status, another dashboard
-# for cache status, and another one to configure features or trigger actions on the bot.
-DASHBOARD = "Control Panel"
-# These are menus inside the dashboard, you can use them to group specific sets
-# of data inside the same dashboard.
-GROUP_CONFIG = "1 - Example"
-# All the 3 configurable namespaces are sorted alphabetically.
-
-
-# Class with all our dashboard logic
-class RillRateDashboard:
-    """Global data shared across the entire bot, used to store dashboard values."""
-
-    __slots__ = ("logger", "value", "selector", "slider")
-
-    def __init__(self) -> None:
-        self.logger = logging.getLogger("dashboard")
-        self.value = 0
-
-        # Install rillrate - Spins up the rillrate service in a separate thread, making it non-blocking :)
-        rillrate.install()
-
-        # Register the dashboard objects
-        dummy_values = [str(i) for i in range(0, 256 + 1, 32)]
-        self.selector = rr_prime.Selector(
-            f"{PACKAGE}.{DASHBOARD}.{GROUP_CONFIG}.Selector", label="Choose!", options=dummy_values
-        )
-        self.slider = rr_prime.Slider(
-            f"{PACKAGE}.{DASHBOARD}.{GROUP_CONFIG}.Slider", label="More fine grain control", min=0, max=256, step=2
-        )
-
-        # Add sync callbacks - This way we tell rillrate what functions to call when a sync event occurs
-        self.selector.sync_callback(self._selector_callback)
-        self.slider.sync_callback(self._slider_callback)
-
-    def _selector_callback(self, activity: rillrate.Activity, action: rillrate.Action) -> None:
-        self.logger.info("Selector activity: %s | action = %s", activity, action)
-
-        if action is not None:
-            value = int(action.value)
-            self.logger.info("Selected: %s", value)
-
-            # Update the slider too, so they show the same value.
-            self.slider.apply(value)
-
-            # Overwrite the current stored value on the global data with the new selected value.
-            self.value = value
-
-    def _slider_callback(self, activity: rillrate.Activity, action: rillrate.Action) -> None:
-        self.logger.info("Slider activity: %s | action = %s", activity, action)
-
-        if action is not None:
-            value = int(action.value)
-            self.logger.info("Slided to: %s", value)
-
-            # Update the selector too, so they show the same value.
-            # It is important to note that since not all values are present in the selector, it might be empty sometimes
-            self.selector.apply(str(value))
-
-            # Overwrite the current stored value on the global data with the new selected value.
-            self.value = value
-
-
-bot = hikari.GatewayBot(token=os.environ["BOT_TOKEN"])
-dashboard = RillRateDashboard()
-
-
-def is_command(cmd_name: str, content: str) -> bool:
-    """Check if the message sent is a valid command."""
-    return content == f"{PREFIX}{cmd_name}"
-
-
-@bot.listen()
-async def message(event: hikari.GuildMessageCreateEvent) -> None:
-    """Listen for messages being created."""
-    if not event.is_human or not event.content:
-        return
-
-    # Command Framework 101 :D
-    if event.content.startswith(PREFIX):
-        if is_command("ping", event.content):
-            await event.message.respond("Pong!")
-        elif is_command("value", event.content):
-            await event.message.respond(f"Current value: {dashboard.value}")
-
-
-bot.run()
diff --git a/examples/simple_dashboard_requirements.txt b/examples/simple_dashboard_requirements.txt
deleted file mode 100644
index 3bf0b99e..00000000
--- a/examples/simple_dashboard_requirements.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-rillrate==0.40
diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py
index 42b78f75..0b6c35ee 100644
--- a/hikari/api/entity_factory.py
+++ b/hikari/api/entity_factory.py
@@ -1350,21 +1350,6 @@ def serialize_command_permission(self, permission: commands.CommandPermission) -
             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.
-
-        Parameters
-        ----------
-        payload
-            The JSON payload to deserialize.
-
-        Returns
-        -------
-        hikari.interactions.base_interactions.PartialInteraction
-            The deserialized partial interaction object.
-        """
-
     @abc.abstractmethod
     def deserialize_command_interaction(
         self, payload: data_binding.JSONObject
@@ -1420,8 +1405,7 @@ def deserialize_interaction(self, payload: data_binding.JSONObject) -> base_inte
 
         !!! note
             This isn't required to implement logic for deserializing
-            PING interactions and if you want to unmarshal those
-            [`hikari.api.entity_factory.EntityFactory.deserialize_partial_interaction`][] should be compatible.
+            PING interactions.
 
         Parameters
         ----------
diff --git a/hikari/channels.py b/hikari/channels.py
index 72d5af2d..b2541b11 100644
--- a/hikari/channels.py
+++ b/hikari/channels.py
@@ -34,6 +34,7 @@
     "PermissibleGuildChannel",
     "TextableChannel",
     "TextableGuildChannel",
+    "ThreadMetadata",
     "PrivateChannel",
     "DMChannel",
     "GroupDMChannel",
@@ -1579,6 +1580,47 @@ class ThreadMember:
     """
 
 
+@attrs.define(kw_only=True, weakref_slot=False)
+class ThreadMetadata:
+    """Represents a thread's metadata."""
+
+    is_archived: bool = attrs.field(repr=True)
+    """Whether the thread is archived."""
+
+    is_invitable: bool = attrs.field(repr=True)
+    """Whether the thread is invitable by non moderators.
+
+    This only applies to private threads, otherwise always [`True`][].
+    """
+
+    auto_archive_duration: datetime.timedelta = attrs.field(repr=True)
+    """How long the thread will be left inactive before being automatically archived.
+
+    As of writing this may either 1 hour, 1 day, 3 days or 1 week.
+    """
+
+    archive_timestamp: datetime.datetime = attrs.field(repr=True)
+    """When the thread's archived state was last changed.
+
+    !!! note
+        If the thread has never been archived then this will be the thread's
+        creation date and this will be changed when a thread is unarchived.
+    """
+
+    is_locked: bool = attrs.field(repr=True)
+    """Whether the thread is locked.
+
+    When a thread is locked, only users with [`hikari.permissions.Permissions.MANAGE_THREADS`][] permission
+    can un-archive it.
+    """
+
+    created_at: typing.Optional[datetime.datetime] = attrs.field(repr=True)
+    """When the thread was created.
+
+    Will be [`None`][] for threads created before 2022-01-09.
+    """
+
+
 @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False)
 class GuildThreadChannel(TextableGuildChannel):
     """Base class for all guild thread channels."""
@@ -1625,30 +1667,6 @@ class GuildThreadChannel(TextableGuildChannel):
         This stop counting at 50.
     """
 
-    is_archived: bool = attrs.field(eq=False, hash=False, repr=True)
-    """Whether the thread is archived."""
-
-    auto_archive_duration: datetime.timedelta = attrs.field(eq=False, hash=False, repr=True)
-    """How long the thread will be left inactive before being automatically archived.
-
-    As of writing this may either 1 hour, 1 day, 3 days or 1 week.
-    """
-
-    archive_timestamp: datetime.datetime = attrs.field(eq=False, hash=False, repr=True)
-    """When the thread's archived state was last changed.
-
-    !!! note
-        If the thread has never been archived then this will be the thread's
-        creation date and this will be changed when a thread is unarchived.
-    """
-
-    is_locked: bool = attrs.field(eq=False, hash=False, repr=True)
-    """Whether the thread is locked.
-
-    When a thread is locked, only users with [`hikari.permissions.Permissions.MANAGE_THREADS`][] permission
-    can un-archive it.
-    """
-
     member: typing.Optional[ThreadMember] = attrs.field(eq=False, hash=False, repr=True)
     """Thread member object for the current user, if they are in the thread.
 
@@ -1663,11 +1681,48 @@ class GuildThreadChannel(TextableGuildChannel):
     parent_id: snowflakes.Snowflake = attrs.field(eq=False, hash=False, repr=True)
     """Id of this thread's textable parent channel."""
 
-    thread_created_at: typing.Optional[datetime.datetime] = attrs.field(eq=False, hash=False, repr=True)
-    """When the thread was created.
+    metadata: ThreadMetadata = attrs.field(eq=False, hash=False, repr=False)
+    """This threads metadata."""
 
-    Will be [`None`][] for threads created before 2020-01-09.
-    """
+    @property
+    def is_archived(self) -> bool:
+        """Whether the thread is archived."""
+        return self.metadata.is_archived
+
+    @property
+    def auto_archive_duration(self) -> datetime.timedelta:
+        """How long the thread will be left inactive before being automatically archived.
+
+        As of writing this may either 1 hour, 1 day, 3 days or 1 week.
+        """
+        return self.metadata.auto_archive_duration
+
+    @property
+    def archive_timestamp(self) -> datetime.datetime:
+        """When the thread's archived state was last changed.
+
+        !!! note
+            If the thread has never been archived then this will be the thread's
+            creation date and this will be changed when a thread is unarchived.
+        """
+        return self.metadata.archive_timestamp
+
+    @property
+    def is_locked(self) -> bool:
+        """Whether the thread is locked.
+
+        When a thread is locked, only users with [`hikari.permissions.Permissions.MANAGE_THREADS`][] permission
+        can un-archive it.
+        """
+        return self.metadata.is_locked
+
+    @property
+    def thread_created_at(self) -> typing.Optional[datetime.datetime]:
+        """When the thread was created.
+
+        Will be [`None`][] for threads created before 2022-01-09.
+        """
+        return self.metadata.created_at
 
 
 class GuildNewsThread(GuildThreadChannel):
@@ -1700,5 +1755,10 @@ class GuildPublicThread(GuildThreadChannel):
 class GuildPrivateThread(GuildThreadChannel):
     """Represents a guild private thread."""
 
-    is_invitable: bool = attrs.field(eq=False, hash=False, repr=True)
-    """Whether non-moderators can add other non-moderators to a private thread."""
+    @property
+    def is_invitable(self) -> bool:
+        """Whether the thread is invitable by non moderators.
+
+        This only applies to private threads.
+        """
+        return self.metadata.is_invitable
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 881bf3e4..ac6ef525 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1343,6 +1343,20 @@ def deserialize_guild_thread(
         _LOGGER.debug(f"Unrecognised thread channel type {channel_type}")
         raise errors.UnrecognisedEntityError(f"Unrecognised thread channel type {channel_type}")
 
+    def _deserialize_thread_metadata(self, payload: data_binding.JSONObject) -> channel_models.ThreadMetadata:
+        created_at: typing.Optional[datetime.datetime] = None
+        if raw_created_at := payload.get("create_timestamp"):
+            created_at = time.iso8601_datetime_string_to_datetime(raw_created_at)
+
+        return channel_models.ThreadMetadata(
+            is_archived=payload["archived"],
+            is_invitable=payload.get("invitable", True),
+            archive_timestamp=time.iso8601_datetime_string_to_datetime(payload["archive_timestamp"]),
+            is_locked=payload["locked"],
+            created_at=created_at,
+            auto_archive_duration=datetime.timedelta(minutes=payload["auto_archive_duration"]),
+        )
+
     def deserialize_guild_news_thread(
         self,
         payload: data_binding.JSONObject,
@@ -1360,15 +1374,10 @@ def deserialize_guild_news_thread(
         if (raw_last_pin_timestamp := payload.get("last_pin_timestamp")) is not None:
             last_pin_timestamp = time.iso8601_datetime_string_to_datetime(raw_last_pin_timestamp)
 
-        metadata = payload["thread_metadata"]
         actual_member = member if member is not undefined.UNDEFINED else None
         if member_payload := payload.get("member"):
             actual_member = self.deserialize_thread_member(member_payload, thread_id=channel_fields.id, user_id=user_id)
 
-        thread_created_at: typing.Optional[datetime.datetime] = None
-        if raw_thread_created_at := metadata.get("create_timestamp"):
-            thread_created_at = time.iso8601_datetime_string_to_datetime(raw_thread_created_at)
-
         assert channel_fields.parent_id is not None
         return channel_models.GuildNewsThread(
             app=self._app,
@@ -1382,13 +1391,9 @@ def deserialize_guild_news_thread(
             rate_limit_per_user=datetime.timedelta(seconds=payload.get("rate_limit_per_user", 0)),
             approximate_member_count=int(payload["member_count"]),
             approximate_message_count=int(payload["message_count"]),
-            is_archived=metadata["archived"],
-            auto_archive_duration=datetime.timedelta(minutes=metadata["auto_archive_duration"]),
-            archive_timestamp=time.iso8601_datetime_string_to_datetime(metadata["archive_timestamp"]),
-            is_locked=metadata["locked"],
             member=actual_member,
             owner_id=snowflakes.Snowflake(payload["owner_id"]),
-            thread_created_at=thread_created_at,
+            metadata=self._deserialize_thread_metadata(payload["thread_metadata"]),
         )
 
     def deserialize_guild_public_thread(
@@ -1414,15 +1419,10 @@ def deserialize_guild_public_thread(
         if (raw_last_pin_timestamp := payload.get("last_pin_timestamp")) is not None:
             last_pin_timestamp = time.iso8601_datetime_string_to_datetime(raw_last_pin_timestamp)
 
-        metadata = payload["thread_metadata"]
         actual_member = member if member is not undefined.UNDEFINED else None
         if member_payload := payload.get("member"):
             actual_member = self.deserialize_thread_member(member_payload, thread_id=channel_fields.id, user_id=user_id)
 
-        thread_created_at: typing.Optional[datetime.datetime] = None
-        if raw_thread_created_at := metadata.get("create_timestamp"):
-            thread_created_at = time.iso8601_datetime_string_to_datetime(raw_thread_created_at)
-
         assert channel_fields.parent_id is not None
         return channel_models.GuildPublicThread(
             app=self._app,
@@ -1436,15 +1436,11 @@ def deserialize_guild_public_thread(
             rate_limit_per_user=datetime.timedelta(seconds=payload.get("rate_limit_per_user", 0)),
             approximate_member_count=int(payload["member_count"]),
             approximate_message_count=int(payload["message_count"]),
-            is_archived=metadata["archived"],
-            auto_archive_duration=datetime.timedelta(minutes=metadata["auto_archive_duration"]),
-            archive_timestamp=time.iso8601_datetime_string_to_datetime(metadata["archive_timestamp"]),
-            is_locked=metadata["locked"],
             member=actual_member,
             owner_id=snowflakes.Snowflake(payload["owner_id"]),
-            thread_created_at=thread_created_at,
             applied_tag_ids=[snowflakes.Snowflake(p) for p in payload.get("applied_tags", ())],
             flags=flags,
+            metadata=self._deserialize_thread_metadata(payload["thread_metadata"]),
         )
 
     def deserialize_guild_private_thread(
@@ -1464,15 +1460,10 @@ def deserialize_guild_private_thread(
         if (raw_last_pin_timestamp := payload.get("last_pin_timestamp")) is not None:
             last_pin_timestamp = time.iso8601_datetime_string_to_datetime(raw_last_pin_timestamp)
 
-        metadata = payload["thread_metadata"]
         actual_member = member if member is not undefined.UNDEFINED else None
         if member_payload := payload.get("member"):
             actual_member = self.deserialize_thread_member(member_payload, thread_id=channel_fields.id, user_id=user_id)
 
-        thread_created_at: typing.Optional[datetime.datetime] = None
-        if raw_thread_created_at := metadata.get("create_timestamp"):
-            thread_created_at = time.iso8601_datetime_string_to_datetime(raw_thread_created_at)
-
         assert channel_fields.parent_id is not None
         return channel_models.GuildPrivateThread(
             app=self._app,
@@ -1486,14 +1477,9 @@ def deserialize_guild_private_thread(
             rate_limit_per_user=datetime.timedelta(seconds=payload.get("rate_limit_per_user", 0)),
             approximate_member_count=int(payload["member_count"]),
             approximate_message_count=int(payload["message_count"]),
-            is_archived=metadata["archived"],
-            auto_archive_duration=datetime.timedelta(minutes=metadata["auto_archive_duration"]),
-            archive_timestamp=time.iso8601_datetime_string_to_datetime(metadata["archive_timestamp"]),
-            is_locked=metadata["locked"],
             member=actual_member,
             owner_id=snowflakes.Snowflake(payload["owner_id"]),
-            is_invitable=metadata["invitable"],
-            thread_created_at=thread_created_at,
+            metadata=self._deserialize_thread_metadata(payload["thread_metadata"]),
         )
 
     def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_instances.StageInstance:
@@ -2429,25 +2415,6 @@ def deserialize_guild_command_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:
-        authorizing_integration_owners = {
-            application_models.ApplicationIntegrationType(int(integration_type)): snowflakes.Snowflake(
-                integration_owner_id
-            )
-            for integration_type, integration_owner_id in payload["authorizing_integration_owners"].items()
-        }
-
-        return base_interactions.PartialInteraction(
-            app=self._app,
-            id=snowflakes.Snowflake(payload["id"]),
-            type=base_interactions.InteractionType(payload["type"]),
-            token=payload["token"],
-            version=payload["version"],
-            application_id=snowflakes.Snowflake(payload["application_id"]),
-            authorizing_integration_owners=authorizing_integration_owners,
-            context=application_models.ApplicationContextType(payload["context"]),
-        )
-
     def _deserialize_interaction_command_option(
         self, payload: data_binding.JSONObject
     ) -> command_interactions.CommandInteractionOption:
@@ -2532,6 +2499,27 @@ def _deserialize_interaction_member(
             guild_flags=guild_flags,
         )
 
+    def _deserialize_interaction_channel(
+        self, payload: data_binding.JSONObject
+    ) -> base_interactions.InteractionChannel:
+        channel_id = snowflakes.Snowflake(payload["id"])
+
+        thread_metadata = (
+            self._deserialize_thread_metadata(payload["thread_metadata"]) if "thread_metadata" in payload else None
+        )
+
+        return base_interactions.InteractionChannel(
+            app=self._app,
+            id=channel_id,
+            type=channel_models.ChannelType(payload["type"]),
+            name=payload.get("name"),
+            # Even tho (at the time of writing) it is documented that these partial channels will always contain
+            # this field, they are explicitly avoided for DM channels.
+            permissions=permission_models.Permissions(int(payload.get("permissions", 0))),
+            parent_id=snowflakes.Snowflake(payload["parent_id"]) if payload.get("parent_id") else None,
+            thread_metadata=thread_metadata,
+        )
+
     def _deserialize_resolved_option_data(
         self, payload: data_binding.JSONObject, *, guild_id: typing.Optional[snowflakes.Snowflake] = None
     ) -> base_interactions.ResolvedOptionData:
@@ -2539,21 +2527,7 @@ def _deserialize_resolved_option_data(
         if raw_channels := payload.get("channels"):
             for channel_payload in raw_channels.values():
                 channel_id = snowflakes.Snowflake(channel_payload["id"])
-
-                # Even tho (at the time of writing) it is documented that these partial channels will always contain
-                # this field, they are explicitly avoided for DM channels.
-                if "permissions" in channel_payload:
-                    permissions = permission_models.Permissions(int(channel_payload["permissions"]))
-                else:
-                    permissions = permission_models.Permissions.NONE
-
-                channels[channel_id] = base_interactions.InteractionChannel(
-                    app=self._app,
-                    id=channel_id,
-                    type=channel_models.ChannelType(channel_payload["type"]),
-                    name=channel_payload.get("name"),
-                    permissions=permissions,
-                )
+                channels[channel_id] = self._deserialize_interaction_channel(channel_payload)
 
         if raw_users := payload.get("users"):
             users = {u.id: u for u in map(self.deserialize_user, raw_users.values())}
@@ -2641,7 +2615,7 @@ def deserialize_command_interaction(
             guild_id=guild_id,
             guild_locale=locales.Locale(payload["guild_locale"]) if "guild_locale" in payload else None,
             locale=locales.Locale(payload["locale"]),
-            channel_id=snowflakes.Snowflake(payload["channel_id"]),
+            channel=self._deserialize_interaction_channel(payload["channel"]),
             member=member,
             user=user,
             token=payload["token"],
@@ -2694,7 +2668,7 @@ def deserialize_autocomplete_interaction(
             id=snowflakes.Snowflake(payload["id"]),
             type=base_interactions.InteractionType(payload["type"]),
             guild_id=guild_id,
-            channel_id=snowflakes.Snowflake(payload["channel_id"]),
+            channel=self._deserialize_interaction_channel(payload["channel"]),
             member=member,
             user=user,
             token=payload["token"],
@@ -2704,6 +2678,7 @@ def deserialize_autocomplete_interaction(
             command_type=commands.CommandType(data_payload.get("type", commands.CommandType.SLASH)),
             options=options,
             locale=locales.Locale(payload["locale"]),
+            app_permissions=permission_models.Permissions(payload["app_permissions"]),
             guild_locale=locales.Locale(payload["guild_locale"]) if "guild_locale" in payload else None,
             registered_guild_id=snowflakes.Snowflake(data_payload["guild_id"]) if "guild_id" in data_payload else None,
             entitlements=[self.deserialize_entitlement(entitlement) for entitlement in payload.get("entitlements", ())],
@@ -2749,7 +2724,7 @@ def deserialize_modal_interaction(self, payload: data_binding.JSONObject) -> mod
             app_permissions=permission_models.Permissions(payload["app_permissions"]),
             guild_locale=locales.Locale(payload["guild_locale"]) if "guild_locale" in payload else None,
             locale=locales.Locale(payload["locale"]),
-            channel_id=snowflakes.Snowflake(payload["channel_id"]),
+            channel=self._deserialize_interaction_channel(payload["channel"]),
             member=member,
             user=user,
             token=payload["token"],
@@ -2845,7 +2820,7 @@ def deserialize_component_interaction(
             id=snowflakes.Snowflake(payload["id"]),
             type=base_interactions.InteractionType(payload["type"]),
             guild_id=guild_id,
-            channel_id=snowflakes.Snowflake(payload["channel_id"]),
+            channel=self._deserialize_interaction_channel(payload["channel"]),
             member=member,
             user=user,
             token=payload["token"],
diff --git a/hikari/interactions/base_interactions.py b/hikari/interactions/base_interactions.py
index 32b75d07..8f2d9a23 100644
--- a/hikari/interactions/base_interactions.py
+++ b/hikari/interactions/base_interactions.py
@@ -46,6 +46,7 @@
 from hikari import channels
 from hikari import guilds
 from hikari import snowflakes
+from hikari import traits
 from hikari import undefined
 from hikari import webhooks
 from hikari.internal import attrs_extensions
@@ -55,9 +56,10 @@
     from hikari import applications
     from hikari import embeds as embeds_
     from hikari import files
+    from hikari import locales
     from hikari import messages
+    from hikari import monetization
     from hikari import permissions as permissions_
-    from hikari import traits
     from hikari import users
     from hikari.api import special_endpoints
 
@@ -221,6 +223,44 @@ class PartialInteraction(snowflakes.Unique, webhooks.ExecutableWebhook):
     version: int = attrs.field(eq=False, repr=True)
     """Version of the interaction system this interaction is under."""
 
+    app_permissions: typing.Optional[permissions_.Permissions] = attrs.field(eq=False, hash=False, repr=False)
+    """Permissions the bot has in this interaction's channel if it's in a guild."""
+
+    user: users.User = attrs.field(eq=False, hash=False, repr=True)
+    """The user who triggered this interaction."""
+
+    member: typing.Optional[InteractionMember] = attrs.field(eq=False, hash=False, repr=True)
+    """The member who triggered this interaction.
+
+    This will be [`None`][] for interactions triggered in DMs.
+
+    !!! note
+        This member object comes with the extra field `permissions` which
+        contains the member's permissions in the current channel.
+    """
+
+    channel: InteractionChannel = attrs.field(eq=False, repr=False)
+    """The channel this interaction was triggered in."""
+
+    guild_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=True)
+    """ID of the guild this modal interaction event was triggered in.
+
+    This will be [`None`][] for modal interactions triggered in DMs.
+    """
+
+    guild_locale: typing.Optional[typing.Union[str, locales.Locale]] = attrs.field(eq=False, hash=False, repr=True)
+    """The preferred language of the guild this component interaction was triggered in.
+
+    This will be [`None`][] for component interactions triggered in DMs.
+
+    !!! note
+        This value can usually only be changed if [COMMUNITY] is in [`hikari.guilds.Guild.features`][]
+        for the guild and will otherwise default to `en-US`.
+    """
+
+    locale: str = attrs.field(eq=False, hash=False, repr=True)
+    """The selected language of the user who triggered this modal interaction."""
+
     authorizing_integration_owners: typing.Mapping[applications.ApplicationIntegrationType, snowflakes.Snowflake] = (
         attrs.field(eq=False, repr=True)
     )
@@ -229,11 +269,60 @@ class PartialInteraction(snowflakes.Unique, webhooks.ExecutableWebhook):
     context: applications.ApplicationContextType = attrs.field(eq=False, repr=True)
     """The interaction context."""
 
+    entitlements: typing.Sequence[monetization.Entitlement] = attrs.field(eq=False, hash=False, repr=True)
+    """For monetized apps, any entitlements for the invoking user, represents access to SKUs."""
+
+    @property
+    def channel_id(self) -> snowflakes.Snowflake:
+        """The ID of the channel this interaction was invoked in."""
+        return self.channel.id
+
     @property
     def webhook_id(self) -> snowflakes.Snowflake:
         # <<inherited docstring from ExecutableWebhook>>.
         return self.application_id
 
+    async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]:
+        """Fetch the guild this interaction happened in.
+
+        Returns
+        -------
+        typing.Optional[hikari.guilds.RESTGuild]
+            Object of the guild this interaction happened in or [`None`][]
+            if this occurred within a DM channel.
+
+        Raises
+        ------
+        hikari.errors.ForbiddenError
+            If you are not part of the guild.
+        hikari.errors.NotFoundError
+            If the guild is not 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.InternalServerError
+            If an internal error occurs on Discord while handling the request.
+        """
+        if not self.guild_id:
+            return None
+
+        return await self.app.rest.fetch_guild(self.guild_id)
+
+    def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
+        """Get the object of the guild this interaction was triggered in from the cache.
+
+        Returns
+        -------
+        typing.Optional[hikari.guilds.GatewayGuild]
+            The object of the guild if found, else [`None`][].
+        """
+        if self.guild_id and isinstance(self.app, traits.CacheAware):
+            return self.app.cache.get_guild(self.guild_id)
+
+        return None
+
 
 @attrs_extensions.with_copy
 @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False)
@@ -687,6 +776,15 @@ class InteractionChannel(channels.PartialChannel):
     permissions: permissions_.Permissions = attrs.field(eq=False, hash=False, repr=True)
     """Permissions the command's executor has in this channel."""
 
+    parent_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=True)
+    """The parent ID of the channel.
+
+    This will be [`None`][] for DM channels and guild channels that have no parent.
+    """
+
+    thread_metadata: typing.Optional[channels.ThreadMetadata] = attrs.field(eq=False, hash=False, repr=False)
+    """The thread metadata, if the channel is a thread."""
+
 
 @attrs_extensions.with_copy
 @attrs.define(kw_only=True, weakref_slot=False)
diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py
index a1790005..f4748dd4 100644
--- a/hikari/interactions/command_interactions.py
+++ b/hikari/interactions/command_interactions.py
@@ -38,11 +38,8 @@
 
 import attrs
 
-from hikari import channels
 from hikari import commands
-from hikari import monetization
 from hikari import snowflakes
-from hikari import traits
 from hikari import undefined
 from hikari.interactions import base_interactions
 from hikari.internal import attrs_extensions
@@ -50,7 +47,6 @@
 if typing.TYPE_CHECKING:
     from typing_extensions import Self
 
-    from hikari import guilds
     from hikari import permissions as permissions_
     from hikari import users as users_
     from hikari.api import special_endpoints
@@ -132,41 +128,6 @@ class BaseCommandInteraction(base_interactions.PartialInteraction):
     May be a command interaction or an autocomplete interaction.
     """
 
-    channel_id: snowflakes.Snowflake = attrs.field(eq=False, hash=False, repr=True)
-    """ID of the channel this command interaction event was triggered in."""
-
-    guild_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=True)
-    """ID of the guild this command interaction event was triggered in.
-
-    This will be [`None`][] for command interactions triggered in DMs.
-    """
-
-    guild_locale: typing.Optional[str] = attrs.field(eq=False, hash=False, repr=True)
-    """The preferred language of the guild this command interaction was triggered in.
-
-    This will be [`None`][] for command interactions triggered in DMs.
-
-    !!! note
-        This value can usually only be changed if [COMMUNITY] is in [`hikari.guilds.Guild.features`][]
-        for the guild and will otherwise default to `en-US`.
-    """
-
-    member: typing.Optional[base_interactions.InteractionMember] = attrs.field(eq=False, hash=False, repr=True)
-    """The member who triggered this command interaction.
-
-    This will be [`None`][] for command interactions triggered in DMs.
-
-    !!! note
-        This member object comes with the extra field `permissions` which
-        contains the member's permissions in the current channel.
-    """
-
-    user: users_.User = attrs.field(eq=False, hash=False, repr=True)
-    """The user who triggered this command interaction."""
-
-    locale: str = attrs.field(eq=False, hash=False, repr=True)
-    """The selected language of the user who triggered this command interaction."""
-
     command_id: snowflakes.Snowflake = attrs.field(eq=False, hash=False, repr=True)
     """ID of the command being invoked."""
 
@@ -179,59 +140,6 @@ class BaseCommandInteraction(base_interactions.PartialInteraction):
     registered_guild_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=True)
     """ID of the guild the command is registered to."""
 
-    entitlements: typing.Sequence[monetization.Entitlement] = attrs.field(eq=False, hash=False, repr=True)
-    """For monetized apps, any entitlements for the invoking user, represents access to SKUs."""
-
-    async def fetch_channel(self) -> channels.TextableChannel:
-        """Fetch the guild channel this was triggered in.
-
-        Returns
-        -------
-        hikari.channels.TextableChannel
-            The requested partial channel derived object of the channel this was
-            triggered in.
-
-        Raises
-        ------
-        hikari.errors.UnauthorizedError
-            If you are unauthorized to make the request (invalid/missing token).
-        hikari.errors.ForbiddenError
-            If you are missing the [`hikari.permissions.Permissions.VIEW_CHANNEL`][] permission in the channel.
-        hikari.errors.NotFoundError
-            If the channel is not found.
-        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.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
-        hikari.errors.InternalServerError
-            If an internal error occurs on Discord while handling the request.
-        """
-        channel = await self.app.rest.fetch_channel(self.channel_id)
-        assert isinstance(channel, channels.TextableChannel)
-        return channel
-
-    def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]:
-        """Get the guild channel this was triggered in from the cache.
-
-        !!! note
-            This will always return [`None`][] for interactions triggered
-            in a DM channel.
-
-        Returns
-        -------
-        typing.Optional[hikari.channels.TextableGuildChannel]
-            The object of the guild channel that was found in the cache or
-            [`None`][].
-        """
-        if isinstance(self.app, traits.CacheAware):
-            channel = self.app.cache.get_guild_channel(self.channel_id)
-            assert channel is None or isinstance(channel, channels.TextableGuildChannel)
-            return channel
-
-        return None
-
     async def fetch_command(self) -> commands.PartialCommand:
         """Fetch the command which triggered this interaction.
 
@@ -258,47 +166,6 @@ async def fetch_command(self) -> commands.PartialCommand:
             application=self.application_id, command=self.id, guild=self.guild_id or undefined.UNDEFINED
         )
 
-    async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]:
-        """Fetch the guild this interaction happened in.
-
-        Returns
-        -------
-        typing.Optional[hikari.guilds.RESTGuild]
-            Object of the guild this interaction happened in or [`None`][]
-            if this occurred within a DM channel.
-
-        Raises
-        ------
-        hikari.errors.ForbiddenError
-            If you are not part of the guild.
-        hikari.errors.NotFoundError
-            If the guild is not 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.InternalServerError
-            If an internal error occurs on Discord while handling the request.
-        """
-        if not self.guild_id:
-            return None
-
-        return await self.app.rest.fetch_guild(self.guild_id)
-
-    def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
-        """Get the object of this interaction's guild guild from the cache.
-
-        Returns
-        -------
-        typing.Optional[hikari.guilds.GatewayGuild]
-            The object of the guild if found, else [`None`][].
-        """
-        if self.guild_id and isinstance(self.app, traits.CacheAware):
-            return self.app.cache.get_guild(self.guild_id)
-
-        return None
-
 
 @attrs_extensions.with_copy
 @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False)
diff --git a/hikari/interactions/component_interactions.py b/hikari/interactions/component_interactions.py
index ff7a5597..47ea1cfa 100644
--- a/hikari/interactions/component_interactions.py
+++ b/hikari/interactions/component_interactions.py
@@ -34,19 +34,12 @@
 
 import attrs
 
-from hikari import channels
-from hikari import traits
 from hikari.interactions import base_interactions
 
 if typing.TYPE_CHECKING:
     from hikari import components as components_
-    from hikari import guilds
-    from hikari import locales
     from hikari import messages
-    from hikari import monetization
-    from hikari import permissions
     from hikari import snowflakes
-    from hikari import users
     from hikari.api import special_endpoints
 
 
@@ -97,9 +90,6 @@ class ComponentInteraction(
 ):
     """Represents a component interaction on Discord."""
 
-    channel_id: snowflakes.Snowflake = attrs.field(eq=False)
-    """ID of the channel this interaction was triggered in."""
-
     component_type: typing.Union[components_.ComponentType, int] = attrs.field(eq=False)
     """The type of component which triggers this interaction.
 
@@ -117,47 +107,9 @@ class ComponentInteraction(
     resolved: typing.Optional[base_interactions.ResolvedOptionData] = attrs.field(eq=False, hash=False, repr=False)
     """Mappings of the objects resolved for the provided command options."""
 
-    guild_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False)
-    """ID of the guild this interaction was triggered in.
-
-    This will be [`None`][] for component interactions triggered in DMs.
-    """
-
-    guild_locale: typing.Optional[typing.Union[str, locales.Locale]] = attrs.field(eq=False, hash=False, repr=True)
-    """The preferred language of the guild this component interaction was triggered in.
-
-    This will be [`None`][] for component interactions triggered in DMs.
-
-    !!! note
-        This value can usually only be changed if [COMMUNITY] is in [`hikari.guilds.Guild.features`][]
-        for the guild and will otherwise default to `en-US`.
-    """
-
     message: messages.Message = attrs.field(eq=False, repr=False)
     """Object of the message the components for this interaction are attached to."""
 
-    member: typing.Optional[base_interactions.InteractionMember] = attrs.field(eq=False, hash=False, repr=True)
-    """The member who triggered this interaction.
-
-    This will be [`None`][] for interactions triggered in DMs.
-
-    !!! note
-        This member object comes with the extra field `permissions` which
-        contains the member's permissions in the current channel.
-    """
-
-    user: users.User = attrs.field(eq=False, hash=False, repr=True)
-    """The user who triggered this interaction."""
-
-    locale: typing.Union[str, locales.Locale] = attrs.field(eq=False, hash=False, repr=True)
-    """The selected language of the user who triggered this component interaction."""
-
-    app_permissions: typing.Optional[permissions.Permissions] = attrs.field(eq=False, hash=False, repr=False)
-    """Permissions the bot has in this interaction's channel if it's in a guild."""
-
-    entitlements: typing.Sequence[monetization.Entitlement] = attrs.field(eq=False, hash=False, repr=True)
-    """For monetized apps, any entitlements for the invoking user, represents access to SKUs."""
-
     def build_response(self, type_: _ImmediateTypesT, /) -> special_endpoints.InteractionMessageBuilder:
         """Get a message response builder for use in the REST server flow.
 
@@ -232,96 +184,6 @@ def build_deferred_response(self, type_: _DeferredTypesT, /) -> special_endpoint
 
         return self.app.rest.interaction_deferred_builder(type_)
 
-    async def fetch_channel(self) -> channels.TextableChannel:
-        """Fetch the channel this interaction occurred in.
-
-        Returns
-        -------
-        hikari.channels.TextableChannel
-            The channel. This will be a _derivative_ of [`hikari.channels.TextableChannel`][].
-
-        Raises
-        ------
-        hikari.errors.UnauthorizedError
-            If you are unauthorized to make the request (invalid/missing token).
-        hikari.errors.ForbiddenError
-            If you are missing the [`hikari.permissions.Permissions.VIEW_CHANNEL`][] permission in the channel.
-        hikari.errors.NotFoundError
-            If the channel is not found.
-        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.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
-        hikari.errors.InternalServerError
-            If an internal error occurs on Discord while handling the request.
-        """
-        channel = await self.app.rest.fetch_channel(self.channel_id)
-        assert isinstance(channel, channels.TextableChannel)
-        return channel
-
-    def get_channel(self) -> typing.Union[channels.GuildTextChannel, channels.GuildNewsChannel, None]:
-        """Get the guild channel this interaction occurred in.
-
-        !!! note
-            This will always return [`None`][] for interactions triggered
-            in a DM channel.
-
-        Returns
-        -------
-        typing.Union[hikari.channels.GuildTextChannel, hikari.channels.GuildNewsChannel, None]
-            The object of the guild channel that was found in the cache or
-            [`None`][].
-        """
-        if isinstance(self.app, traits.CacheAware):
-            channel = self.app.cache.get_guild_channel(self.channel_id)
-            assert channel is None or isinstance(channel, (channels.GuildTextChannel, channels.GuildNewsChannel))
-            return channel
-
-        return None
-
-    async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]:
-        """Fetch the guild this interaction happened in.
-
-        Returns
-        -------
-        typing.Optional[hikari.guilds.RESTGuild]
-            Object of the guild this interaction happened in or [`None`][]
-            if this occurred within a DM channel.
-
-        Raises
-        ------
-        hikari.errors.ForbiddenError
-            If you are not part of the guild.
-        hikari.errors.NotFoundError
-            If the guild is not 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.InternalServerError
-            If an internal error occurs on Discord while handling the request.
-        """
-        if not self.guild_id:
-            return None
-
-        return await self.app.rest.fetch_guild(self.guild_id)
-
-    def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
-        """Get the object of this interaction's guild from the cache.
-
-        Returns
-        -------
-        typing.Optional[hikari.guilds.GatewayGuild]
-            The object of the guild if found, else [`None`][].
-        """
-        if self.guild_id and isinstance(self.app, traits.CacheAware):
-            return self.app.cache.get_guild(self.guild_id)
-
-        return None
-
 
 @attrs.define(unsafe_hash=True, kw_only=True, weakref_slot=False)
 class ComponentInteractionMetadata(base_interactions.PartialInteractionMetadata):
diff --git a/hikari/interactions/modal_interactions.py b/hikari/interactions/modal_interactions.py
index 2fc694f6..d0fb0b4e 100644
--- a/hikari/interactions/modal_interactions.py
+++ b/hikari/interactions/modal_interactions.py
@@ -34,20 +34,13 @@
 
 import attrs
 
-from hikari import channels
-from hikari import guilds
 from hikari import messages
-from hikari import permissions
 from hikari import snowflakes
-from hikari import traits
 from hikari.interactions import base_interactions
 from hikari.internal import attrs_extensions
 
 if typing.TYPE_CHECKING:
     from hikari import components as components_
-    from hikari import locales
-    from hikari import monetization
-    from hikari import users as _users
     from hikari.api import special_endpoints
 
 ModalResponseTypesT = typing.Literal[
@@ -78,152 +71,18 @@ class ModalInteraction(
 ):
     """Represents a modal interaction on Discord."""
 
-    channel_id: snowflakes.Snowflake = attrs.field(eq=False, hash=False, repr=True)
-    """ID of the channel this modal interaction event was triggered in."""
-
     custom_id: str = attrs.field(eq=False, hash=False, repr=True)
     """The custom id of the modal."""
 
-    guild_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=True)
-    """ID of the guild this modal interaction event was triggered in.
-
-    This will be [`None`][] for modal interactions triggered in DMs.
-    """
-
-    guild_locale: typing.Union[str, locales.Locale, None] = attrs.field(eq=False, hash=False, repr=True)
-    """The preferred language of the guild this modal interaction was triggered in.
-
-    This will be [`None`][] for modal interactions triggered in DMs.
-
-    !!! note
-        This value can usually only be changed if [`hikari.guilds.GuildFeature.COMMUNITY`][]
-        is in [`hikari.guilds.Guild.features`][] for the guild and will otherwise
-        default to [`hikari.locales.Locale.EN_US`][].
-    """
-
     message: typing.Optional[messages.Message] = attrs.field(eq=False, repr=False)
     """The message whose component triggered the modal.
 
     This will be [`None`][] if the modal was a response to a command.
     """
 
-    member: typing.Optional[base_interactions.InteractionMember] = attrs.field(eq=False, hash=False, repr=True)
-    """The member who triggered this modal interaction.
-
-    This will be [`None`][] for modal interactions triggered in DMs.
-
-    !!! note
-        This member object comes with the extra field `permissions` which
-        contains the member's permissions in the current channel.
-    """
-
-    user: _users.User = attrs.field(eq=False, hash=False, repr=True)
-    """The user who triggered this modal interaction."""
-
-    locale: str = attrs.field(eq=False, hash=False, repr=True)
-    """The selected language of the user who triggered this modal interaction."""
-
-    app_permissions: typing.Optional[permissions.Permissions] = attrs.field(eq=False, hash=False, repr=False)
-    """Permissions the bot has in this interaction's channel if it's in a guild."""
-
     components: typing.Sequence[components_.ModalActionRowComponent] = attrs.field(eq=False, hash=False, repr=True)
     """Components in the modal."""
 
-    entitlements: typing.Sequence[monetization.Entitlement] = attrs.field(eq=False, hash=False, repr=True)
-    """For monetized apps, any entitlements for the invoking user, represents access to SKUs."""
-
-    async def fetch_channel(self) -> channels.TextableChannel:
-        """Fetch the guild channel this interaction was triggered in.
-
-        Returns
-        -------
-        hikari.channels.TextableChannel
-            The requested partial channel derived object of the channel this
-            interaction was triggered in.
-
-        Raises
-        ------
-        hikari.errors.UnauthorizedError
-            If you are unauthorized to make the request (invalid/missing token).
-        hikari.errors.ForbiddenError
-            If you are missing the [`hikari.permissions.Permissions.VIEW_CHANNEL`][]
-            permission in the channel.
-        hikari.errors.NotFoundError
-            If the channel is not found.
-        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.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
-        hikari.errors.InternalServerError
-            If an internal error occurs on Discord while handling the request.
-        """
-        channel = await self.app.rest.fetch_channel(self.channel_id)
-        assert isinstance(channel, channels.TextableChannel)
-        return channel
-
-    def get_channel(self) -> typing.Optional[channels.TextableGuildChannel]:
-        """Get the guild channel this interaction was triggered in from the cache.
-
-        !!! note
-            This will always return [`None`][] for interactions triggered
-            in a DM channel.
-
-        Returns
-        -------
-        typing.Optional[hikari.channels.TextableGuildChannel]
-            The object of the guild channel that was found in the cache or
-            [`None`][].
-        """
-        if isinstance(self.app, traits.CacheAware):
-            channel = self.app.cache.get_guild_channel(self.channel_id)
-            assert channel is None or isinstance(channel, channels.TextableGuildChannel)
-            return channel
-
-        return None
-
-    async def fetch_guild(self) -> typing.Optional[guilds.RESTGuild]:
-        """Fetch the guild this interaction happened in.
-
-        Returns
-        -------
-        typing.Optional[hikari.guilds.RESTGuild]
-            Object of the guild this interaction happened in or [`None`][]
-            if this occurred within a DM channel.
-
-        Raises
-        ------
-        hikari.errors.ForbiddenError
-            If you are not part of the guild.
-        hikari.errors.NotFoundError
-            If the guild is not 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.InternalServerError
-            If an internal error occurs on Discord while handling the request.
-        """
-        if not self.guild_id:
-            return None
-
-        return await self.app.rest.fetch_guild(self.guild_id)
-
-    def get_guild(self) -> typing.Optional[guilds.GatewayGuild]:
-        """Get the object of the guild this interaction was triggered in from the cache.
-
-        Returns
-        -------
-        typing.Optional[hikari.guilds.GatewayGuild]
-            The object of the guild if found, else [`None`][].
-        """
-        if self.guild_id and isinstance(self.app, traits.CacheAware):
-            return self.app.cache.get_guild(self.guild_id)
-
-        return None
-
     def build_response(self) -> special_endpoints.InteractionMessageBuilder:
         """Get a message response builder for use in the REST server flow.
 
@@ -280,7 +139,7 @@ class ModalInteractionMetadata(base_interactions.PartialInteractionMetadata):
     original_response_message_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=True)
     """The ID of the original response message, present only on follow-up messages."""
 
-    triggering_interaction_metadata: typing.Union[base_interactions.PartialInteractionMetadata] = attrs.field(
+    triggering_interaction_metadata: base_interactions.PartialInteractionMetadata = attrs.field(
         eq=False, hash=False, repr=True
     )
     """The metadata for the interaction that was used to open the modal."""
diff --git a/pyproject.toml b/pyproject.toml
index 0e41680e..8f76242d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -134,7 +134,7 @@ pytest = [
     { include-group = "coverage" },
 ]
 release = ["twine==6.1.0"]
-ruff = ["ruff==0.11.1"]
+ruff = ["ruff==0.11.2"]
 slotscheck = ["slotscheck==0.19.1"]
 towncrier = ["towncrier==24.8.0"]
 
diff --git a/uv.lock b/uv.lock
index c31929da..b01f1461 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1331,7 +1331,7 @@ pytest = [
     { name = "pytest-randomly", specifier = "==3.16.0" },
 ]
 release = [{ name = "twine", specifier = "==6.1.0" }]
-ruff = [{ name = "ruff", specifier = "==0.11.1" }]
+ruff = [{ name = "ruff", specifier = "==0.11.2" }]
 slotscheck = [{ name = "slotscheck", specifier = "==0.19.1" }]
 towncrier = [{ name = "towncrier", specifier = "==24.8.0" }]
 
@@ -2881,27 +2881,27 @@ wheels = [
 
 [[package]]
 name = "ruff"
-version = "0.11.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a0/ab/be73f46695c350147ffbfd98d42f34bfe370e6b4146bb0bfdc5b3c636fc7/ruff-0.11.1.tar.gz", hash = "sha256:f2e209a283c9fa423e268cad015ec4fb249178608f755fb67491ff175ecbffbf", size = 3850423 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/de/09/fa72b32fb7a1f670c8d3cb925910447c496117850c6aa1601365ad11cd00/ruff-0.11.1-py3-none-linux_armv6l.whl", hash = "sha256:9c833671aaefcbe280aa54da387264402ffbb1e513ff3420c9c7265ea56d6c5c", size = 10114855 },
-    { url = "https://files.pythonhosted.org/packages/fa/c2/544da7ee0bf0a28117218065ccac69683d8d7a885544e6c84076cf72351b/ruff-0.11.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5a57cd457764228c73066b832040728b02a3837c53c8a781a960b68129c4e0b", size = 10870287 },
-    { url = "https://files.pythonhosted.org/packages/ee/56/0d1c0a3f3ab644e813f365c7eee5d0f9b75e755c5642400cd6ad7070f730/ruff-0.11.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:da91da0d42e70cd8bda8e6687fab2afd28513a3cc9434539f4149610e63baf8f", size = 10223642 },
-    { url = "https://files.pythonhosted.org/packages/d6/7c/88f06c23018ca02b8b0f78b62e4c701faa2ef6413d9f5e82277bc094d2e9/ruff-0.11.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:429a2e533e3a0dba2ba7e0608a736e728150aa9b6d179245aa11a1339baa968b", size = 10384382 },
-    { url = "https://files.pythonhosted.org/packages/67/55/15d2a355e4c64a712938df4c650d7239128865981eb8314a49782a53ef45/ruff-0.11.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6bbcc2984a4d5cbc0f7b10409e74a00a043be45d813e5e81eb58e707455df7f1", size = 9965192 },
-    { url = "https://files.pythonhosted.org/packages/39/e8/4312a514fac213e90e5b45120339dbaa95a396f4cf3ab740e757fed4fa35/ruff-0.11.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d9c283ebc88faa5bc23fa33f399b6d47a93f5980c92edcddf1f2127aa376b3", size = 11563312 },
-    { url = "https://files.pythonhosted.org/packages/69/ff/4da09bea58e1d784f45169fd281643422727a1c0776802cd6d4b42a0037d/ruff-0.11.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1f2b03d504516d6b22065ce7fac2564dac15d79a6a776452dabfdd7673a45b07", size = 12223815 },
-    { url = "https://files.pythonhosted.org/packages/ec/ba/7e3550f1478a53c4f7efcb0c31a03256eb032a871026da61324ad06dcd0b/ruff-0.11.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b95a9071f5ad8552af890bd814c6a04daf5b27297ac1054e3667016f3ab739", size = 11662551 },
-    { url = "https://files.pythonhosted.org/packages/7d/69/7e2f1c8a0c76de18f9c054382d31c6d5f9b55a3e476b2630fefe289d31a6/ruff-0.11.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28e2d89e7ba8a1525cdb50bc86c07aba35e7bbeef86dad93781b14ad94dc732c", size = 13792940 },
-    { url = "https://files.pythonhosted.org/packages/02/4b/14f54bbf8f8dd03cb9a8a0294e7de1dc41f3be1806af07b8a104761db597/ruff-0.11.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e76be5a98dc6c29d85dfa72eb419e8d9276ee96ccf5c33f2b6828001907dcb17", size = 11328700 },
-    { url = "https://files.pythonhosted.org/packages/f5/0c/40c1ba7e9a8c2c7325267c214746b80da1b54fb7e70a9265608213f4b2fb/ruff-0.11.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:441f94c44fe250691c92382ef84f40acef290766fb3e819a9035e83eadd4dbbe", size = 10272650 },
-    { url = "https://files.pythonhosted.org/packages/a3/27/f86b786e0153c5b52768370f395086c4523dee1af37757a8ef73779b9fbd/ruff-0.11.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:62882a4cc7c0a48c2f34189bd4c7ba45f3d0efb990e02413eeb180aa042a39ca", size = 9973454 },
-    { url = "https://files.pythonhosted.org/packages/ab/46/47bf0c7ee80b0476226865d724ee7682323a2cb20aa5d134b8a665f58793/ruff-0.11.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:111dbad1706d8200a7138237b4766b45ba7ee45cc8299c02102f4327624f86a2", size = 10975303 },
-    { url = "https://files.pythonhosted.org/packages/9d/9d/deea8ece99f31aef07f378f83b5571baca74dff0d28fec720e54b171b862/ruff-0.11.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e2df41763d7a9fd438b6b7bde7b75eb3a92ef2f4682ed2d8e4b997b5f0c76ca9", size = 11402605 },
-    { url = "https://files.pythonhosted.org/packages/b8/8c/f11e21f4a5ce0858c35745d43e9a5abd870ed33e151ada23c124bf433e71/ruff-0.11.1-py3-none-win32.whl", hash = "sha256:e17b85919d461583aa7e0171bb4f419a6545b261ca080984db49b1f8dced1d4b", size = 10335500 },
-    { url = "https://files.pythonhosted.org/packages/21/c9/ac2784bad48c7a8702d2f13851b69b2a08534b59657ee2b724f42aaaf861/ruff-0.11.1-py3-none-win_amd64.whl", hash = "sha256:caa872941b876f7ad73abc60144f9a7f6efb575e4f91c4fc1517f0339bcea01e", size = 11365097 },
-    { url = "https://files.pythonhosted.org/packages/9e/7d/f37251c102cf155cdf664322f1f362f4fd8fe1eb6870e9154255d3cbf0d8/ruff-0.11.1-py3-none-win_arm64.whl", hash = "sha256:7aa939fa57ef6770d18bd5cf0d6de77198dd762a559bd0d4a8763bdae4c8cc16", size = 10510306 },
+version = "0.11.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/61/fb87430f040e4e577e784e325351186976516faef17d6fcd921fe28edfd7/ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94", size = 3857511 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/62/99/102578506f0f5fa29fd7e0df0a273864f79af044757aef73d1cae0afe6ad/ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477", size = 10113146 },
+    { url = "https://files.pythonhosted.org/packages/74/ad/5cd4ba58ab602a579997a8494b96f10f316e874d7c435bcc1a92e6da1b12/ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272", size = 10867092 },
+    { url = "https://files.pythonhosted.org/packages/fc/3e/d3f13619e1d152c7b600a38c1a035e833e794c6625c9a6cea6f63dbf3af4/ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9", size = 10224082 },
+    { url = "https://files.pythonhosted.org/packages/90/06/f77b3d790d24a93f38e3806216f263974909888fd1e826717c3ec956bbcd/ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb", size = 10394818 },
+    { url = "https://files.pythonhosted.org/packages/99/7f/78aa431d3ddebfc2418cd95b786642557ba8b3cb578c075239da9ce97ff9/ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3", size = 9952251 },
+    { url = "https://files.pythonhosted.org/packages/30/3e/f11186d1ddfaca438c3bbff73c6a2fdb5b60e6450cc466129c694b0ab7a2/ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74", size = 11563566 },
+    { url = "https://files.pythonhosted.org/packages/22/6c/6ca91befbc0a6539ee133d9a9ce60b1a354db12c3c5d11cfdbf77140f851/ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608", size = 12208721 },
+    { url = "https://files.pythonhosted.org/packages/19/b0/24516a3b850d55b17c03fc399b681c6a549d06ce665915721dc5d6458a5c/ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f", size = 11662274 },
+    { url = "https://files.pythonhosted.org/packages/d7/65/76be06d28ecb7c6070280cef2bcb20c98fbf99ff60b1c57d2fb9b8771348/ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147", size = 13792284 },
+    { url = "https://files.pythonhosted.org/packages/ce/d2/4ceed7147e05852876f3b5f3fdc23f878ce2b7e0b90dd6e698bda3d20787/ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b", size = 11327861 },
+    { url = "https://files.pythonhosted.org/packages/c4/78/4935ecba13706fd60ebe0e3dc50371f2bdc3d9bc80e68adc32ff93914534/ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9", size = 10276560 },
+    { url = "https://files.pythonhosted.org/packages/81/7f/1b2435c3f5245d410bb5dc80f13ec796454c21fbda12b77d7588d5cf4e29/ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab", size = 9945091 },
+    { url = "https://files.pythonhosted.org/packages/39/c4/692284c07e6bf2b31d82bb8c32f8840f9d0627d92983edaac991a2b66c0a/ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630", size = 10977133 },
+    { url = "https://files.pythonhosted.org/packages/94/cf/8ab81cb7dd7a3b0a3960c2769825038f3adcd75faf46dd6376086df8b128/ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f", size = 11378514 },
+    { url = "https://files.pythonhosted.org/packages/d9/3a/a647fa4f316482dacf2fd68e8a386327a33d6eabd8eb2f9a0c3d291ec549/ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc", size = 10319835 },
+    { url = "https://files.pythonhosted.org/packages/86/54/3c12d3af58012a5e2cd7ebdbe9983f4834af3f8cbea0e8a8c74fa1e23b2b/ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080", size = 11373713 },
+    { url = "https://files.pythonhosted.org/packages/d6/d4/dd813703af8a1e2ac33bf3feb27e8a5ad514c9f219df80c64d69807e7f71/ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4", size = 10441990 },
 ]
 
 [[package]]


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index a304691d..8f7c238b 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -4213,24 +4213,6 @@ def partial_interaction_payload(self):
             "context": 2,
         }
 
-    def test_deserialize_partial_interaction(self, mock_app, entity_factory_impl, partial_interaction_payload):
-        interaction = entity_factory_impl.deserialize_partial_interaction(partial_interaction_payload)
-
-        assert interaction.app is mock_app
-        assert interaction.id == 795459528803745843
-        assert interaction.token == "-- token redacted --"
-        assert interaction.type == 1
-        assert interaction.version == 1
-        assert interaction.application_id == 1
-        assert interaction.authorizing_integration_owners[
-            application_models.ApplicationIntegrationType.GUILD_INSTALL
-        ] == snowflakes.Snowflake(123)
-        assert interaction.authorizing_integration_owners[
-            application_models.ApplicationIntegrationType.USER_INSTALL
-        ] == snowflakes.Snowflake(456)
-        assert interaction.context == application_models.ApplicationContextType.PRIVATE_CHANNEL
-        assert type(interaction) is base_interactions.PartialInteraction
-
     @pytest.fixture
     def interaction_member_payload(self, user_payload):
         return {
@@ -4379,7 +4361,9 @@ def interaction_resolved_data_payload(
         }
 
     @pytest.fixture
-    def command_interaction_payload(self, interaction_member_payload, interaction_resolved_data_payload):
+    def command_interaction_payload(
+        self, interaction_member_payload, interaction_resolved_data_payload, guild_text_channel_payload
+    ):
         return {
             "id": "3490190239012093",
             "type": 2,
@@ -4401,7 +4385,7 @@ def command_interaction_payload(self, interaction_member_payload, interaction_re
                 "guild_id": "12345678",
                 "resolved": interaction_resolved_data_payload,
             },
-            "channel_id": "49949494",
+            "channel": guild_text_channel_payload,
             "member": interaction_member_payload,
             "token": "moe cat girls",
             "locale": "es-ES",
@@ -4434,6 +4418,7 @@ def test_deserialize_command_interaction(
         command_interaction_payload,
         interaction_member_payload,
         interaction_resolved_data_payload,
+        guild_text_channel_payload,
     ):
         interaction = entity_factory_impl.deserialize_command_interaction(command_interaction_payload)
         assert interaction.app is mock_app
@@ -4442,7 +4427,7 @@ def test_deserialize_command_interaction(
         assert interaction.type is base_interactions.InteractionType.APPLICATION_COMMAND
         assert interaction.token == "moe cat girls"
         assert interaction.version == 69420
-        assert interaction.channel_id == 49949494
+        assert interaction.channel == entity_factory_impl._deserialize_interaction_channel(guild_text_channel_payload)
         assert interaction.guild_id == 43123123
         assert interaction.locale == "es-ES"
         assert interaction.locale is locales.Locale.ES_ES
@@ -4496,7 +4481,9 @@ def test_deserialize_command_interaction(
         assert isinstance(interaction, command_interactions.CommandInteraction)
 
     @pytest.fixture
-    def context_menu_command_interaction_payload(self, interaction_member_payload, user_payload):
+    def context_menu_command_interaction_payload(
+        self, interaction_member_payload, user_payload, guild_text_channel_payload
+    ):
         return {
             "id": "3490190239012093",
             "type": 4,
@@ -4509,7 +4496,7 @@ def context_menu_command_interaction_payload(self, interaction_member_payload, u
                 "resolved": {"users": {"115590097100865541": user_payload}},
                 "guild_id": 12345678,
             },
-            "channel_id": "49949494",
+            "channel": guild_text_channel_payload,
             "member": interaction_member_payload,
             "token": "moe cat girls",
             "locale": "es-ES",
@@ -4572,7 +4559,7 @@ def test_deserialize_command_interaction_with_null_attributes(
         assert interaction.registered_guild_id is None
 
     @pytest.fixture
-    def autocomplete_interaction_payload(self, member_payload, user_payload, interaction_resolved_data_payload):
+    def autocomplete_interaction_payload(self, member_payload, user_payload, guild_text_channel_payload):
         return {
             "id": "3490190239012093",
             "type": 4,
@@ -4594,7 +4581,8 @@ def autocomplete_interaction_payload(self, member_payload, user_payload, interac
                 ],
                 "guild_id": 12345678,
             },
-            "channel_id": "49949494",
+            "app_permissions": "5431234",
+            "channel": guild_text_channel_payload,
             "user": user_payload,
             "token": "moe cat girls",
             "locale": "es-ES",
@@ -4625,7 +4613,7 @@ def test_deserialize_autocomplete_interaction(
         mock_app,
         member_payload,
         autocomplete_interaction_payload,
-        interaction_resolved_data_payload,
+        guild_text_channel_payload,
     ):
         entity_factory_impl._deserialize_interaction_member = mock.Mock()
         entity_factory_impl._deserialize_resolved_option_data = mock.Mock()
@@ -4637,7 +4625,7 @@ def test_deserialize_autocomplete_interaction(
         assert interaction.type is base_interactions.InteractionType.AUTOCOMPLETE
         assert interaction.token == "moe cat girls"
         assert interaction.version == 69420
-        assert interaction.channel_id == 49949494
+        assert interaction.channel == entity_factory_impl._deserialize_interaction_channel(guild_text_channel_payload)
         assert interaction.guild_id == 43123123
         assert interaction.member is entity_factory_impl._deserialize_interaction_member.return_value
         entity_factory_impl._deserialize_interaction_member.assert_called_once_with(member_payload, guild_id=43123123)
@@ -4871,7 +4859,7 @@ def test_deserialize_context_menu_command_default_member_permissions(
 
     @pytest.fixture
     def component_interaction_payload(
-        self, interaction_member_payload, message_payload, interaction_resolved_data_payload
+        self, interaction_member_payload, message_payload, interaction_resolved_data_payload, guild_text_channel_payload
     ):
         return {
             "version": 1,
@@ -4892,6 +4880,7 @@ def component_interaction_payload(
             "locale": "es-ES",
             "guild_locale": "en-US",
             "app_permissions": "5431234",
+            "channel": guild_text_channel_payload,
             "entitlements": [
                 {
                     "id": "696969696969696",
@@ -4918,6 +4907,7 @@ def test_deserialize_component_interaction(
         mock_app,
         message_payload,
         interaction_resolved_data_payload,
+        guild_text_channel_payload,
     ):
         interaction = entity_factory_impl.deserialize_component_interaction(component_interaction_payload)
 
@@ -4927,7 +4917,7 @@ def test_deserialize_component_interaction(
         assert interaction.type is base_interactions.InteractionType.MESSAGE_COMPONENT
         assert interaction.token == "unique_interaction_token"
         assert interaction.version == 1
-        assert interaction.channel_id == 345626669114982999
+        assert interaction.channel == entity_factory_impl._deserialize_interaction_channel(guild_text_channel_payload)
         assert interaction.component_type is component_models.ComponentType.BUTTON
         assert interaction.custom_id == "click_one"
         assert interaction.guild_id == 290926798626357999
@@ -4959,7 +4949,7 @@ def test_deserialize_component_interaction(
         assert interaction.entitlements[0].id == 696969696969696
 
     def test_deserialize_component_interaction_with_undefined_fields(
-        self, entity_factory_impl, user_payload, message_payload
+        self, entity_factory_impl, user_payload, message_payload, guild_text_channel_payload
     ):
         interaction = entity_factory_impl.deserialize_component_interaction(
             {
@@ -4968,6 +4958,7 @@ def test_deserialize_component_interaction_with_undefined_fields(
                 "token": "unique_interaction_token",
                 "message": message_payload,
                 "user": user_payload,
+                "channel": guild_text_channel_payload,
                 "id": "846462639134605312",
                 "data": {"custom_id": "click_one", "component_type": 2},
                 "channel_id": "345626669114982999",
@@ -5001,7 +4992,7 @@ def test_deserialize_component_interaction_with_undefined_fields(
         assert isinstance(interaction, component_interactions.ComponentInteraction)
 
     @pytest.fixture
-    def modal_interaction_payload(self, interaction_member_payload, message_payload):
+    def modal_interaction_payload(self, interaction_member_payload, message_payload, guild_text_channel_payload):
         return {
             "version": 1,
             "type": 5,
@@ -5017,7 +5008,7 @@ def modal_interaction_payload(self, interaction_member_payload, message_payload)
                     {"type": 1, "components": [{"value": "Longer Text", "type": 4, "custom_id": "about"}]},
                 ],
             },
-            "channel_id": "345626669114982999",
+            "channel": guild_text_channel_payload,
             "application_id": "290926444748734465",
             "locale": "en-US",
             "guild_locale": "es-ES",
@@ -5041,7 +5032,13 @@ def modal_interaction_payload(self, interaction_member_payload, message_payload)
         }
 
     def test_deserialize_modal_interaction(
-        self, entity_factory_impl, mock_app, modal_interaction_payload, interaction_member_payload, message_payload
+        self,
+        entity_factory_impl,
+        mock_app,
+        modal_interaction_payload,
+        interaction_member_payload,
+        message_payload,
+        guild_text_channel_payload,
     ):
         interaction = entity_factory_impl.deserialize_modal_interaction(modal_interaction_payload)
         assert interaction.app is mock_app
@@ -5050,7 +5047,7 @@ def test_deserialize_modal_interaction(
         assert interaction.type is base_interactions.InteractionType.MODAL_SUBMIT
         assert interaction.token == "unique_interaction_token"
         assert interaction.version == 1
-        assert interaction.channel_id == 345626669114982999
+        assert interaction.channel == entity_factory_impl._deserialize_interaction_channel(guild_text_channel_payload)
         assert interaction.guild_id == 290926798626357999
         assert interaction.message == entity_factory_impl.deserialize_message(message_payload)
         assert interaction.member == entity_factory_impl._deserialize_interaction_member(
diff --git a/tests/hikari/impl/test_interaction_server.py b/tests/hikari/impl/test_interaction_server.py
index 7d04cb79..88456214 100644
--- a/tests/hikari/impl/test_interaction_server.py
+++ b/tests/hikari/impl/test_interaction_server.py
@@ -675,6 +675,14 @@ async def test_on_interaction(
             version=1,
             authorizing_integration_owners={},
             context=applications.ApplicationContextType.GUILD,
+            app_permissions=123123123,
+            user=mock.Mock(),
+            member=mock.Mock(),
+            channel=mock.Mock(),
+            guild_id=123123,
+            guild_locale="en-GB",
+            locale="es-ES",
+            entitlements=[],
         )
         mock_builder = mock.Mock(build=mock.Mock(return_value=({"ok": "No boomer"}, [mock_file_1, mock_file_2])))
         mock_listener = mock.AsyncMock(return_value=mock_builder)
@@ -723,6 +731,14 @@ async def mock_generator_listener(event):
             version=1,
             authorizing_integration_owners={},
             context=applications.ApplicationContextType.GUILD,
+            app_permissions=123123123,
+            user=mock.Mock(),
+            member=mock.Mock(),
+            channel=mock.Mock(),
+            guild_id=123123,
+            guild_locale="en-GB",
+            locale="es-ES",
+            entitlements=[],
         )
         mock_builder = mock.Mock(build=mock.Mock(return_value=({"ok": "No boomer"}, [mock_file_1, mock_file_2])))
         g_called = False
@@ -895,6 +911,14 @@ async def test_on_interaction_on_dispatch_error(
             version=1,
             authorizing_integration_owners={},
             context=applications.ApplicationContextType.GUILD,
+            app_permissions=123123123,
+            user=mock.Mock(),
+            member=mock.Mock(),
+            channel=mock.Mock(),
+            guild_id=123123,
+            guild_locale="en-GB",
+            locale="es-ES",
+            entitlements=[],
         )
         mock_interaction_server.set_listener(
             base_interactions.PartialInteraction, mock.Mock(side_effect=mock_exception)
@@ -931,6 +955,14 @@ async def test_on_interaction_when_response_builder_error(
             version=1,
             authorizing_integration_owners={},
             context=applications.ApplicationContextType.GUILD,
+            app_permissions=123123123,
+            user=mock.Mock(),
+            member=mock.Mock(),
+            channel=mock.Mock(),
+            guild_id=123123,
+            guild_locale="en-GB",
+            locale="es-ES",
+            entitlements=[],
         )
         mock_builder = mock.Mock(build=mock.Mock(side_effect=mock_exception))
         mock_interaction_server.set_listener(
@@ -969,6 +1001,14 @@ async def test_on_interaction_when_json_encode_fails(
             version=1,
             authorizing_integration_owners={},
             context=applications.ApplicationContextType.GUILD,
+            app_permissions=123123123,
+            user=mock.Mock(),
+            member=mock.Mock(),
+            channel=mock.Mock(),
+            guild_id=123123,
+            guild_locale="en-GB",
+            locale="es-ES",
+            entitlements=[],
         )
         mock_builder = mock.Mock(build=mock.Mock(return_value=({"ok": "No"}, [])))
         mock_interaction_server.set_listener(
diff --git a/tests/hikari/interactions/test_base_interactions.py b/tests/hikari/interactions/test_base_interactions.py
index f14ba06b..8d1c9a44 100644
--- a/tests/hikari/interactions/test_base_interactions.py
+++ b/tests/hikari/interactions/test_base_interactions.py
@@ -24,6 +24,7 @@
 import pytest
 
 from hikari import applications
+from hikari import monetization
 from hikari import snowflakes
 from hikari import traits
 from hikari import undefined
@@ -45,15 +46,74 @@ def mock_partial_interaction(self, mock_app):
             type=base_interactions.InteractionType.APPLICATION_COMMAND,
             token="399393939doodsodso",
             version=3122312,
+            guild_id=snowflakes.Snowflake(5412231),
+            channel=mock.Mock(id=3123123),
+            member=object(),
+            user=object(),
+            locale="es-ES",
+            guild_locale="en-US",
+            app_permissions=123321,
             authorizing_integration_owners={
                 applications.ApplicationIntegrationType.GUILD_INSTALL: snowflakes.Snowflake(123)
             },
+            entitlements=[
+                monetization.Entitlement(
+                    id=snowflakes.Snowflake(123123),
+                    sku_id=snowflakes.Snowflake(123123),
+                    application_id=snowflakes.Snowflake(123123),
+                    guild_id=snowflakes.Snowflake(123123),
+                    user_id=snowflakes.Snowflake(123123),
+                    type=monetization.EntitlementType.APPLICATION_SUBSCRIPTION,
+                    starts_at=None,
+                    ends_at=None,
+                    is_deleted=False,
+                    subscription_id=None,
+                )
+            ],
             context=applications.ApplicationContextType.PRIVATE_CHANNEL,
         )
 
     def test_webhook_id_property(self, mock_partial_interaction):
         assert mock_partial_interaction.webhook_id is mock_partial_interaction.application_id
 
+    @pytest.mark.asyncio
+    async def test_fetch_guild(self, mock_partial_interaction, mock_app):
+        mock_partial_interaction.guild_id = 43123123
+
+        assert await mock_partial_interaction.fetch_guild() is mock_app.rest.fetch_guild.return_value
+
+        mock_app.rest.fetch_guild.assert_awaited_once_with(43123123)
+
+    @pytest.mark.asyncio
+    async def test_fetch_guild_for_dm_interaction(self, mock_partial_interaction, mock_app):
+        mock_partial_interaction.guild_id = None
+
+        assert await mock_partial_interaction.fetch_guild() is None
+
+        mock_app.rest.fetch_guild.assert_not_called()
+
+    def test_get_guild(self, mock_partial_interaction, mock_app):
+        mock_partial_interaction.guild_id = 874356
+
+        assert mock_partial_interaction.get_guild() is mock_app.cache.get_guild.return_value
+
+        mock_app.cache.get_guild.assert_called_once_with(874356)
+
+    def test_get_guild_for_dm_interaction(self, mock_partial_interaction, mock_app):
+        mock_partial_interaction.guild_id = None
+
+        assert mock_partial_interaction.get_guild() is None
+
+        mock_app.cache.get_guild.assert_not_called()
+
+    def test_get_guild_when_cacheless(self, mock_partial_interaction, mock_app):
+        mock_partial_interaction.guild_id = 321123
+        mock_partial_interaction.app = mock.Mock(traits.RESTAware)
+
+        assert mock_partial_interaction.get_guild() is None
+
+        mock_app.cache.get_guild.assert_not_called()
+
 
 class TestMessageResponseMixin:
     @pytest.fixture
@@ -65,10 +125,31 @@ def mock_message_response_mixin(self, mock_app):
             type=base_interactions.InteractionType.APPLICATION_COMMAND,
             token="399393939doodsodso",
             version=3122312,
+            context=applications.ApplicationContextType.PRIVATE_CHANNEL,
+            guild_id=snowflakes.Snowflake(5412231),
+            channel=mock.Mock(id=3123123),
+            member=object(),
+            user=object(),
+            locale="es-ES",
+            guild_locale="en-US",
+            app_permissions=123321,
             authorizing_integration_owners={
                 applications.ApplicationIntegrationType.GUILD_INSTALL: snowflakes.Snowflake(123)
             },
-            context=applications.ApplicationContextType.PRIVATE_CHANNEL,
+            entitlements=[
+                monetization.Entitlement(
+                    id=snowflakes.Snowflake(123123),
+                    sku_id=snowflakes.Snowflake(123123),
+                    application_id=snowflakes.Snowflake(123123),
+                    guild_id=snowflakes.Snowflake(123123),
+                    user_id=snowflakes.Snowflake(123123),
+                    type=monetization.EntitlementType.APPLICATION_SUBSCRIPTION,
+                    starts_at=None,
+                    ends_at=None,
+                    is_deleted=False,
+                    subscription_id=None,
+                )
+            ],
         )
 
     @pytest.mark.asyncio
@@ -218,10 +299,31 @@ def mock_modal_response_mixin(self, mock_app):
             type=base_interactions.InteractionType.APPLICATION_COMMAND,
             token="399393939doodsodso",
             version=3122312,
+            context=applications.ApplicationContextType.PRIVATE_CHANNEL,
+            guild_id=snowflakes.Snowflake(5412231),
+            channel=mock.Mock(id=3123123),
+            member=object(),
+            user=object(),
+            locale="es-ES",
+            guild_locale="en-US",
+            app_permissions=123321,
             authorizing_integration_owners={
                 applications.ApplicationIntegrationType.GUILD_INSTALL: snowflakes.Snowflake(123)
             },
-            context=applications.ApplicationContextType.PRIVATE_CHANNEL,
+            entitlements=[
+                monetization.Entitlement(
+                    id=snowflakes.Snowflake(123123),
+                    sku_id=snowflakes.Snowflake(123123),
+                    application_id=snowflakes.Snowflake(123123),
+                    guild_id=snowflakes.Snowflake(123123),
+                    user_id=snowflakes.Snowflake(123123),
+                    type=monetization.EntitlementType.APPLICATION_SUBSCRIPTION,
+                    starts_at=None,
+                    ends_at=None,
+                    is_deleted=False,
+                    subscription_id=None,
+                )
+            ],
         )
 
     @pytest.mark.asyncio
diff --git a/tests/hikari/interactions/test_command_interactions.py b/tests/hikari/interactions/test_command_interactions.py
index 8ad0bcd8..40879fd3 100644
--- a/tests/hikari/interactions/test_command_interactions.py
+++ b/tests/hikari/interactions/test_command_interactions.py
@@ -24,7 +24,6 @@
 import pytest
 
 from hikari import applications
-from hikari import channels
 from hikari import monetization
 from hikari import snowflakes
 from hikari import traits
@@ -45,7 +44,7 @@ def mock_command_interaction(self, mock_app):
             app=mock_app,
             id=snowflakes.Snowflake(2312312),
             type=base_interactions.InteractionType.APPLICATION_COMMAND,
-            channel_id=snowflakes.Snowflake(3123123),
+            channel=mock.Mock(id=3123123),
             guild_id=snowflakes.Snowflake(5412231),
             member=object(),
             user=object(),
@@ -81,6 +80,9 @@ def mock_command_interaction(self, mock_app):
             context=applications.ApplicationContextType.PRIVATE_CHANNEL,
         )
 
+    def test_channel_id_property(self, mock_command_interaction):
+        assert mock_command_interaction.channel_id == 3123123
+
     def test_build_response(self, mock_command_interaction, mock_app):
         mock_app.rest.interaction_message_builder = mock.Mock()
         builder = mock_command_interaction.build_response()
@@ -97,30 +99,6 @@ def test_build_deferred_response(self, mock_command_interaction, mock_app):
             base_interactions.ResponseType.DEFERRED_MESSAGE_CREATE
         )
 
-    @pytest.mark.asyncio
-    async def test_fetch_channel(self, mock_command_interaction, mock_app):
-        mock_app.rest.fetch_channel.return_value = mock.Mock(channels.TextableGuildChannel)
-        assert await mock_command_interaction.fetch_channel() is mock_app.rest.fetch_channel.return_value
-
-        mock_app.rest.fetch_channel.assert_awaited_once_with(3123123)
-
-    def test_get_channel(self, mock_command_interaction, mock_app):
-        mock_app.cache.get_guild_channel.return_value = mock.Mock(channels.TextableGuildChannel)
-
-        assert mock_command_interaction.get_channel() is mock_app.cache.get_guild_channel.return_value
-        mock_app.cache.get_guild_channel.assert_called_once_with(3123123)
-
-    def test_get_channel_when_not_cached(self, mock_command_interaction, mock_app):
-        mock_app.cache.get_guild_channel.return_value = None
-
-        assert mock_command_interaction.get_channel() is None
-        mock_app.cache.get_guild_channel.assert_called_once_with(3123123)
-
-    def test_get_channel_without_cache(self, mock_command_interaction):
-        mock_command_interaction.app = mock.Mock(traits.RESTAware)
-
-        assert mock_command_interaction.get_channel() is None
-
 
 class TestAutocompleteInteraction:
     @pytest.fixture
@@ -129,10 +107,11 @@ def mock_autocomplete_interaction(self, mock_app):
             app=mock_app,
             id=snowflakes.Snowflake(2312312),
             type=base_interactions.InteractionType.APPLICATION_COMMAND,
-            channel_id=snowflakes.Snowflake(3123123),
+            channel=mock.Mock(3123123),
             guild_id=snowflakes.Snowflake(5412231),
             guild_locale="en-US",
             locale="en-US",
+            app_permissions=123321,
             member=object(),
             user=object(),
             token="httptptptptptptptp",
diff --git a/tests/hikari/interactions/test_component_interactions.py b/tests/hikari/interactions/test_component_interactions.py
index 1737f9ab..cbcb7640 100644
--- a/tests/hikari/interactions/test_component_interactions.py
+++ b/tests/hikari/interactions/test_component_interactions.py
@@ -24,10 +24,8 @@
 import pytest
 
 from hikari import applications
-from hikari import channels
 from hikari import monetization
 from hikari import snowflakes
-from hikari import traits
 from hikari.interactions import base_interactions
 from hikari.interactions import component_interactions
 
@@ -44,8 +42,8 @@ def mock_component_interaction(self, mock_app):
             app=mock_app,
             id=snowflakes.Snowflake(2312312),
             type=base_interactions.InteractionType.MESSAGE_COMPONENT,
-            channel_id=snowflakes.Snowflake(3123123),
             guild_id=snowflakes.Snowflake(5412231),
+            channel=object(),
             member=object(),
             user=object(),
             token="httptptptptptptptp",
@@ -100,68 +98,3 @@ def test_build_deferred_response(self, mock_component_interaction, mock_app):
     def test_build_deferred_response_with_invalid_type(self, mock_component_interaction):
         with pytest.raises(ValueError, match="Invalid type passed for a deferred response"):
             mock_component_interaction.build_deferred_response(33333)
-
-    @pytest.mark.asyncio
-    async def test_fetch_channel(self, mock_component_interaction, mock_app):
-        mock_app.rest.fetch_channel.return_value = mock.Mock(channels.TextableChannel)
-
-        assert await mock_component_interaction.fetch_channel() is mock_app.rest.fetch_channel.return_value
-
-        mock_app.rest.fetch_channel.assert_awaited_once_with(3123123)
-
-    def test_get_channel(self, mock_component_interaction, mock_app):
-        mock_app.cache.get_guild_channel.return_value = mock.Mock(channels.GuildTextChannel)
-
-        assert mock_component_interaction.get_channel() is mock_app.cache.get_guild_channel.return_value
-
-        mock_app.cache.get_guild_channel.assert_called_once_with(3123123)
-
-    def test_get_channel_when_not_cached(self, mock_component_interaction, mock_app):
-        mock_app.cache.get_guild_channel.return_value = None
-
-        assert mock_component_interaction.get_channel() is None
-
-        mock_app.cache.get_guild_channel.assert_called_once_with(3123123)
-
-    def test_get_channel_without_cache(self, mock_component_interaction):
-        mock_component_interaction.app = mock.Mock(traits.RESTAware)
-
-        assert mock_component_interaction.get_channel() is None
-
-    @pytest.mark.asyncio
-    async def test_fetch_guild(self, mock_component_interaction, mock_app):
-        mock_component_interaction.guild_id = 43123123
-
-        assert await mock_component_interaction.fetch_guild() is mock_app.rest.fetch_guild.return_value
-
-        mock_app.rest.fetch_guild.assert_awaited_once_with(43123123)
-
-    @pytest.mark.asyncio
-    async def test_fetch_guild_for_dm_interaction(self, mock_component_interaction, mock_app):
-        mock_component_interaction.guild_id = None
-
-        assert await mock_component_interaction.fetch_guild() is None
-
-        mock_app.rest.fetch_guild.assert_not_called()
-
-    def test_get_guild(self, mock_component_interaction, mock_app):
-        mock_component_interaction.guild_id = 874356
-
-        assert mock_component_interaction.get_guild() is mock_app.cache.get_guild.return_value
-
-        mock_app.cache.get_guild.assert_called_once_with(874356)
-
-    def test_get_guild_for_dm_interaction(self, mock_component_interaction, mock_app):
-        mock_component_interaction.guild_id = None
-
-        assert mock_component_interaction.get_guild() is None
-
-        mock_app.cache.get_guild.assert_not_called()
-
-    def test_get_guild_when_cacheless(self, mock_component_interaction, mock_app):
-        mock_component_interaction.guild_id = 321123
-        mock_component_interaction.app = mock.Mock(traits.RESTAware)
-
-        assert mock_component_interaction.get_guild() is None
-
-        mock_app.cache.get_guild.assert_not_called()
diff --git a/tests/hikari/interactions/test_modal_interactions.py b/tests/hikari/interactions/test_modal_interactions.py
index 6837e7f8..b7f6f7f4 100644
--- a/tests/hikari/interactions/test_modal_interactions.py
+++ b/tests/hikari/interactions/test_modal_interactions.py
@@ -24,7 +24,6 @@
 import pytest
 
 from hikari import applications
-from hikari import channels
 from hikari import components
 from hikari import monetization
 from hikari import snowflakes
@@ -46,7 +45,7 @@ def mock_modal_interaction(self, mock_app):
             app=mock_app,
             id=snowflakes.Snowflake(2312312),
             type=base_interactions.InteractionType.APPLICATION_COMMAND,
-            channel_id=snowflakes.Snowflake(3123123),
+            channel=object(),
             guild_id=snowflakes.Snowflake(5412231),
             member=object(),
             user=object(),
@@ -99,26 +98,6 @@ def test_build_deferred_response(self, mock_modal_interaction, mock_app):
         assert response is mock_app.rest.interaction_deferred_builder.return_value
         mock_app.rest.interaction_deferred_builder.assert_called_once()
 
-    @pytest.mark.asyncio
-    async def test_fetch_channel(self, mock_modal_interaction, mock_app):
-        mock_app.rest.fetch_channel.return_value = mock.Mock(channels.TextableChannel)
-
-        assert await mock_modal_interaction.fetch_channel() is mock_app.rest.fetch_channel.return_value
-
-        mock_app.rest.fetch_channel.assert_awaited_once_with(3123123)
-
-    def test_get_channel(self, mock_modal_interaction, mock_app):
-        mock_app.cache.get_guild_channel.return_value = mock.Mock(channels.GuildTextChannel)
-
-        assert mock_modal_interaction.get_channel() is mock_app.cache.get_guild_channel.return_value
-
-        mock_app.cache.get_guild_channel.assert_called_once_with(3123123)
-
-    def test_get_channel_without_cache(self, mock_modal_interaction):
-        mock_modal_interaction.app = mock.Mock(traits.RESTAware)
-
-        assert mock_modal_interaction.get_channel() is None
-
     @pytest.mark.asyncio
     async def test_fetch_guild(self, mock_modal_interaction, mock_app):
         mock_modal_interaction.guild_id = 43123123