Skip to content

fix(channel): improve handling of placeholder guilds in GuildChannel and Thread #1287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/1287.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 10 additions & 1 deletion disnake/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -679,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.

Expand Down
38 changes: 38 additions & 0 deletions disnake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 (
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion disnake/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -429,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.
Expand Down