From 0106d30369b88a859c25d704a25dd02655a4dbca Mon Sep 17 00:00:00 2001 From: shiftinv Date: Thu, 6 Mar 2025 12:47:07 +0100 Subject: [PATCH 1/4] fix: improve handling of `Object` guilds in `GuildChannel` and `Thread` --- disnake/channel.py | 38 ++++++++++++++++++++++++++++++++++++++ disnake/threads.py | 5 ++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/disnake/channel.py b/disnake/channel.py index 800d618001..2378f819c4 100644 --- a/disnake/channel.py +++ b/disnake/channel.py @@ -44,6 +44,7 @@ from .flags import ChannelFlags, MessageFlags from .iterators import ArchivedThreadIterator from .mixins import Hashable +from .object import Object from .partial_emoji import PartialEmoji from .permissions import PermissionOverwrite, Permissions from .soundboard import GuildSoundboardSound, PartialSoundboardSound, SoundboardSound @@ -341,6 +342,8 @@ def permissions_for( @property def members(self) -> List[Member]: """List[:class:`Member`]: Returns all members that can see this channel.""" + if isinstance(self.guild, Object): + return [] return [m for m in self.guild.members if self.permissions_for(m).view_channel] @property @@ -349,6 +352,8 @@ def threads(self) -> List[Thread]: .. versionadded:: 2.0 """ + if isinstance(self.guild, Object): + return [] return [thread for thread in self.guild._threads.values() if thread.parent_id == self.id] def is_nsfw(self) -> bool: @@ -978,6 +983,8 @@ def get_thread(self, thread_id: int, /) -> Optional[Thread]: Optional[:class:`Thread`] The returned thread or ``None`` if not found. """ + if isinstance(self.guild, Object): + return None return self.guild.get_thread(thread_id) @overload @@ -1217,6 +1224,9 @@ def _sorting_bucket(self) -> int: @property def members(self) -> List[Member]: """List[:class:`Member`]: Returns all members that are currently inside this voice channel.""" + if isinstance(self.guild, Object): + return [] + ret = [] for user_id, state in self.guild._voice_states.items(): if state.channel and state.channel.id == self.id: @@ -1241,6 +1251,9 @@ def voice_states(self) -> Dict[int, VoiceState]: Mapping[:class:`int`, :class:`VoiceState`] The mapping of member ID to a voice state. """ + if isinstance(self.guild, Object): + return {} + return { key: value for key, value in self.guild._voice_states.items() @@ -2277,6 +2290,8 @@ def instance(self) -> Optional[StageInstance]: .. versionadded:: 2.0 """ + if isinstance(self.guild, Object): + return None return utils.get(self.guild.stage_instances, channel_id=self.id) async def create_instance( @@ -3090,6 +3105,8 @@ def channels(self) -> List[GuildChannelType]: These are sorted by the official Discord UI, which places voice channels below the text channels. """ + if isinstance(self.guild, Object): + return [] def comparator(channel): return ( @@ -3104,6 +3121,9 @@ def comparator(channel): @property def text_channels(self) -> List[TextChannel]: """List[:class:`TextChannel`]: Returns the text channels that are under this category.""" + if isinstance(self.guild, Object): + return [] + ret = [ c for c in self.guild.channels @@ -3115,6 +3135,9 @@ def text_channels(self) -> List[TextChannel]: @property def voice_channels(self) -> List[VoiceChannel]: """List[:class:`VoiceChannel`]: Returns the voice channels that are under this category.""" + if isinstance(self.guild, Object): + return [] + ret = [ c for c in self.guild.channels @@ -3129,6 +3152,9 @@ def stage_channels(self) -> List[StageChannel]: .. versionadded:: 1.7 """ + if isinstance(self.guild, Object): + return [] + ret = [ c for c in self.guild.channels @@ -3143,6 +3169,9 @@ def forum_channels(self) -> List[ForumChannel]: .. versionadded:: 2.5 """ + if isinstance(self.guild, Object): + return [] + ret = [ c for c in self.guild.channels @@ -3157,6 +3186,9 @@ def media_channels(self) -> List[MediaChannel]: .. versionadded:: 2.10 """ + if isinstance(self.guild, Object): + return [] + ret = [ c for c in self.guild.channels @@ -3368,11 +3400,15 @@ def permissions_for( @property def members(self) -> List[Member]: """List[:class:`Member`]: Returns all members that can see this channel.""" + if isinstance(self.guild, Object): + return [] return [m for m in self.guild.members if self.permissions_for(m).view_channel] @property def threads(self) -> List[Thread]: """List[:class:`Thread`]: Returns all the threads that you can see.""" + if isinstance(self.guild, Object): + return [] return [thread for thread in self.guild._threads.values() if thread.parent_id == self.id] def is_nsfw(self) -> bool: @@ -3466,6 +3502,8 @@ def get_thread(self, thread_id: int, /) -> Optional[Thread]: Optional[:class:`Thread`] The returned thread of ``None`` if not found. """ + if isinstance(self.guild, Object): + return None return self.guild.get_thread(thread_id) @overload diff --git a/disnake/threads.py b/disnake/threads.py index b7a11f25c9..5c069196a3 100644 --- a/disnake/threads.py +++ b/disnake/threads.py @@ -11,6 +11,7 @@ from .errors import ClientException from .flags import ChannelFlags from .mixins import Hashable +from .object import Object from .partial_emoji import PartialEmoji, _EmojiTag from .permissions import Permissions from .utils import MISSING, _get_as_snowflake, _unique, parse_time, snowflake_time @@ -244,12 +245,14 @@ def type(self) -> ThreadType: @property def parent(self) -> Optional[Union[TextChannel, ForumChannel, MediaChannel]]: """Optional[Union[:class:`TextChannel`, :class:`ForumChannel`, :class:`MediaChannel`]]: The parent channel this thread belongs to.""" + if isinstance(self.guild, Object): + return None return self.guild.get_channel(self.parent_id) # type: ignore @property def owner(self) -> Optional[Member]: """Optional[:class:`Member`]: The member this thread belongs to.""" - if self.owner_id is None: + if self.owner_id is None or isinstance(self.guild, Object): return None return self.guild.get_member(self.owner_id) From d37e99528183850c4110eaf1378269f24ea0cfd0 Mon Sep 17 00:00:00 2001 From: shiftinv Date: Thu, 6 Mar 2025 12:55:08 +0100 Subject: [PATCH 2/4] docs: add changelog entry --- changelog/1287.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/1287.bugfix.rst diff --git a/changelog/1287.bugfix.rst b/changelog/1287.bugfix.rst new file mode 100644 index 0000000000..cef90dc132 --- /dev/null +++ b/changelog/1287.bugfix.rst @@ -0,0 +1 @@ +Improve checks for partial :class:`Guild`\s in guild-dependent attributes of channels and threads, which could previously raise errors with user-installed apps. From b1ca53d6edaf4493966f3b340bc10e4141caf193 Mon Sep 17 00:00:00 2001 From: shiftinv Date: Fri, 11 Apr 2025 13:25:01 +0200 Subject: [PATCH 3/4] fix: handle two additional cases in abc --- disnake/abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/disnake/abc.py b/disnake/abc.py index 85c63dd592..7a3836d93f 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -600,6 +600,8 @@ def category(self) -> Optional[CategoryChannel]: If there is no category then this is ``None``. """ + if isinstance(self.guild, Object): + return None return self.guild.get_channel(self.category_id) # type: ignore @property @@ -611,7 +613,7 @@ def permissions_synced(self) -> bool: .. versionadded:: 1.3 """ - if self.category_id is None: + if self.category_id is None or isinstance(self.guild, Object): return False category = self.guild.get_channel(self.category_id) From edd744fbf5f7b55f7b646df4177834fafddd65cd Mon Sep 17 00:00:00 2001 From: shiftinv Date: Wed, 23 Apr 2025 11:29:42 +0200 Subject: [PATCH 4/4] docs: add note for `permissions_for` in unknown guilds --- disnake/abc.py | 7 +++++++ disnake/threads.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/disnake/abc.py b/disnake/abc.py index 7a3836d93f..37d9afa59c 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -681,6 +681,13 @@ def permissions_for( - The default role permission overwrites - The permission overwrites of the role used as a parameter + .. note:: + If the channel originated from an :class:`.Interaction` and + the :attr:`.guild` attribute is unavailable, such as with + user-installed applications in guilds, this method will not work + due to an API limitation. + Consider using :attr:`.Interaction.permissions` or :attr:`~.Interaction.app_permissions` instead. + .. versionchanged:: 2.0 The object passed in can now be a role object. diff --git a/disnake/threads.py b/disnake/threads.py index 5c069196a3..4d90eac85d 100644 --- a/disnake/threads.py +++ b/disnake/threads.py @@ -432,6 +432,13 @@ def permissions_for( :attr:`GuildChannel.permissions_for <.abc.GuildChannel.permissions_for>` method directly. + .. note:: + If the thread originated from an :class:`.Interaction` and + the :attr:`.guild` attribute is unavailable, such as with + user-installed applications in guilds, this method will not work + due to an API limitation. + Consider using :attr:`.Interaction.permissions` or :attr:`~.Interaction.app_permissions` instead. + .. versionchanged:: 2.9 Properly takes :attr:`Permissions.send_messages_in_threads` into consideration.