Skip to content

Commit

Permalink
fix: show the proper permissions in threads (#1047)
Browse files Browse the repository at this point in the history
Co-authored-by: shiftinv <[email protected]>
  • Loading branch information
onerandomusername and shiftinv authored Jun 19, 2023
1 parent 375840e commit 1843145
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 72 deletions.
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 :class:`Thread`\s to use :attr:`Permissions.send_messages_in_threads` instead of :attr:`Permissions.send_messages` for calculating implicit permissions.
33 changes: 17 additions & 16 deletions disnake/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,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 @@ -784,25 +799,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
33 changes: 28 additions & 5 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 @@ -423,9 +423,15 @@ def permissions_for(
or :class:`~disnake.Role`.
Since threads do not have their own permissions, they inherit them
from the parent channel. This is a convenience method for
calling :meth:`~disnake.TextChannel.permissions_for` on the
parent channel.
from the parent channel.
However, the permission context is different compared to a normal channel,
so this method has different behavior than calling the parent's
:attr:`GuildChannel.permissions_for <.abc.GuildChannel.permissions_for>`
method directly.
.. versionchanged:: 2.9
Properly takes :attr:`Permissions.send_messages_in_threads`
into consideration.
Parameters
----------
Expand Down Expand Up @@ -460,7 +466,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

0 comments on commit 1843145

Please sign in to comment.