Browse Source

Merge c2ec97aeab into 1add8a0b40

pull/10487/merge
harumaki4649 2 weeks ago
committed by GitHub
parent
commit
296a8b2c88
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 14
      discord/abc.py
  2. 88
      discord/channel.py
  3. 8
      discord/flags.py
  4. 26
      discord/guild.py
  5. 1
      discord/http.py
  6. 1
      discord/types/channel.py
  7. 114
      tests/test_channel_flags.py

14
discord/abc.py

@ -62,7 +62,7 @@ from .http import handle_message_parameters
from .voice_client import VoiceClient, VoiceProtocol
from .sticker import GuildSticker, StickerItem
from . import utils
from .flags import InviteFlags
from .flags import ChannelFlags, InviteFlags
import warnings
__all__ = (
@ -423,11 +423,23 @@ class GuildChannel:
category_id: Optional[int]
_state: ConnectionState
_overwrites: List[_Overwrites]
_flags: int
if TYPE_CHECKING:
def __init__(self, *, state: ConnectionState, guild: Guild, data: GuildChannelPayload): ...
@property
def flags(self) -> ChannelFlags:
""":class:`~discord.ChannelFlags`: The flags associated with this channel.
.. versionadded:: 2.1
.. versionchanged:: 2.8
This is now available on all guild channels.
"""
return ChannelFlags._from_value(self._flags)
def __str__(self) -> str:
return self.name

88
discord/channel.py

@ -66,7 +66,7 @@ from .errors import ClientException
from .stage_instance import StageInstance
from .threads import Thread
from .partial_emoji import _EmojiTag, PartialEmoji
from .flags import ChannelFlags, MessageFlags
from .flags import MessageFlags
from .http import handle_message_parameters
from .object import Object
from .soundboard import BaseSoundboardSound, SoundboardDefaultSound
@ -129,6 +129,7 @@ if TYPE_CHECKING:
topic: str
slowmode_delay: int
nsfw: bool
spoiler: bool
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite]
default_auto_archive_duration: int
default_thread_slowmode_delay: int
@ -138,6 +139,7 @@ if TYPE_CHECKING:
user_limit: int
rtc_region: Optional[str]
video_quality_mode: VideoQualityMode
spoiler: bool
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite]
class _CreateStageChannelOptions(_CreateVoiceChannelOptions, total=False):
@ -350,6 +352,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
'last_message_id',
'default_auto_archive_duration',
'default_thread_slowmode_delay',
'_flags',
)
def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[TextChannelPayload, NewsChannelPayload]):
@ -381,6 +384,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
self.slowmode_delay: int = data.get('rate_limit_per_user', 0)
self.default_auto_archive_duration: ThreadArchiveDuration = data.get('default_auto_archive_duration', 1440)
self.default_thread_slowmode_delay: int = data.get('default_thread_rate_limit_per_user', 0)
self._flags: int = data.get('flags', 0)
self._type: Literal[0, 5] = data.get('type', self._type)
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self._fill_overwrites(data)
@ -430,6 +434,13 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
""":class:`bool`: Checks if the channel is NSFW."""
return self.nsfw
def is_spoiler(self) -> bool:
""":class:`bool`: Checks if members must opt in before viewing the channel's contents.
.. versionadded:: 2.8
"""
return self.flags.spoiler
def is_news(self) -> bool:
""":class:`bool`: Checks if the channel is a news channel."""
return self._type == ChannelType.news.value
@ -470,6 +481,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
topic: str = ...,
position: int = ...,
nsfw: bool = ...,
spoiler: bool = ...,
sync_permissions: bool = ...,
category: Optional[CategoryChannel] = ...,
slowmode_delay: int = ...,
@ -509,6 +521,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
The new channel's position.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
spoiler: :class:`bool`
Whether members must opt in before viewing the channel's contents.
sync_permissions: :class:`bool`
Whether to sync permissions with the channel's new or pre-existing
category. Defaults to ``False``.
@ -554,6 +568,15 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
then ``None`` is returned instead.
"""
try:
spoiler = options.pop('spoiler')
except KeyError:
pass
else:
flags = self.flags
flags.spoiler = spoiler
options['flags'] = flags.value
payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
@ -1087,6 +1110,7 @@ class VocalGuildChannel(discord.abc.Messageable, discord.abc.Connectable, discor
'rtc_region',
'video_quality_mode',
'last_message_id',
'_flags',
)
def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[VoiceChannelPayload, StageChannelPayload]):
@ -1115,6 +1139,7 @@ class VocalGuildChannel(discord.abc.Messageable, discord.abc.Connectable, discor
self.slowmode_delay = data.get('rate_limit_per_user', 0)
self.bitrate: int = data['bitrate']
self.user_limit: int = data['user_limit']
self._flags: int = data.get('flags', 0)
self._fill_overwrites(data)
@property
@ -1128,6 +1153,13 @@ class VocalGuildChannel(discord.abc.Messageable, discord.abc.Connectable, discor
"""
return self.nsfw
def is_spoiler(self) -> bool:
""":class:`bool`: Checks if members must opt in before viewing the channel's contents.
.. versionadded:: 2.8
"""
return self.flags.spoiler
@property
def members(self) -> List[Member]:
"""List[:class:`Member`]: Returns all members that are currently inside this voice channel."""
@ -1557,6 +1589,7 @@ class VoiceChannel(VocalGuildChannel):
*,
name: str = ...,
nsfw: bool = ...,
spoiler: bool = ...,
bitrate: int = ...,
user_limit: int = ...,
position: int = ...,
@ -1598,6 +1631,8 @@ class VoiceChannel(VocalGuildChannel):
The new channel's bitrate.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
spoiler: :class:`bool`
Whether members must opt in before viewing the channel's contents.
user_limit: :class:`int`
The new channel's user limit.
position: :class:`int`
@ -1646,6 +1681,15 @@ class VoiceChannel(VocalGuildChannel):
The newly edited voice channel. If the edit was only positional
then ``None`` is returned instead.
"""
try:
spoiler = options.pop('spoiler')
except KeyError:
pass
else:
flags = self.flags
flags.spoiler = spoiler
options['flags'] = flags.value
payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
@ -1919,6 +1963,7 @@ class StageChannel(VocalGuildChannel):
*,
name: str = ...,
nsfw: bool = ...,
spoiler: bool = ...,
bitrate: int = ...,
user_limit: int = ...,
position: int = ...,
@ -1961,6 +2006,8 @@ class StageChannel(VocalGuildChannel):
The new channel's position.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
spoiler: :class:`bool`
Whether members must opt in before viewing the channel's contents.
user_limit: :class:`int`
The new channel's user limit.
sync_permissions: :class:`bool`
@ -2001,6 +2048,15 @@ class StageChannel(VocalGuildChannel):
then ``None`` is returned instead.
"""
try:
spoiler = options.pop('spoiler')
except KeyError:
pass
else:
flags = self.flags
flags.spoiler = spoiler
options['flags'] = flags.value
payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
@ -2049,7 +2105,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead.
"""
__slots__ = ('name', 'id', 'guild', 'nsfw', '_state', 'position', '_overwrites', 'category_id')
__slots__ = ('name', 'id', 'guild', 'nsfw', '_state', 'position', '_overwrites', 'category_id', '_flags')
def __init__(self, *, state: ConnectionState, guild: Guild, data: CategoryChannelPayload):
self._state: ConnectionState = state
@ -2065,6 +2121,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
self.category_id: Optional[int] = utils._get_as_snowflake(data, 'parent_id')
self.nsfw: bool = data.get('nsfw', False)
self.position: int = data['position']
self._flags: int = data.get('flags', 0)
self._fill_overwrites(data)
@property
@ -2566,14 +2623,6 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
"""List[:class:`Thread`]: Returns all the threads that you can see."""
return [thread for thread in self.guild._threads.values() if thread.parent_id == self.id]
@property
def flags(self) -> ChannelFlags:
""":class:`ChannelFlags`: The flags associated with this forum.
.. versionadded:: 2.1
"""
return ChannelFlags._from_value(self._flags)
@property
def available_tags(self) -> Sequence[ForumTag]:
"""Sequence[:class:`ForumTag`]: Returns all the available tags for this forum.
@ -2603,6 +2652,13 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
""":class:`bool`: Checks if the forum is NSFW."""
return self.nsfw
def is_spoiler(self) -> bool:
""":class:`bool`: Checks if members must opt in before viewing the forum's contents.
.. versionadded:: 2.8
"""
return self.flags.spoiler
def is_media(self) -> bool:
""":class:`bool`: Checks if the channel is a media channel.
@ -2655,6 +2711,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
topic: str = ...,
position: int = ...,
nsfw: bool = ...,
spoiler: bool = ...,
sync_permissions: bool = ...,
category: Optional[CategoryChannel] = ...,
slowmode_delay: int = ...,
@ -2686,6 +2743,8 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
The new forum's position.
nsfw: :class:`bool`
To mark the forum as NSFW or not.
spoiler: :class:`bool`
Whether members must opt in before viewing the forum's contents.
sync_permissions: :class:`bool`
Whether to sync permissions with the forum's new or pre-existing
category. Defaults to ``False``.
@ -2779,6 +2838,15 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
flags.require_tag = require_tag
options['flags'] = flags.value
try:
spoiler = options.pop('spoiler')
except KeyError:
pass
else:
flags = self.flags
flags.spoiler = spoiler
options['flags'] = flags.value
try:
layout = options.pop('default_layout')
except KeyError:

8
discord/flags.py

@ -1778,6 +1778,14 @@ class ChannelFlags(BaseFlags):
"""
return 1 << 15
@flag_value
def spoiler(self):
""":class:`bool`: Returns ``True`` if the channel requires members to opt in before viewing its contents.
.. versionadded:: 2.8
"""
return 1 << 21
class ArrayFlags(BaseFlags):
@classmethod

