diff --git a/CHANGELOG.md b/CHANGELOG.md index 861f66ec0f..228ebbafe4 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)) - Fixed type annotations of cached properties. diff --git a/discord/abc.py b/discord/abc.py index 7e9d462b89..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 ( @@ -46,7 +47,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 +86,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 @@ -418,11 +418,8 @@ async def _edit( except KeyError: pass - try: - if options.pop("require_tag"): - options["flags"] = ChannelFlags.require_tag.flag - except KeyError: - pass + with contextlib.suppress(KeyError): + options["flags"] = options.pop("flags").value try: options["available_tags"] = [ diff --git a/discord/channel.py b/discord/channel.py index d70083d9f7..e9cf7ca715 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,198 @@ async def create_thread( return ret +class MediaChannel(ForumChannel): + """Represents a Discord media channel. Subclass of :class:`ForumChannel`. + + .. 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. + + .. versionadded:: 2.7 + + 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. + + flags: :class:`ChannelFlags` + Extra features of the channel. + + available_tags: List[:class:`ForumTag`] + The set of tags that can be used in a forum channel. + + default_sort_order: Optional[:class:`SortOrder`] + The default sort order type used to order posts in this channel. + + default_thread_slowmode_delay: Optional[:class:`int`] + The initial slowmode delay to set on newly created threads in this channel. + + default_reaction_emoji: Optional[:class:`str` | :class:`discord.GuildEmoji`] + The default forum reaction emoji. + """ + + @property + def hides_media_download_options(self) -> bool: + """Whether media download options are hidden in this media channel.""" + 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. + + default_sort_order: Optional[:class:`SortOrder`] + The default sort order type to use to order posts in this channel. + + 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 (e.g., ''). + + available_tags: List[:class:`ForumTag`] + The set of tags that can be used in this channel. Must be less than `20`. + + require_tag: :class:`bool` + Whether a tag should be required to be specified when creating a thread in this channel. + + hide_media_download_options: :class:`bool` + Whether media download options should be hidden in this media channel. + + 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", @@ -3232,8 +3429,12 @@ def _guild_channel_factory(channel_type: int): return TextChannel, value elif value is ChannelType.stage_voice: return StageChannel, value + elif value is ChannelType.directory: + return None, value # todo: Add DirectoryChannel when applicable elif value is ChannelType.forum: return ForumChannel, value + elif value is ChannelType.media: + return MediaChannel, value else: return None, value 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 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. 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()