Skip to content

fix: show the proper permissions in threads #1047

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

Merged
merged 12 commits into from
Jun 19, 2023
1 change: 1 addition & 0 deletions changelog/1047.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix permission resolution for :meth:`.Thread.permissions_for` when :attr:`Permissions.send_messages` is ``False``, but :attr:`Permissions.send_messages_in_threads` is ``True``.
33 changes: 17 additions & 16 deletions disnake/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,21 @@ def jump_url(self) -> str:
"""
return f"https://discord.com/channels/{self.guild.id}/{self.id}"

def _apply_implict_permissions(self, base: Permissions) -> None:
# if you can't send a message in a channel then you can't have certain
# permissions as well
if not base.send_messages:
base.send_tts_messages = False
base.send_voice_messages = False
base.mention_everyone = False
base.embed_links = False
base.attach_files = False

# if you can't view a channel then you have no permissions there
if not base.view_channel:
denied = Permissions.all_channel()
base.value &= ~denied.value

def permissions_for(
self,
obj: Union[Member, Role],
Expand Down Expand Up @@ -780,25 +795,11 @@ def permissions_for(
base.handle_overwrite(allow=overwrite.allow, deny=overwrite.deny)
break

# if you can't send a message in a channel then you can't have certain
# permissions as well
if not base.send_messages:
base.send_tts_messages = False
base.send_voice_messages = False
base.mention_everyone = False
base.embed_links = False
base.attach_files = False

# if you can't view a channel then you have no permissions there
if not base.view_channel:
denied = Permissions.all_channel()
base.value &= ~denied.value

# if you have a timeout then you can't have any permissions
# except read messages and read message history
if not ignore_timeout and obj.current_timeout:
denied = Permissions(view_channel=True, read_message_history=True)
base.value &= denied.value
allowed = Permissions(view_channel=True, read_message_history=True)
base.value &= allowed.value

return base

Expand Down
95 changes: 44 additions & 51 deletions disnake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ def permissions_for(
ignore_timeout: bool = MISSING,
) -> Permissions:
base = super().permissions_for(obj, ignore_timeout=ignore_timeout)
self._apply_implict_permissions(base)

# text channels do not have voice related permissions
denied = Permissions.voice()
Expand Down Expand Up @@ -1183,6 +1184,35 @@ def voice_states(self) -> Dict[int, VoiceState]:
if value.channel and value.channel.id == self.id
}

@utils.copy_doc(disnake.abc.GuildChannel.permissions_for)
def permissions_for(
self,
obj: Union[Member, Role],
/,
*,
ignore_timeout: bool = MISSING,
) -> Permissions:
base = super().permissions_for(obj, ignore_timeout=ignore_timeout)
self._apply_implict_permissions(base)

# voice channels cannot be edited by people who can't connect to them
# It also implicitly denies all other voice perms
if not base.connect:
denied = Permissions.voice()
# voice channels also deny all text related permissions
denied.value |= Permissions.text().value
# stage channels remove the stage permissions
denied.value |= Permissions.stage().value

denied.update(
manage_channels=True,
manage_roles=True,
manage_events=True,
manage_webhooks=True,
)
base.value &= ~denied.value
return base


class VoiceChannel(disnake.abc.Messageable, VocalGuildChannel):
"""Represents a Discord guild voice channel.
Expand Down Expand Up @@ -1442,32 +1472,6 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage:

return PartialMessage(channel=self, id=message_id)

@utils.copy_doc(disnake.abc.GuildChannel.permissions_for)
def permissions_for(
self,
obj: Union[Member, Role],
/,
*,
ignore_timeout: bool = MISSING,
) -> Permissions:
base = super().permissions_for(obj, ignore_timeout=ignore_timeout)

# voice channels cannot be edited by people who can't connect to them
# It also implicitly denies all other voice perms
if not base.connect:
denied = Permissions.voice()
# voice channels also deny all text related permissions
denied.value |= Permissions.text().value

denied.update(
manage_channels=True,
manage_roles=True,
manage_events=True,
manage_webhooks=True,
)
base.value &= ~denied.value
return base

# if only these parameters are passed, `_move` is called and no channel will be returned
@overload
async def edit(
Expand Down Expand Up @@ -2183,31 +2187,6 @@ def instance(self) -> Optional[StageInstance]:
"""
return utils.get(self.guild.stage_instances, channel_id=self.id)

@utils.copy_doc(disnake.abc.GuildChannel.permissions_for)
def permissions_for(
self,
obj: Union[Member, Role],
/,
*,
ignore_timeout: bool = MISSING,
) -> Permissions:
base = super().permissions_for(obj, ignore_timeout=ignore_timeout)

# voice channels cannot be edited by people who can't connect to them
# It also implicitly denies all other channel permissions.
if not base.connect:
denied = Permissions.voice()
denied.value |= Permissions.text().value
denied.value |= Permissions.stage().value
denied.update(
manage_channels=True,
manage_roles=True,
manage_events=True,
manage_webhooks=True,
)
base.value &= ~denied.value
return base

async def create_instance(
self,
*,
Expand Down Expand Up @@ -2787,6 +2766,19 @@ def type(self) -> Literal[ChannelType.category]:
"""
return ChannelType.category

@utils.copy_doc(disnake.abc.GuildChannel.permissions_for)
def permissions_for(
self,
obj: Union[Member, Role],
/,
*,
ignore_timeout: bool = MISSING,
) -> Permissions:
base = super().permissions_for(obj, ignore_timeout=ignore_timeout)
self._apply_implict_permissions(base)

return base

def is_nsfw(self) -> bool:
"""Whether the category is marked as NSFW.

Expand Down Expand Up @@ -3329,6 +3321,7 @@ def permissions_for(
ignore_timeout: bool = MISSING,
) -> Permissions:
base = super().permissions_for(obj, ignore_timeout=ignore_timeout)
self._apply_implict_permissions(base)

# forum channels do not have voice related permissions
denied = Permissions.voice()
Expand Down
21 changes: 19 additions & 2 deletions disnake/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import time
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Literal, Optional, Sequence, Union

from .abc import Messageable
from .abc import GuildChannel, Messageable
from .enums import ChannelType, ThreadArchiveDuration, try_enum, try_enum_to_int
from .errors import ClientException
from .flags import ChannelFlags
Expand Down Expand Up @@ -460,7 +460,24 @@ def permissions_for(
parent = self.parent
if parent is None:
raise ClientException("Parent channel not found")
return parent.permissions_for(obj, ignore_timeout=ignore_timeout)
# n.b. GuildChannel is used here so implicit overrides are not applied based on send_messages
base = GuildChannel.permissions_for(parent, obj, ignore_timeout=ignore_timeout)

# if you can't send a message in a channel then you can't have certain
# permissions as well
if not base.send_messages_in_threads:
base.send_tts_messages = False
base.send_voice_messages = False
base.mention_everyone = False
base.embed_links = False
base.attach_files = False

# if you can't view a channel then you have no permissions there
if not base.view_channel:
denied = Permissions.all_channel()
base.value &= ~denied.value

return base

async def delete_messages(self, messages: Iterable[Snowflake]) -> None:
"""|coro|
Expand Down