26
discord/guild.py

@ -82,7 +82,7 @@ from .user import User
from .invite import Invite
from .widget import Widget
from .asset import Asset
from .flags import SystemChannelFlags
from .flags import ChannelFlags, SystemChannelFlags
from .integrations import Integration, PartialIntegration, _integration_factory
from .scheduled_event import ScheduledEvent
from .stage_instance import StageInstance
@ -1411,6 +1411,7 @@ class Guild(Hashable):
topic: str = MISSING,
slowmode_delay: int = MISSING,
nsfw: bool = MISSING,
spoiler: bool = MISSING,
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING,
default_auto_archive_duration: int = MISSING,
default_thread_slowmode_delay: int = MISSING,
@ -1478,6 +1479,8 @@ class Guild(Hashable):
The maximum value possible is ``21600``.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
spoiler: :class:`bool`
Whether members must opt in before viewing the channel's contents.
news: :class:`bool`
Whether to create the text channel as a news channel.
@ -1522,6 +1525,9 @@ class Guild(Hashable):
if nsfw is not MISSING:
options['nsfw'] = nsfw
if spoiler is not MISSING:
options['flags'] = ChannelFlags(spoiler=spoiler).value
if default_auto_archive_duration is not MISSING:
options['default_auto_archive_duration'] = default_auto_archive_duration
@ -1555,6 +1561,7 @@ class Guild(Hashable):
video_quality_mode: VideoQualityMode = MISSING,
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING,
nsfw: bool = MISSING,
spoiler: bool = MISSING,
) -> VoiceChannel:
"""|coro|
@ -1596,6 +1603,8 @@ class Guild(Hashable):
To mark the channel as NSFW or not.
.. versionadded:: 2.6
spoiler: :class:`bool`
Whether members must opt in before viewing the channel's contents.
reason: Optional[:class:`str`]
The reason for creating this channel. Shows up on the audit log.
@ -1634,6 +1643,9 @@ class Guild(Hashable):
if nsfw is not MISSING:
options['nsfw'] = nsfw
if spoiler is not MISSING:
options['flags'] = ChannelFlags(spoiler=spoiler).value
data = await self._create_channel(
name, overwrites=overwrites, channel_type=ChannelType.voice, category=category, reason=reason, **options
)
@ -1656,6 +1668,7 @@ class Guild(Hashable):
video_quality_mode: VideoQualityMode = MISSING,
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING,
nsfw: bool = MISSING,
spoiler: bool = MISSING,
) -> StageChannel:
"""|coro|
@ -1703,6 +1716,8 @@ class Guild(Hashable):
To mark the channel as NSFW or not.
.. versionadded:: 2.6
spoiler: :class:`bool`
Whether members must opt in before viewing the channel's contents.
reason: Optional[:class:`str`]
The reason for creating this channel. Shows up on the audit log.
@ -1742,6 +1757,9 @@ class Guild(Hashable):
if nsfw is not MISSING:
options['nsfw'] = nsfw
if spoiler is not MISSING:
options['flags'] = ChannelFlags(spoiler=spoiler).value
data = await self._create_channel(
name, overwrites=overwrites, channel_type=ChannelType.stage_voice, category=category, reason=reason, **options
)
@ -1810,6 +1828,7 @@ class Guild(Hashable):
category: Optional[CategoryChannel] = None,
slowmode_delay: int = MISSING,
nsfw: bool = MISSING,
spoiler: bool = MISSING,
media: bool = MISSING,
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite] = MISSING,
reason: Optional[str] = None,
@ -1850,6 +1869,8 @@ class Guild(Hashable):
at 0. e.g. the top channel is position 0.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
spoiler: :class:`bool`
Whether members must opt in before viewing the channel's contents.
slowmode_delay: :class:`int`
Specifies the slowmode rate limit for users in this channel, in seconds.
The maximum possible value is ``21600``.
@ -1913,6 +1934,9 @@ class Guild(Hashable):
if nsfw is not MISSING:
options['nsfw'] = nsfw
if spoiler is not MISSING:
options['flags'] = ChannelFlags(spoiler=spoiler).value
if default_auto_archive_duration is not MISSING:
options['default_auto_archive_duration'] = default_auto_archive_duration

