Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
Loading