From 21eee2217dd464d6c3c4330b46099ba78a2a603a Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 8 Nov 2024 21:02:13 +0100 Subject: [PATCH 01/10] :sparkles: `MediaChannel` --- discord/channel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index d70083d9f7..fd0b7d3cbc 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -3232,7 +3232,9 @@ def _guild_channel_factory(channel_type: int): return TextChannel, value elif value is ChannelType.stage_voice: return StageChannel, value - elif value is ChannelType.forum: + elif value is ChannelType.directory: + return None, value # todo: Add DirectoryChannel when applicable + elif value is ChannelType.forum or value is ChannelType.media: return ForumChannel, value else: return None, value From f68d979403debb9ce726065aa1f8748ab9372119 Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 8 Nov 2024 22:32:01 +0100 Subject: [PATCH 02/10] :sparkles: Add `MediaChannel` and fix `ForumChannel` flags --- discord/abc.py | 14 ++- discord/channel.py | 220 ++++++++++++++++++++++++++++++++++++++++++++- discord/flags.py | 8 ++ discord/threads.py | 6 +- 4 files changed, 233 insertions(+), 15 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 7e9d462b89..a8217a53fb 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -46,7 +46,7 @@ from .enums import ChannelType from .errors import ClientException, InvalidArgument from .file import File -from .flags import MessageFlags +from .flags import ChannelFlags, MessageFlags from .invite import Invite from .iterators import HistoryIterator from .mentions import AllowedMentions @@ -85,7 +85,6 @@ from .client import Client from .embeds import Embed from .enums import InviteTarget - from .flags import ChannelFlags from .guild import Guild from .member import Member from .message import Message, MessageReference, PartialMessage @@ -417,13 +416,10 @@ async def _edit( ) except KeyError: pass - - try: - if options.pop("require_tag"): - options["flags"] = ChannelFlags.require_tag.flag - except KeyError: - pass - + if options.get("flags") and not isinstance( + options["flags"], int + ): # it shouldn't be an int but just in case + options["flags"] = options["flags"].value try: options["available_tags"] = [ tag.to_dict() for tag in options.pop("available_tags") diff --git a/discord/channel.py b/discord/channel.py index fd0b7d3cbc..04b6fbddcb 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -25,6 +25,7 @@ from __future__ import annotations +import contextlib import datetime from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, TypeVar, overload @@ -65,6 +66,7 @@ "GroupChannel", "PartialMessageable", "ForumChannel", + "MediaChannel", "ForumTag", ) @@ -853,7 +855,6 @@ async def edit(self, *, reason=None, **options): HTTPException Editing the channel failed. """ - payload = await self._edit(options, reason=reason) if payload is not None: # the payload will always be the proper channel payload @@ -1056,7 +1057,7 @@ def guidelines(self) -> str | None: @property def requires_tag(self) -> bool: - """Whether a tag is required to be specified when creating a thread in this forum channel. + """Whether a tag is required to be specified when creating a thread in this forum or media channel. Tags are specified in :attr:`applied_tags`. @@ -1169,6 +1170,10 @@ async def edit(self, *, reason=None, **options): HTTPException Editing the channel failed. """ + with contextlib.suppress(KeyError): + require_tag = options.pop("require_tag") + options["flags"] = ChannelFlags._from_value(self.flags.value) + options["flags"].require_tag = require_tag payload = await self._edit(options, reason=reason) if payload is not None: @@ -1351,6 +1356,213 @@ async def create_thread( return ret +class MediaChannel(ForumChannel): + """Represents a Discord media channel. + + .. container:: operations + + .. describe:: x == y + + Checks if two channels are equal. + + .. describe:: x != y + + Checks if two channels are not equal. + + .. describe:: hash(x) + + Returns the channel's hash. + + .. describe:: str(x) + + Returns the channel's name. + + Attributes + ---------- + name: :class:`str` + The channel name. + guild: :class:`Guild` + The guild the channel belongs to. + id: :class:`int` + The channel ID. + category_id: Optional[:class:`int`] + The category channel ID this channel belongs to, if applicable. + topic: Optional[:class:`str`] + The channel's topic. ``None`` if it doesn't exist. + + .. note:: + + :attr:`guidelines` exists as an alternative to this attribute. + position: Optional[:class:`int`] + The position in the channel list. This is a number that starts at 0. e.g. the + top channel is position 0. Can be ``None`` if the channel was received in an interaction. + last_message_id: Optional[:class:`int`] + The last message ID of the message sent to this channel. It may + *not* point to an existing or valid message. + slowmode_delay: :class:`int` + The number of seconds a member must wait between sending messages + in this channel. A value of `0` denotes that it is disabled. + Bots and users with :attr:`~Permissions.manage_channels` or + :attr:`~Permissions.manage_messages` bypass slowmode. + nsfw: :class:`bool` + If the channel is marked as "not safe for work". + + .. note:: + + To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead. + default_auto_archive_duration: :class:`int` + The default auto archive duration in minutes for threads created in this channel. + + .. versionadded:: 2.0 + flags: :class:`ChannelFlags` + Extra features of the channel. + + .. versionadded:: 2.0 + available_tags: List[:class:`ForumTag`] + The set of tags that can be used in a forum channel. + + .. versionadded:: 2.3 + default_sort_order: Optional[:class:`SortOrder`] + The default sort order type used to order posts in this channel. + + .. versionadded:: 2.3 + default_thread_slowmode_delay: Optional[:class:`int`] + The initial slowmode delay to set on newly created threads in this channel. + + .. versionadded:: 2.3 + default_reaction_emoji: Optional[:class:`str` | :class:`discord.GuildEmoji`] + The default forum reaction emoji. + + .. versionadded:: 2.5 + """ + + @property + def hides_media_download_options(self): + """Whether media download options are be hidden in this media channel. + + .. versionadded:: 2.7 + """ + return self.flags.hide_media_download_options + + @overload + async def edit( + self, + *, + reason: str | None = ..., + name: str = ..., + topic: str = ..., + position: int = ..., + nsfw: bool = ..., + 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] = ..., + ) -> ForumChannel | None: ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + Parameters + ---------- + name: :class:`str` + The new channel name. + topic: :class:`str` + The new channel's topic. + position: :class:`int` + The new channel's position. + nsfw: :class:`bool` + To mark the channel as NSFW or not. + sync_permissions: :class:`bool` + Whether to sync permissions with the channel's new or pre-existing + category. Defaults to ``False``. + category: Optional[:class:`CategoryChannel`] + The new category for this channel. Can be ``None`` to remove the + category. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for user in this channel, in seconds. + A value of `0` disables slowmode. The maximum value possible is `21600`. + reason: Optional[:class:`str`] + The reason for editing this channel. Shows up on the audit log. + overwrites: Dict[Union[:class:`Role`, :class:`Member`, :class:`~discord.abc.Snowflake`], :class:`PermissionOverwrite`] + The overwrites to apply to channel permissions. Useful for creating secret channels. + default_auto_archive_duration: :class:`int` + The new default auto archive duration in minutes for threads created in this channel. + Must be one of ``60``, ``1440``, ``4320``, or ``10080``. + default_thread_slowmode_delay: :class:`int` + The new default slowmode delay in seconds for threads created in this channel. + + .. versionadded:: 2.3 + default_sort_order: Optional[:class:`SortOrder`] + The default sort order type to use to order posts in this channel. + + .. versionadded:: 2.3 + default_reaction_emoji: Optional[:class:`discord.GuildEmoji` | :class:`int` | :class:`str`] + The default reaction emoji. + Can be a unicode emoji or a custom emoji in the forms: + :class:`GuildEmoji`, snowflake ID, string representation (eg. ''). + + .. versionadded:: 2.5 + available_tags: List[:class:`ForumTag`] + The set of tags that can be used in this channel. Must be less than `20`. + + .. versionadded:: 2.3 + require_tag: :class:`bool` + Whether a tag should be required to be specified when creating a thread in this channel. + + .. versionadded:: 2.3 + hide_media_download_options: :class:`bool` + Whether media download options should be hidden in this media channel. + + .. versionadded:: 2.7 + + Returns + ------- + Optional[:class:`.MediaChannel`] + The newly edited media channel. If the edit was only positional + then ``None`` is returned instead. + + Raises + ------ + InvalidArgument + If position is less than 0 or greater than the number of channels, or if + the permission overwrite information is not in proper form. + Forbidden + You do not have permissions to edit the channel. + HTTPException + Editing the channel failed. + """ + with contextlib.suppress(KeyError): + require_tag = options.pop("require_tag") + options["flags"] = options.get("flags") or ChannelFlags._from_value( + self.flags.value + ) + options["flags"].require_tag = require_tag + + with contextlib.suppress(KeyError): + hide_media_download_options = options.pop("hide_media_download_options") + options["flags"] = options.get("flags") or ChannelFlags._from_value( + self.flags.value + ) + options["flags"].hide_media_download_options = hide_media_download_options + + payload = await self._edit(options, reason=reason) + if payload is not None: + # the payload will always be the proper channel payload + return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore + + class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): __slots__ = ( "name", @@ -3234,8 +3446,10 @@ def _guild_channel_factory(channel_type: int): return StageChannel, value elif value is ChannelType.directory: return None, value # todo: Add DirectoryChannel when applicable - elif value is ChannelType.forum or value is ChannelType.media: + elif value is ChannelType.forum: return ForumChannel, value + elif value is ChannelType.media: + return MediaChannel, value else: return None, value diff --git a/discord/flags.py b/discord/flags.py index 1a6af50c72..7073a56e35 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -1561,6 +1561,14 @@ def require_tag(self): """ return 1 << 4 + @flag_value + def hide_media_download_options(self): + """:class:`bool`: Returns ``True`` if the embedded media download options are hidden for the media channel posts. + + .. versionadded:: 2.7 + """ + return 1 << 15 + @fill_with_flags() class AttachmentFlags(BaseFlags): diff --git a/discord/threads.py b/discord/threads.py index 28eafc9522..0e1675a1bd 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -300,7 +300,7 @@ def members(self) -> list[ThreadMember]: def applied_tags(self) -> list[ForumTag]: """List[:class:`ForumTag`]: A list of tags applied to this thread. - This is only available for threads in forum channels. + This is only available for threads in forum or media channels. """ from .channel import ForumChannel # to prevent circular import @@ -394,7 +394,7 @@ def starting_message(self) -> Message | None: return self._state._get_message(self.id) def is_pinned(self) -> bool: - """Whether the thread is pinned to the top of its parent forum channel. + """Whether the thread is pinned to the top of its parent forum or media channel. .. versionadded:: 2.3 """ @@ -638,7 +638,7 @@ async def edit( reason: Optional[:class:`str`] The reason for editing this thread. Shows up on the audit log. pinned: :class:`bool` - Whether to pin the thread or not. This only works if the thread is part of a forum. + Whether to pin the thread or not. This only works if the thread is part of a forum or media channel. applied_tags: List[:class:`ForumTag`] The set of tags to apply to the thread. Each tag object should have an ID set. From a5be2ccca1b86916a26d8895a9df2ec556e99e67 Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 8 Nov 2024 22:32:01 +0100 Subject: [PATCH 03/10] :memo: Add `MediaChannel` to docs --- docs/api/models.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api/models.rst b/docs/api/models.rst index 0238e2fc77..39800ae05c 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -428,6 +428,12 @@ Channels :members: :inherited-members: +.. attributetable:: MediaChannel + +.. autoclass:: MediaChannel() + :members: + :inherited-members: + .. attributetable:: VoiceChannel .. autoclass:: VoiceChannel() From 9e21786cdc8e663424f563aec7666201ef2bbc24 Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 8 Nov 2024 22:36:14 +0100 Subject: [PATCH 04/10] :memo: Improve docstring --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 04b6fbddcb..57c5cbb777 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1357,7 +1357,7 @@ async def create_thread( class MediaChannel(ForumChannel): - """Represents a Discord media channel. + """Represents a Discord media channel. Subclass of :class:`ForumChannel`. .. container:: operations From c3dfbe259da86e27bf8a0035d17bf25b5a23cdd8 Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 8 Nov 2024 22:40:21 +0100 Subject: [PATCH 05/10] :memo: CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9547fbafc..dc72431c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ These changes are available on the `master` branch, but have not yet been releas `Permissions.use_external_sounds` and `Permissions.view_creator_monetization_analytics`. ([#2620](https://github.com/Pycord-Development/pycord/pull/2620)) +- Added `MediaChannel` channel type. + ([#2641](https://github.com/Pycord-Development/pycord/pull/2641)) ### Fixed @@ -60,6 +62,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2627](https://github.com/Pycord-Development/pycord/issues/2627)) - Fixed `AttributeError` when sending polls with `PartialWebook`. ([#2624](https://github.com/Pycord-Development/pycord/pull/2624)) +- Fixed editing `ForumChannel` flags not working. + ([#2641](https://github.com/Pycord-Development/pycord/pull/2641)) - Fixed `AttributeError` when accessing `Member.guild_permissions` for user installed apps. ([#2650](https://github.com/Pycord-Development/pycord/pull/2650)) From 38b5f3ec2a8586f62b35a4773755adba087b0d2d Mon Sep 17 00:00:00 2001 From: Paillat Date: Fri, 15 Nov 2024 23:39:44 +0100 Subject: [PATCH 06/10] :memo: Requested changes --- discord/abc.py | 4 +--- discord/channel.py | 23 ++++------------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index a8217a53fb..842038a4e3 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -416,9 +416,7 @@ async def _edit( ) except KeyError: pass - if options.get("flags") and not isinstance( - options["flags"], int - ): # it shouldn't be an int but just in case + if options.get("flags"): options["flags"] = options["flags"].value try: options["available_tags"] = [ diff --git a/discord/channel.py b/discord/channel.py index 57c5cbb777..fb2a818961 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1377,6 +1377,8 @@ class MediaChannel(ForumChannel): Returns the channel's name. + .. versionadded:: 2.7 + Attributes ---------- name: :class:`str` @@ -1413,35 +1415,25 @@ class MediaChannel(ForumChannel): default_auto_archive_duration: :class:`int` The default auto archive duration in minutes for threads created in this channel. - .. versionadded:: 2.0 flags: :class:`ChannelFlags` Extra features of the channel. - .. versionadded:: 2.0 available_tags: List[:class:`ForumTag`] The set of tags that can be used in a forum channel. - .. versionadded:: 2.3 default_sort_order: Optional[:class:`SortOrder`] The default sort order type used to order posts in this channel. - .. versionadded:: 2.3 default_thread_slowmode_delay: Optional[:class:`int`] The initial slowmode delay to set on newly created threads in this channel. - .. versionadded:: 2.3 default_reaction_emoji: Optional[:class:`str` | :class:`discord.GuildEmoji`] The default forum reaction emoji. - - .. versionadded:: 2.5 """ @property - def hides_media_download_options(self): - """Whether media download options are be hidden in this media channel. - - .. versionadded:: 2.7 - """ + def hides_media_download_options(self) -> bool: + """Whether media download options are be hidden in this media channel.""" return self.flags.hide_media_download_options @overload @@ -1503,30 +1495,23 @@ async def edit(self, *, reason=None, **options): default_thread_slowmode_delay: :class:`int` The new default slowmode delay in seconds for threads created in this channel. - .. versionadded:: 2.3 default_sort_order: Optional[:class:`SortOrder`] The default sort order type to use to order posts in this channel. - .. versionadded:: 2.3 default_reaction_emoji: Optional[:class:`discord.GuildEmoji` | :class:`int` | :class:`str`] The default reaction emoji. Can be a unicode emoji or a custom emoji in the forms: :class:`GuildEmoji`, snowflake ID, string representation (eg. ''). - .. versionadded:: 2.5 available_tags: List[:class:`ForumTag`] The set of tags that can be used in this channel. Must be less than `20`. - .. versionadded:: 2.3 require_tag: :class:`bool` Whether a tag should be required to be specified when creating a thread in this channel. - .. versionadded:: 2.3 hide_media_download_options: :class:`bool` Whether media download options should be hidden in this media channel. - .. versionadded:: 2.7 - Returns ------- Optional[:class:`.MediaChannel`] From 99596cc13a4450e58793cebab617c0387a961180 Mon Sep 17 00:00:00 2001 From: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:51:43 +0300 Subject: [PATCH 07/10] refactor: switch to try-except Signed-off-by: Dorukyum <53639936+Dorukyum@users.noreply.github.com> --- discord/abc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 842038a4e3..2403391961 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -416,8 +416,12 @@ async def _edit( ) except KeyError: pass - if options.get("flags"): - options["flags"] = options["flags"].value + + try: + options["flags"] = options.pop("flags").value + except KeyError: + pass + try: options["available_tags"] = [ tag.to_dict() for tag in options.pop("available_tags") From 0b3f756cf1279150c6270f6ff12169d0c8baf612 Mon Sep 17 00:00:00 2001 From: Paillat Date: Wed, 20 Nov 2024 22:12:46 +0100 Subject: [PATCH 08/10] :adhesive_bandage: Allow `MediaChannel` as slash option. --- discord/commands/options.py | 2 ++ discord/enums.py | 1 + 2 files changed, 3 insertions(+) diff --git a/discord/commands/options.py b/discord/commands/options.py index 4b35a080d9..0c6b1fb6f3 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -34,6 +34,7 @@ CategoryChannel, DMChannel, ForumChannel, + MediaChannel, StageChannel, TextChannel, Thread, @@ -85,6 +86,7 @@ CategoryChannel: ChannelType.category, Thread: ChannelType.public_thread, ForumChannel: ChannelType.forum, + MediaChannel: ChannelType.media, DMChannel: ChannelType.private, } diff --git a/discord/enums.py b/discord/enums.py index 91425b8cf8..7488ad43a8 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -825,6 +825,7 @@ def from_datatype(cls, datatype): "ThreadOption", "Thread", "ForumChannel", + "MediaChannel", "DMChannel", ]: return cls.channel From 077696459001948ccaba11c0cc0a10ea3b97775c Mon Sep 17 00:00:00 2001 From: Paillat Date: Wed, 20 Nov 2024 22:22:10 +0100 Subject: [PATCH 09/10] :goal_net: Use `contextlib.suppress` instead of `try:` `except: pass` --- discord/abc.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 2403391961..a66ebd0d92 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -26,6 +26,7 @@ from __future__ import annotations import asyncio +import contextlib import copy import time from typing import ( @@ -417,10 +418,8 @@ async def _edit( except KeyError: pass - try: + with contextlib.suppress(KeyError): options["flags"] = options.pop("flags").value - except KeyError: - pass try: options["available_tags"] = [ From 19e4bed6253ebd1e21374ce45dc0af15b7f28d08 Mon Sep 17 00:00:00 2001 From: Paillat Date: Wed, 18 Dec 2024 15:24:47 +0100 Subject: [PATCH 10/10] :pencil2: Fix writing --- discord/channel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index fb2a818961..e9cf7ca715 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1433,7 +1433,7 @@ class MediaChannel(ForumChannel): @property def hides_media_download_options(self) -> bool: - """Whether media download options are be hidden in this media channel.""" + """Whether media download options are hidden in this media channel.""" return self.flags.hide_media_download_options @overload @@ -1501,7 +1501,7 @@ async def edit(self, *, reason=None, **options): default_reaction_emoji: Optional[:class:`discord.GuildEmoji` | :class:`int` | :class:`str`] The default reaction emoji. Can be a unicode emoji or a custom emoji in the forms: - :class:`GuildEmoji`, snowflake ID, string representation (eg. ''). + :class:`GuildEmoji`, snowflake ID, string representation (e.g., ''). available_tags: List[:class:`ForumTag`] The set of tags that can be used in this channel. Must be less than `20`.