From 18ae88b791aea9062b0afc253c20133d2467e337 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Tue, 3 May 2022 10:49:52 -0400 Subject: [PATCH] Improve generic duck type programming with PartialMessageable This adds jump_url, permissions_for, and created_at. Luckily, most cases of this type being constructed already have the guild_id at creation time. --- discord/channel.py | 39 ++++++++++++++++++++++++++++++++++++++- discord/client.py | 11 +++++++++-- discord/state.py | 2 +- discord/webhook/async_.py | 4 ++-- discord/webhook/sync.py | 2 +- 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index 6514396f7..7f1c0fe93 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -3031,13 +3031,16 @@ class PartialMessageable(discord.abc.Messageable, Hashable): ----------- id: :class:`int` The channel ID associated with this partial messageable. + guild_id: Optional[:class:`int`] + The guild ID associated with this partial messageable. type: Optional[:class:`ChannelType`] The channel type associated with this partial messageable, if given. """ - def __init__(self, state: ConnectionState, id: int, type: Optional[ChannelType] = None): + def __init__(self, state: ConnectionState, id: int, guild_id: Optional[int] = None, type: Optional[ChannelType] = None): self._state: ConnectionState = state self.id: int = id + self.guild_id: Optional[int] = guild_id self.type: Optional[ChannelType] = type self.last_message_id: Optional[int] = None @@ -3047,6 +3050,40 @@ class PartialMessageable(discord.abc.Messageable, Hashable): async def _get_channel(self) -> PartialMessageable: return self + @property + def jump_url(self) -> str: + """:class:`str`: Returns a URL that allows the client to jump to the channel.""" + if self.guild_id is None: + return f'https://discord.com/channels/@me/{self.id}' + return f'https://discord.com/channels/{self.guild_id}/{self.id}' + + @property + def created_at(self) -> datetime.datetime: + """:class:`datetime.datetime`: Returns the direct message channel's creation time in UTC.""" + return utils.snowflake_time(self.id) + + def permissions_for(self, obj: Any = None, /) -> Permissions: + """Handles permission resolution for a :class:`User`. + + This function is there for compatibility with other channel types. + + Since partial messageables cannot reasonably have the concept of + permissions, this will always return :meth:`Permissions.none`. + + Parameters + ----------- + obj: :class:`User` + The user to check permissions for. This parameter is ignored + but kept for compatibility with other ``permissions_for`` methods. + + Returns + -------- + :class:`Permissions` + The resolved permissions. + """ + + return Permissions.none() + def get_partial_message(self, message_id: int, /) -> PartialMessage: """Creates a :class:`PartialMessage` from the message ID. diff --git a/discord/client.py b/discord/client.py index e338602cf..36829f7a1 100644 --- a/discord/client.py +++ b/discord/client.py @@ -1039,7 +1039,9 @@ class Client: """ return self._connection.get_channel(id) # type: ignore # The cache contains all channel types - def get_partial_messageable(self, id: int, *, type: Optional[ChannelType] = None) -> PartialMessageable: + def get_partial_messageable( + self, id: int, *, guild_id: Optional[int] = None, type: Optional[ChannelType] = None + ) -> PartialMessageable: """Returns a partial messageable with the given channel ID. This is useful if you have a channel_id but don't want to do an API call @@ -1051,6 +1053,11 @@ class Client: ----------- id: :class:`int` The channel ID to create a partial messageable for. + guild_id: Optional[:class:`int`] + The optional guild ID to create a partial messageable for. + + This is not required to actually send messages, but it does allow the + :meth:`PartialMessageable.jump_url` property to form a well formed URL. type: Optional[:class:`.ChannelType`] The underlying channel type for the partial messageable. @@ -1059,7 +1066,7 @@ class Client: :class:`.PartialMessageable` The partial messageable """ - return PartialMessageable(state=self._connection, id=id, type=type) + return PartialMessageable(state=self._connection, id=id, guild_id=guild_id, type=type) def get_stage_instance(self, id: int, /) -> Optional[StageInstance]: """Returns a stage instance with the given stage channel ID. diff --git a/discord/state.py b/discord/state.py index eff5fd1ab..4e982c68e 100644 --- a/discord/state.py +++ b/discord/state.py @@ -873,7 +873,7 @@ class ConnectionState: else: channel = guild and guild._resolve_channel(channel_id) - return channel or PartialMessageable(state=self, id=channel_id), guild + return channel or PartialMessageable(state=self, guild_id=guild_id, id=channel_id), guild async def _delete_messages(self, channel_id, messages, reason: Optional[str] = None) -> None: delete_message = self.http.delete_message diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 5cfb2fd69..268cee8fa 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -1157,14 +1157,14 @@ class Webhook(BaseWebhook): state = _WebhookState(self, parent=self._state, thread=thread) # state may be artificial (unlikely at this point...) if thread is MISSING: - channel = self.channel or PartialMessageable(state=self._state, id=int(data['channel_id'])) # type: ignore + channel = self.channel or PartialMessageable(state=self._state, guild_id=self.guild_id, id=int(data['channel_id'])) # type: ignore else: channel = self.channel if isinstance(channel, TextChannel): channel = channel.get_thread(thread.id) if channel is None: - channel = PartialMessageable(state=self._state, id=int(data['channel_id'])) # type: ignore + channel = PartialMessageable(state=self._state, guild_id=self.guild_id, id=int(data['channel_id'])) # type: ignore # state is artificial return WebhookMessage(data=data, state=state, channel=channel) # type: ignore diff --git a/discord/webhook/sync.py b/discord/webhook/sync.py index 323612163..b1fb36208 100644 --- a/discord/webhook/sync.py +++ b/discord/webhook/sync.py @@ -842,7 +842,7 @@ class SyncWebhook(BaseWebhook): def _create_message(self, data: MessagePayload, *, thread: Snowflake = MISSING) -> SyncWebhookMessage: state = _WebhookState(self, parent=self._state, thread=thread) # state may be artificial (unlikely at this point...) - channel = self.channel or PartialMessageable(state=self._state, id=int(data['channel_id'])) # type: ignore + channel = self.channel or PartialMessageable(state=self._state, guild_id=self.guild_id, id=int(data['channel_id'])) # type: ignore # state is artificial return SyncWebhookMessage(data=data, state=state, channel=channel) # type: ignore