diff --git a/discord/channel.py b/discord/channel.py index 907d0b4bd..8c212c0cb 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -2890,7 +2890,12 @@ class DMChannel(discord.abc.Messageable, discord.abc.PrivateChannel, Hashable): def __init__(self, *, me: ClientUser, state: ConnectionState, data: DMChannelPayload): self._state: ConnectionState = state - self.recipient: Optional[User] = state.store_user(data['recipients'][0]) + self.recipient: Optional[User] = None + + recipients = data.get('recipients') + if recipients is not None: + self.recipient = state.store_user(recipients[0]) + self.me: ClientUser = me self.id: int = int(data['id']) diff --git a/discord/interactions.py b/discord/interactions.py index f9ed7976d..f62a08703 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -25,6 +25,8 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations + +import logging from typing import Any, Dict, Optional, Generic, TYPE_CHECKING, Sequence, Tuple, Union import asyncio import datetime @@ -33,7 +35,7 @@ from . import utils from .enums import try_enum, Locale, InteractionType, InteractionResponseType from .errors import InteractionResponded, HTTPException, ClientException, DiscordException from .flags import MessageFlags -from .channel import PartialMessageable, ChannelType +from .channel import ChannelType from ._types import ClientT from .user import User @@ -44,6 +46,7 @@ from .http import handle_message_parameters from .webhook.async_ import async_context, Webhook, interaction_response_params, interaction_message_response_params from .app_commands.namespace import Namespace from .app_commands.translator import locale_str, TranslationContext, TranslationContextLocation +from .channel import _threaded_channel_factory __all__ = ( 'Interaction', @@ -69,12 +72,19 @@ if TYPE_CHECKING: from .ui.view import View from .app_commands.models import Choice, ChoiceT from .ui.modal import Modal - from .channel import VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel + from .channel import VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel, DMChannel, GroupChannel from .threads import Thread from .app_commands.commands import Command, ContextMenu InteractionChannel = Union[ - VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel, Thread, PartialMessageable + VoiceChannel, + StageChannel, + TextChannel, + ForumChannel, + CategoryChannel, + Thread, + DMChannel, + GroupChannel, ] MISSING: Any = utils.MISSING @@ -96,8 +106,10 @@ class Interaction(Generic[ClientT]): The interaction type. guild_id: Optional[:class:`int`] The guild ID the interaction was sent from. - channel_id: Optional[:class:`int`] - The channel ID the interaction was sent from. + channel: Optional[Union[:class:`abc.GuildChannel`, :class:`abc.PrivateChannel`, :class:`Thread`]] + The channel the interaction was sent from. + + Note that due to a Discord limitation, if sent from a DM channel :attr:`~DMChannel.recipient` is ``None``. application_id: :class:`int` The application ID that the interaction was for. user: Union[:class:`User`, :class:`Member`] @@ -128,7 +140,6 @@ class Interaction(Generic[ClientT]): 'id', 'type', 'guild_id', - 'channel_id', 'data', 'application_id', 'message', @@ -148,7 +159,7 @@ class Interaction(Generic[ClientT]): '_original_response', '_cs_response', '_cs_followup', - '_cs_channel', + 'channel', '_cs_namespace', '_cs_command', ) @@ -171,8 +182,24 @@ class Interaction(Generic[ClientT]): self.data: Optional[InteractionData] = data.get('data') self.token: str = data['token'] self.version: int = data['version'] - self.channel_id: Optional[int] = utils._get_as_snowflake(data, 'channel_id') self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id') + self.channel: Optional[InteractionChannel] = None + + raw_channel = data.get('channel', {}) + raw_ch_type = raw_channel.get('type') + if raw_ch_type is not None: + factory, ch_type = _threaded_channel_factory(raw_ch_type) # type is never None + if factory is None: + logging.info('Unknown channel type {type} for channel ID {id}.'.format_map(raw_channel)) + else: + if ch_type in (ChannelType.group, ChannelType.private): + channel = factory(me=self._client.user, data=raw_channel, state=self._state) # type: ignore + else: + guild = self._state._get_or_create_unavailable_guild(self.guild_id) # type: ignore + channel = factory(guild=guild, state=self._state, data=raw_channel) # type: ignore + + self.channel = channel + self.application_id: int = int(data['application_id']) self.locale: Locale = try_enum(Locale, data.get('locale', 'en-US')) @@ -227,21 +254,10 @@ class Interaction(Generic[ClientT]): """Optional[:class:`Guild`]: The guild the interaction was sent from.""" return self._state and self._state._get_guild(self.guild_id) - @utils.cached_slot_property('_cs_channel') - def channel(self) -> Optional[InteractionChannel]: - """Optional[Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]]: The channel the interaction was sent from. - - Note that due to a Discord limitation, DM channels are not resolved since there is - no data to complete them. These are :class:`PartialMessageable` instead. - """ - guild = self.guild - channel = guild and guild._resolve_channel(self.channel_id) - if channel is None: - if self.channel_id is not None: - type = ChannelType.text if self.guild_id is not None else ChannelType.private - return PartialMessageable(state=self._state, guild_id=self.guild_id, id=self.channel_id, type=type) - return None - return channel + @property + def channel_id(self) -> Optional[int]: + """Optional[:class:`int`]: The ID of the channel the interaction was sent from.""" + return self.channel.id if self.channel is not None else None @property def permissions(self) -> Permissions: diff --git a/discord/types/channel.py b/discord/types/channel.py index 421232b45..3068185f1 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -150,16 +150,24 @@ class ForumChannel(_BaseTextChannel): GuildChannel = Union[TextChannel, NewsChannel, VoiceChannel, CategoryChannel, StageChannel, ThreadChannel, ForumChannel] -class DMChannel(_BaseChannel): +class _BaseDMChannel(_BaseChannel): type: Literal[1] last_message_id: Optional[Snowflake] + + +class DMChannel(_BaseDMChannel): recipients: List[PartialUser] +class InteractionDMChannel(_BaseDMChannel): + recipients: NotRequired[List[PartialUser]] + + class GroupDMChannel(_BaseChannel): type: Literal[3] icon: Optional[str] owner_id: Snowflake + recipients: List[PartialUser] Channel = Union[GuildChannel, DMChannel, GroupDMChannel] diff --git a/discord/types/interactions.py b/discord/types/interactions.py index cfbaf310e..039203dfa 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -27,7 +27,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Dict, List, Literal, TypedDict, Union from typing_extensions import NotRequired -from .channel import ChannelTypeWithoutThread, ThreadMetadata +from .channel import ChannelTypeWithoutThread, ThreadMetadata, GuildChannel, InteractionDMChannel, GroupDMChannel from .threads import ThreadType from .member import Member from .message import Attachment @@ -204,6 +204,7 @@ class _BaseInteraction(TypedDict): version: Literal[1] guild_id: NotRequired[Snowflake] channel_id: NotRequired[Snowflake] + channel: Union[GuildChannel, InteractionDMChannel, GroupDMChannel] app_permissions: NotRequired[str] locale: NotRequired[str] guild_locale: NotRequired[str]