1
discord/http.py

@ -1280,6 +1280,7 @@ class HTTPClient:
'default_reaction_emoji',
'default_forum_layout',
'available_tags',
'flags',
)
payload.update({k: v for k, v in options.items() if k in valid_keys and v is not None})

1
discord/types/channel.py

@ -56,6 +56,7 @@ class _BaseGuildChannel(_BaseChannel):
permission_overwrites: List[PermissionOverwrite]
nsfw: bool
parent_id: Optional[Snowflake]
flags: NotRequired[int]
class PartialChannel(_BaseChannel):

114
tests/test_channel_flags.py

@ -0,0 +1,114 @@
import pytest
from discord.channel import StageChannel, TextChannel
from discord.flags import ChannelFlags
def test_spoiler_channel_flag():
flags = ChannelFlags()
assert flags.spoiler is False
flags.spoiler = True
assert flags.spoiler is True
assert flags.value == 1 << 21
def test_spoiler_channel_flag_preserves_other_flags():
flags = ChannelFlags._from_value((1 << 4) | (1 << 15))
flags.spoiler = True
assert flags.value == (1 << 4) | (1 << 15) | (1 << 21)
class _HTTP:
async def edit_channel(self, channel_id, *, reason, **options):
self.channel_id = channel_id
self.reason = reason
self.options = options
return {
'id': str(channel_id),
'type': 0,
'name': 'spoilers',
'position': 0,
'permission_overwrites': [],
'flags': options['flags'],
}
class _State:
def __init__(self):
self.http = _HTTP()
class _Guild:
id = 1
@pytest.mark.asyncio
async def test_text_channel_edit_sets_spoiler_flag():
state = _State()
channel = TextChannel(
state=state,
guild=_Guild(),
data={
'id': '1',
'type': 0,
'name': 'spoilers',
'position': 0,
'permission_overwrites': [],
'flags': 1 << 4,
},
)
edited = await channel.edit(spoiler=True)
assert state.http.options == {'flags': (1 << 4) | (1 << 21)}
assert edited is not None
assert edited.flags.spoiler is True
assert edited.is_spoiler() is True
class _StageHTTP(_HTTP):
async def edit_channel(self, channel_id, *, reason, **options):
self.channel_id = channel_id
self.reason = reason
self.options = options
return {
'id': str(channel_id),
'type': 13,
'name': 'spoilers',
'position': 0,
'permission_overwrites': [],
'bitrate': 64000,
'user_limit': 0,
'flags': options['flags'],
}
@pytest.mark.asyncio
async def test_stage_channel_edit_sets_spoiler_flag():
state = _State()
state.http = _StageHTTP()
channel = StageChannel(
state=state,
guild=_Guild(),
data={
'id': '1',
'type': 13,
'name': 'spoilers',
'position': 0,
'permission_overwrites': [],
'bitrate': 64000,
'user_limit': 0,
'flags': 1 << 4,
},
)
edited = await channel.edit(spoiler=True)
assert state.http.options == {'flags': (1 << 4) | (1 << 21)}
assert edited is not None
assert edited.flags.spoiler is True
assert edited.is_spoiler() is True
Loading…
Cancel
Save