Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b1ded01
Add spoiler channel flag and functions
ToothyDev Jun 5, 2026
7b79faa
Add editing spoiler flag to channel editing functions
ToothyDev Jun 5, 2026
9d09881
Merge branch 'master' into feat/spoiler-channels
ToothyDev Jun 22, 2026
65bfc90
Merge branch 'master' into feat/spoiler-channels
Lulalaby Jul 30, 2026
f426f25
fix: changelog
Lulalaby Jul 30, 2026
cb9f534
Merge branch 'master' into feat/spoiler-channels
Lulalaby Jul 30, 2026
7aaad87
Merge branch 'master' into feat/spoiler-channels
ToothyDev Aug 4, 2026
769cc08
Make spoiler attribute a property
ToothyDev Aug 4, 2026
19531ba
Add mutual exclusivity check for nsfw and spoiler options
ToothyDev Aug 4, 2026
16309ed
Merge branch 'master' into feat/spoiler-channels
ToothyDev Aug 4, 2026
68608ab
Adjust changelog to new naming
ToothyDev Aug 10, 2026
763c13f
Merge branch 'master' into feat/spoiler-channels
Lulalaby Aug 10, 2026
f2cb6bb
Add channel creation support with spoiler flag
ToothyDev Aug 10, 2026
9508a82
Add channel creation support with spoiler flag
ToothyDev Aug 10, 2026
4f41083
Minor documentation formatting
ToothyDev Aug 10, 2026
7327338
Fix value defaulting
ToothyDev Aug 10, 2026
57e3dd7
Address code review change requests
ToothyDev Aug 10, 2026
06167d2
Warn when passing both nsfw and spoiler options
ToothyDev Aug 10, 2026
c8f6f61
Add overloads for voice and forum channels
ToothyDev Aug 11, 2026
b56e022
Apply change requests from code review
ToothyDev Aug 11, 2026
2582634
Apply change requests from code review
ToothyDev Aug 11, 2026
ccca218
Fix edit() overloads
ToothyDev Aug 12, 2026
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ These changes are available on the `master` branch, but have not yet been releas

- Added `Member.vr_status` property.
([#3328](https://github.com/Pycord-Development/pycord/pull/3328))
- Added spoiler channels.
([#3252](https://github.com/Pycord-Development/pycord/pull/3252))

### Changed

Expand Down
186 changes: 184 additions & 2 deletions discord/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
from __future__ import annotations

import datetime
from _warnings import warn
from collections.abc import Callable, Iterable, Mapping, Sequence
from typing import (
TYPE_CHECKING,
Any,
Literal,
NamedTuple,
TypeVar,
overload,
Expand Down Expand Up @@ -317,6 +319,17 @@ def is_nsfw(self) -> bool:
"""Checks if the channel is NSFW."""
return self.nsfw

@property
def spoiler(self) -> bool:
"""Checks if the channel is a spoiler channel.
Comment thread
ToothyDev marked this conversation as resolved.

.. note::
This is an alias for :attr:`flags.is_spoiler_channel`.

.. versionadded:: 2.9
"""
return self.flags.is_spoiler_channel

@property
def last_message(self) -> Message | None:
"""Fetches the last message from this channel in cache.
Expand Down Expand Up @@ -809,6 +822,26 @@ async def edit(
default_thread_slowmode_delay: int = ...,
type: ChannelType = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
spoiler: Literal[False] = ...,
) -> TextChannel | None: ...

@overload
async def edit(
self,
*,
reason: str | None = ...,
name: str = ...,
topic: str = ...,
position: int = ...,
nsfw: Literal[False] = ...,
sync_permissions: bool = ...,
category: CategoryChannel | None = ...,
slowmode_delay: int = ...,
default_auto_archive_duration: ThreadArchiveDuration = ...,
default_thread_slowmode_delay: int = ...,
type: ChannelType = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
spoiler: bool = ...,
Comment thread
ToothyDev marked this conversation as resolved.
) -> TextChannel | None: ...

@overload
Expand Down Expand Up @@ -841,6 +874,10 @@ async def edit(self, *, reason=None, **options):
The new channel's position.
nsfw: :class:`bool`
Whether the channel is marked as NSFW.

.. note::
This setting is mutually exclusive with :attr:`spoiler`. Applying this to a spoiler channel will convert it to an NSFW channel.
Passing both as ``True`` will make the channel an NSFW channel.
sync_permissions: :class:`bool`
Whether to sync permissions with the channel's new or pre-existing
category. Defaults to ``False``.
Expand All @@ -865,6 +902,14 @@ async def edit(self, *, reason=None, **options):
The new default slowmode delay in seconds for threads created in this channel.

.. versionadded:: 2.3
spoiler: :class:`bool`
Whether the channel should be a spoiler channel.

.. note::
This setting is mutually exclusive with :attr:`nsfw`. Applying this to an NSFW channel will convert it to a spoiler channel.
Passing both as ``True`` will make the channel an NSFW channel.

.. versionadded:: 2.9

Returns
-------
Expand All @@ -882,6 +927,16 @@ async def edit(self, *, reason=None, **options):
HTTPException
Editing the channel failed.
"""
if "spoiler" in options:
options["flags"] = ChannelFlags._from_value(self.flags.value)
options["flags"].is_spoiler_channel = options["spoiler"]

if options.get("nsfw") and options["spoiler"]:
warn(
"NSFW setting is mutually exclusive with spoiler setting. Channel will become an NSFW channel."
)
options.pop("spoiler")

payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
Expand Down Expand Up @@ -1123,6 +1178,31 @@ async def edit(
available_tags: list[ForumTag] = ...,
require_tag: bool = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
spoiler: Literal[False] = ...,
) -> ForumChannel | None: ...

@overload
async def edit(
self,
*,
reason: str | None = ...,
name: str = ...,
topic: str = ...,
position: int = ...,
nsfw: Literal[False] = ...,
sync_permissions: bool = ...,
category: CategoryChannel | None = ...,
slowmode_delay: int = ...,
default_auto_archive_duration: (
ThreadArchiveDuration | ThreadArchiveDurationEnum
) = ...,
default_thread_slowmode_delay: int = ...,
default_sort_order: SortOrder = ...,
default_reaction_emoji: GuildEmoji | int | str | None = ...,
available_tags: list[ForumTag] = ...,
require_tag: bool = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
spoiler: bool = ...,
) -> ForumChannel | None: ...

@overload
Expand Down Expand Up @@ -1185,6 +1265,10 @@ async def edit(self, *, reason=None, **options):
Whether a tag should be required to be specified when creating a thread in this channel.

.. versionadded:: 2.3
spoiler: :class:`bool`
Whether the channel should be a spoiler channel. Mutually exclusive with :attr:`nsfw`.
Comment thread
Paillat-dev marked this conversation as resolved.

.. versionadded:: 2.9

Returns
-------
Expand All @@ -1205,6 +1289,14 @@ async def edit(self, *, reason=None, **options):
if "require_tag" in options:
options["flags"] = ChannelFlags._from_value(self.flags.value)
options["flags"].require_tag = options.pop("require_tag")
if "spoiler" in options:
if "nsfw" in options:
warn(
"The NSFW setting is mutually exclusive with the spoiler setting. The channel will become an NSFW channel."
)
if "flags" not in options:
options["flags"] = ChannelFlags._from_value(self.flags.value)
Comment thread
ToothyDev marked this conversation as resolved.
options["flags"].is_spoiler_channel = options.pop("spoiler")

payload = await self._edit(options, reason=reason)
if payload is not None:
Expand Down Expand Up @@ -1504,6 +1596,30 @@ async def edit(
require_tag: bool = ...,
hide_media_download_options: bool = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
spoiler: Literal[False] = ...,
) -> ForumChannel | None: ...

@overload
async def edit(
self,
*,
reason: str | None = ...,
name: str = ...,
topic: str = ...,
position: int = ...,
nsfw: Literal[False] = ...,
sync_permissions: bool = ...,
category: CategoryChannel | None = ...,
slowmode_delay: int = ...,
default_auto_archive_duration: ThreadArchiveDuration = ...,
default_thread_slowmode_delay: int = ...,
default_sort_order: SortOrder = ...,
default_reaction_emoji: GuildEmoji | int | str | None = ...,
available_tags: list[ForumTag] = ...,
require_tag: bool = ...,
hide_media_download_options: bool = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
spoiler: bool = ...,
) -> ForumChannel | None: ...

async def edit(self, *, reason=None, **options):
Expand Down Expand Up @@ -1560,6 +1676,11 @@ async def edit(self, *, reason=None, **options):
hide_media_download_options: :class:`bool`
Whether media download options should be hidden in this media channel.

spoiler: :class:`bool`
Whether the channel should be a spoiler channel. Mutually exclusive with :attr:`nsfw`.

.. versionadded:: 2.9

Returns
-------
Optional[:class:`.MediaChannel`]
Expand All @@ -1577,14 +1698,18 @@ async def edit(self, *, reason=None, **options):
Editing the channel failed.
"""

if "require_tag" in options or "hide_media_download_options" in options:
if {"require_tag", "hide_media_download_options", "spoiler"} & options.keys():
flags = ChannelFlags._from_value(self.flags.value)
flags.require_tag = options.pop("require_tag", flags.require_tag)
flags.hide_media_download_options = options.pop(
"hide_media_download_options", flags.hide_media_download_options
)
if options.get("nsfw") and options["spoiler"]:
warn(
"NSFW setting is mutually exclusive with spoiler setting. Channel will become an NSFW channel."
)
flags.is_spoiler_channel = options.pop("spoiler", flags.is_spoiler_channel)
options["flags"] = flags

payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
Expand Down Expand Up @@ -1815,6 +1940,17 @@ def is_nsfw(self) -> bool:
"""Checks if the channel is NSFW."""
return self.nsfw

@property
def spoiler(self) -> bool:
"""Checks if the channel is a spoiler channel.

Comment thread
ToothyDev marked this conversation as resolved.
.. note::
This is an alias for :attr:`flags.is_spoiler_channel`.

.. versionadded:: 2.9
"""
return self.flags.is_spoiler_channel

@property
def last_message(self) -> Message | None:
"""Fetches the last message from this channel in cache.
Expand Down Expand Up @@ -2096,6 +2232,26 @@ async def edit(
slowmode_delay: int = ...,
nsfw: bool = ...,
reason: str | None = ...,
spoiler: Literal[False] = ...,
) -> VoiceChannel | None: ...

@overload
async def edit(
self,
*,
name: str = ...,
bitrate: int = ...,
user_limit: int = ...,
position: int = ...,
sync_permissions: int = ...,
category: CategoryChannel | None = ...,
overwrites: Mapping[Role | Member, PermissionOverwrite] = ...,
rtc_region: VoiceRegion | None = ...,
video_quality_mode: VideoQualityMode = ...,
slowmode_delay: int = ...,
nsfw: Literal[False] = ...,
reason: str | None = ...,
spoiler: bool = ...,
) -> VoiceChannel | None: ...

@overload
Expand Down Expand Up @@ -2154,6 +2310,11 @@ async def edit(self, *, reason=None, **options):

.. versionadded:: 2.7

spoiler: :class:`bool`
Whether the channel should be a spoiler channel. Mutually exclusive with :attr:`nsfw`.

.. versionadded:: 2.9

Returns
-------
Optional[:class:`.VoiceChannel`]
Expand All @@ -2169,6 +2330,15 @@ async def edit(self, *, reason=None, **options):
HTTPException
Editing the channel failed.
"""
if "spoiler" in options:
options["flags"] = ChannelFlags._from_value(self.flags.value)
options["flags"].is_spoiler_channel = options["spoiler"]

if options.get("nsfw") and options["spoiler"]:
warn(
"NSFW setting is mutually exclusive with spoiler setting. Channel will become an NSFW channel."
)
options.pop("spoiler")

payload = await self._edit(options, reason=reason)
if payload is not None:
Expand Down Expand Up @@ -2403,6 +2573,17 @@ def is_nsfw(self) -> bool:
"""Checks if the channel is NSFW."""
return self.nsfw

@property
def spoiler(self) -> bool:
"""Checks if the channel is a spoiler channel.

Comment thread
ToothyDev marked this conversation as resolved.
.. note::
This is an alias for :attr:`flags.is_spoiler_channel`.

.. versionadded:: 2.9
"""
return self.flags.is_spoiler_channel

@property
def last_message(self) -> Message | None:
"""Fetches the last message from this channel in cache.
Expand Down Expand Up @@ -2781,6 +2962,7 @@ async def edit(
rtc_region: VoiceRegion | None = ...,
video_quality_mode: VideoQualityMode = ...,
reason: str | None = ...,
spoiler: bool = ...,
) -> StageChannel | None: ...

@overload
Expand Down
8 changes: 8 additions & 0 deletions discord/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,14 @@ def hide_media_download_options(self):
"""
return 1 << 15

@flag_value
def is_spoiler_channel(self):
""":class:`bool`: Returns ``True`` if the channel is a spoiler channel.

.. versionadded:: 2.9
"""
return 1 << 21


@fill_with_flags()
class AttachmentFlags(BaseFlags):
Expand Down
Loading