From 78f58ea23b291a2bec1d0958c6ff3cf742946e35 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Thu, 15 Sep 2022 16:42:56 +0300 Subject: [PATCH 01/34] Add ForumTag type --- discord/types/channel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/discord/types/channel.py b/discord/types/channel.py index 2b7c2d3a8d..b0258c4f94 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -71,6 +71,14 @@ class TextChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[0] +class ForumTag(TypedDict): + id: Snowflake + name: str + moderated: bool + emoji_id: Snowflake + emoji_name: str + + class ForumChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[15] From 6d5dc2d79c7041f7da13b902e4a7894920600e9f Mon Sep 17 00:00:00 2001 From: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Date: Fri, 16 Sep 2022 08:41:08 +0300 Subject: [PATCH 02/34] Make emoji_name nullable --- discord/types/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/types/channel.py b/discord/types/channel.py index b0258c4f94..a16b9199aa 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -76,7 +76,7 @@ class ForumTag(TypedDict): name: str moderated: bool emoji_id: Snowflake - emoji_name: str + emoji_name: Optional[str] class ForumChannel(_BaseGuildChannel, _TextChannelOptional): From 83e638205b2183444cae0cec5fd4f89dfe43006e Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 28 Sep 2022 19:06:05 +0300 Subject: [PATCH 03/34] Add forum tag fields --- discord/channel.py | 13 +++++++++++++ discord/http.py | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/discord/channel.py b/discord/channel.py index b1fac739cb..1364258025 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -114,6 +114,7 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "_type", "last_message_id", "default_auto_archive_duration", + "available_tags", "flags", ) @@ -146,6 +147,11 @@ def _update(self, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayl # Does this need coercion into `int`? No idea yet. 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.available_tags: Optional[List[int]] = ( + [int(tag_id) for tag_id in tag_ids] + if (tag_ids := data.get("available_tags")) is not None + else None + ) self.last_message_id: Optional[int] = utils._get_as_snowflake(data, "last_message_id") self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) self._fill_overwrites(data) @@ -219,6 +225,7 @@ async def edit( category: Optional[CategoryChannel] = ..., slowmode_delay: int = ..., default_auto_archive_duration: ThreadArchiveDuration = ..., + available_tags: List[int] = ..., type: ChannelType = ..., overwrites: Mapping[Union[Role, Member, Snowflake], PermissionOverwrite] = ..., ) -> Optional[TextChannel]: @@ -275,6 +282,10 @@ async def edit(self, *, reason=None, **options): 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``. + available_tags: List[:class:`int`] + The set of tags that can be used in a forum channel. + + .. versionadded:: 2.2 Raises ------ @@ -888,6 +899,7 @@ async def create_thread( nonce=None, allowed_mentions=None, view=None, + applied_tags=None, auto_archive_duration: ThreadArchiveDuration = MISSING, slowmode_delay: int = MISSING, reason: Optional[str] = None, @@ -1043,6 +1055,7 @@ async def create_thread( components=components, auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, rate_limit_per_user=slowmode_delay or self.slowmode_delay, + applied_tags=applied_tags, reason=reason, ) ret = Thread(guild=self.guild, state=self._state, data=data) diff --git a/discord/http.py b/discord/http.py index f488088482..a7c69c359a 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1102,6 +1102,7 @@ def start_forum_thread( auto_archive_duration: threads.ThreadArchiveDuration, rate_limit_per_user: int, invitable: bool = True, + applied_tags: Optional[SnowflakeList], reason: Optional[str] = None, embed: Optional[embed.Embed] = None, embeds: Optional[List[embed.Embed]] = None, @@ -1118,6 +1119,9 @@ def start_forum_thread( if content: payload["content"] = content + if applied_tags: + payload["applied_tags"] = applied_tags + if embed: payload["embeds"] = [embed] From 97427b16776c0896f1c838568707098afe4e2853 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 17 Oct 2022 19:48:39 +0300 Subject: [PATCH 04/34] Add missing attributes & create ForumTag --- discord/channel.py | 98 +++++++++++++++++++++++++++++++++++++++- discord/flags.py | 9 ++++ discord/http.py | 5 ++ discord/partial_emoji.py | 20 ++++++-- discord/types/channel.py | 12 ++++- 5 files changed, 137 insertions(+), 7 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index df0abd867c..d97cd9bcb2 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -55,6 +55,7 @@ VoiceRegion, try_enum, ) +from .partial_emoji import PartialEmoji, _EmojiTag from .errors import ClientException, InvalidArgument from .file import File from .flags import ChannelFlags @@ -76,6 +77,7 @@ "GroupChannel", "PartialMessageable", "ForumChannel", + "ForumTag" ) if TYPE_CHECKING: @@ -83,7 +85,7 @@ from .guild import Guild from .guild import GuildChannel as GuildChannelType from .member import Member, VoiceState - from .message import Message, PartialMessage + from .message import Message, PartialMessage, EmojiInputType from .role import Role from .state import ConnectionState from .types.channel import CategoryChannel as CategoryChannelPayload @@ -93,6 +95,7 @@ from .types.channel import StageChannel as StageChannelPayload from .types.channel import TextChannel as TextChannelPayload from .types.channel import VoiceChannel as VoiceChannelPayload + from .types.channel import ForumTag as ForumTagPayload from .types.snowflake import SnowflakeList from .types.threads import ThreadArchiveDuration from .user import BaseUser, ClientUser, User @@ -114,6 +117,7 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "_type", "last_message_id", "default_auto_archive_duration", + "default_thread_slowmode_delay", "available_tags", "flags", ) @@ -147,6 +151,7 @@ def _update(self, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayl # Does this need coercion into `int`? No idea yet. 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.available_tags: Optional[List[int]] = ( [int(tag_id) for tag_id in tag_ids] if (tag_ids := data.get("available_tags")) is not None @@ -225,6 +230,7 @@ async def edit( category: Optional[CategoryChannel] = ..., slowmode_delay: int = ..., default_auto_archive_duration: ThreadArchiveDuration = ..., + default_thread_slowmode_delay: int = ..., available_tags: List[int] = ..., type: ChannelType = ..., overwrites: Mapping[Union[Role, Member, Snowflake], PermissionOverwrite] = ..., @@ -282,6 +288,9 @@ async def edit(self, *, reason=None, **options): 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 available_tags: List[:class:`int`] The set of tags that can be used in a forum channel. @@ -810,6 +819,88 @@ async def create_thread( return Thread(guild=self.guild, state=self._state, data=data) +class ForumTag(Hashable): + """Represents a forum tag that can be added to a thread inside a :class:`ForumChannel` +. + .. versionadded:: 2.3 + + .. container:: operations + + .. describe:: x == y + + Checks if two forum tags are equal. + + .. describe:: x != y + + Checks if two forum tags are not equal. + + .. describe:: hash(x) + + Returns the forum tag's hash. + + .. describe:: str(x) + + Returns the forum tag's name. + + Attributes + ----------- + id: :class:`int` + The tag ID. + Note that if the object was created manually then this will be ``0``. + name: :class:`str` + The name of the tag. Can only be up to 20 characters. + moderated: :class:`bool` + Whether this tag can only be added or removed by a moderator with + the :attr:`~Permissions.manage_threads` permission. + emoji: :class:`PartialEmoji` + The emoji that is used to represent this tag. + Note that if the emoji is a custom emoji, it will *not* have name information. + + """ + __slots__ = ("name", "id", "moderated", "emoji") + + def __init__(self, *, name: str, emoji: EmojiInputType, moderated: bool = False) -> None: + self.name: str = name + self.id: int = 0 + self.moderated: bool = moderated + self.emoji: PartialEmoji + if isinstance(emoji, _EmojiTag): + self.emoji = emoji._to_partial() + elif isinstance(emoji, str): + self.emoji = PartialEmoji.from_str(emoji) + else: + raise TypeError(f"emoji must be a Emoji, PartialEmoji, or str and not {emoji.__class__!r}") + + def __repr__(self) -> str: + return f'' + + def __str__(self) -> str: + return self.name + + @classmethod + def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> "ForumTag": + self = cls.__new__(cls) + self.name = data['name'] + self.id = int(data['id']) + self.moderated = data.get('moderated', False) + + emoji_name = data['emoji_name'] or '' + emoji_id = utils._get_as_snowflake(data, 'emoji_id') or None + self.emoji = PartialEmoji.with_state(state=state, name=emoji_name, id=emoji_id) + return self + + def to_dict(self) -> Dict[str, Any]: + payload: Dict[str, Any] = { + 'name': self.name, + 'moderated': self.moderated, + } | self.emoji._to_forum_tag_payload() + + if self.id: + payload['id'] = self.id + + return payload + + class ForumChannel(_TextChannel): """Represents a Discord forum channel. @@ -943,6 +1034,8 @@ async def create_thread( are used instead. view: :class:`discord.ui.View` A Discord UI View to add to the message. + applied_tags: List[:class:`discord.ForumTag`] + A list of tags to apply to the new thread. auto_archive_duration: :class:`int` The duration in minutes before a thread is automatically archived for inactivity. If not provided, the channel's default auto archive duration is used. @@ -999,6 +1092,9 @@ async def create_thread( else: components = None + if applied_tags is not None: + applied_tags = [str(tag.id) for tag in applied_tags] + if file is not None and files is not None: raise InvalidArgument("cannot pass both file and files parameter to send()") diff --git a/discord/flags.py b/discord/flags.py index 967339c007..6bb0bf08b8 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -1422,3 +1422,12 @@ class ChannelFlags(BaseFlags): def pinned(self): """:class:`bool`: Returns ``True`` if the thread is pinned to the top of its parent forum channel.""" return 1 << 1 + + @flag_value + def require_tag(self): + """:class:`bool`: Returns ``True`` if a tag is required to be specified when creating a thread in a + :class:`ForumChannel`. + + .. versionadded:: 2.2 + """ + return 1 << 4 diff --git a/discord/http.py b/discord/http.py index a7c69c359a..0e1e47e4c4 100644 --- a/discord/http.py +++ b/discord/http.py @@ -990,6 +990,11 @@ def edit_channel( "locked", "invitable", "default_auto_archive_duration", + "flags", + "default_thread_rate_limit_per_user", + "default_reaction_emoji", + "available_tags", + "applied_tags", ) payload = {k: v for k, v in options.items() if k in valid_keys} return self.request(r, reason=reason, json=payload) diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 18f6b06673..275dc23213 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -26,7 +26,7 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Any, Dict, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Type, TypeVar, Union, TypedDict from . import utils from .asset import Asset, AssetMixin @@ -158,6 +158,15 @@ def to_dict(self) -> Dict[str, Any]: def _to_partial(self) -> PartialEmoji: return self + def _to_forum_tag_payload(self) -> Union[ + TypedDict("TagPayload", {"emoji_id": int, "emoji_name": None}), + TypedDict("TagPayload", {"emoji_id": None, "emoji_name": str}) + ]: + if self.id is None: + return {"emoji_id": None, "emoji_name": self.name} + else: + return {'emoji_id': self.id, 'emoji_name': None} + @classmethod def with_state( cls: Type[PE], @@ -172,11 +181,12 @@ def with_state( return self def __str__(self) -> str: + # Emoji won't render if the name is empty + name = self.name or "_" if self.id is None: - return self.name - if self.animated: - return f"" - return f"<:{self.name}:{self.id}>" + return name + animated_tag = "a" if self.animated else "" + return f"<{animated_tag}:{name}:{self.id}>" def __repr__(self): return f"<{self.__class__.__name__} animated={self.animated} name={self.name!r} id={self.id}>" diff --git a/discord/types/channel.py b/discord/types/channel.py index a16b9199aa..69be667a3c 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -71,16 +71,24 @@ class TextChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[0] +class DefaultReaction(TypedDict): + emoji_id: Optional[Snowflake] + emoji_name: Optional[str] + + class ForumTag(TypedDict): id: Snowflake name: str moderated: bool - emoji_id: Snowflake + emoji_id: Optional[Snowflake] emoji_name: Optional[str] class ForumChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[15] + available_tags: List[ForumTag] + default_reaction_emoji: Optional[DefaultReaction] + flags: int class NewsChannel(_BaseGuildChannel, _TextChannelOptional): @@ -122,6 +130,8 @@ class _ThreadChannelOptional(TypedDict, total=False): rate_limit_per_user: int last_message_id: Optional[Snowflake] last_pin_timestamp: str + flags: int + applied_tags: List[Snowflake] class ThreadChannel(_BaseChannel, _ThreadChannelOptional): From 344badced52bc8e3513bb807cb9ddee7b0894608 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:19:09 +0000 Subject: [PATCH 05/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- discord/channel.py | 86 +++++++++++++++++++++------------------- discord/http.py | 2 +- discord/partial_emoji.py | 14 ++++--- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index fd69a8e467..fbd9ea5c67 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -41,7 +41,6 @@ VoiceRegion, try_enum, ) -from .partial_emoji import PartialEmoji, _EmojiTag from .errors import ClientException, InvalidArgument from .file import File from .flags import ChannelFlags @@ -49,6 +48,7 @@ from .iterators import ArchivedThreadIterator from .mixins import Hashable from .object import Object +from .partial_emoji import PartialEmoji, _EmojiTag from .permissions import PermissionOverwrite, Permissions from .stage_instance import StageInstance from .threads import Thread @@ -63,7 +63,7 @@ "GroupChannel", "PartialMessageable", "ForumChannel", - "ForumTag" + "ForumTag", ) if TYPE_CHECKING: @@ -71,17 +71,17 @@ from .guild import Guild from .guild import GuildChannel as GuildChannelType from .member import Member, VoiceState - from .message import Message, PartialMessage, EmojiInputType + from .message import EmojiInputType, Message, PartialMessage from .role import Role from .state import ConnectionState from .types.channel import CategoryChannel as CategoryChannelPayload from .types.channel import DMChannel as DMChannelPayload from .types.channel import ForumChannel as ForumChannelPayload + from .types.channel import ForumTag as ForumTagPayload from .types.channel import GroupDMChannel as GroupChannelPayload from .types.channel import StageChannel as StageChannelPayload from .types.channel import TextChannel as TextChannelPayload from .types.channel import VoiceChannel as VoiceChannelPayload - from .types.channel import ForumTag as ForumTagPayload from .types.snowflake import SnowflakeList from .types.threads import ThreadArchiveDuration from .user import BaseUser, ClientUser, User @@ -842,45 +842,47 @@ async def create_thread( class ForumTag(Hashable): """Represents a forum tag that can be added to a thread inside a :class:`ForumChannel` -. - .. versionadded:: 2.3 + . + .. versionadded:: 2.3 - .. container:: operations + .. container:: operations - .. describe:: x == y + .. describe:: x == y - Checks if two forum tags are equal. + Checks if two forum tags are equal. - .. describe:: x != y + .. describe:: x != y - Checks if two forum tags are not equal. + Checks if two forum tags are not equal. - .. describe:: hash(x) + .. describe:: hash(x) - Returns the forum tag's hash. + Returns the forum tag's hash. - .. describe:: str(x) + .. describe:: str(x) - Returns the forum tag's name. - - Attributes - ----------- - id: :class:`int` - The tag ID. - Note that if the object was created manually then this will be ``0``. - name: :class:`str` - The name of the tag. Can only be up to 20 characters. - moderated: :class:`bool` - Whether this tag can only be added or removed by a moderator with - the :attr:`~Permissions.manage_threads` permission. - emoji: :class:`PartialEmoji` - The emoji that is used to represent this tag. - Note that if the emoji is a custom emoji, it will *not* have name information. + Returns the forum tag's name. + Attributes + ---------- + id: :class:`int` + The tag ID. + Note that if the object was created manually then this will be ``0``. + name: :class:`str` + The name of the tag. Can only be up to 20 characters. + moderated: :class:`bool` + Whether this tag can only be added or removed by a moderator with + the :attr:`~Permissions.manage_threads` permission. + emoji: :class:`PartialEmoji` + The emoji that is used to represent this tag. + Note that if the emoji is a custom emoji, it will *not* have name information. """ + __slots__ = ("name", "id", "moderated", "emoji") - def __init__(self, *, name: str, emoji: EmojiInputType, moderated: bool = False) -> None: + def __init__( + self, *, name: str, emoji: EmojiInputType, moderated: bool = False + ) -> None: self.name: str = name self.id: int = 0 self.moderated: bool = moderated @@ -890,34 +892,36 @@ def __init__(self, *, name: str, emoji: EmojiInputType, moderated: bool = False) elif isinstance(emoji, str): self.emoji = PartialEmoji.from_str(emoji) else: - raise TypeError(f"emoji must be a Emoji, PartialEmoji, or str and not {emoji.__class__!r}") + raise TypeError( + f"emoji must be a Emoji, PartialEmoji, or str and not {emoji.__class__!r}" + ) def __repr__(self) -> str: - return f'' + return f"" def __str__(self) -> str: return self.name @classmethod - def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> "ForumTag": + def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> ForumTag: self = cls.__new__(cls) - self.name = data['name'] - self.id = int(data['id']) - self.moderated = data.get('moderated', False) + self.name = data["name"] + self.id = int(data["id"]) + self.moderated = data.get("moderated", False) - emoji_name = data['emoji_name'] or '' - emoji_id = utils._get_as_snowflake(data, 'emoji_id') or None + emoji_name = data["emoji_name"] or "" + emoji_id = utils._get_as_snowflake(data, "emoji_id") or None self.emoji = PartialEmoji.with_state(state=state, name=emoji_name, id=emoji_id) return self def to_dict(self) -> Dict[str, Any]: payload: Dict[str, Any] = { - 'name': self.name, - 'moderated': self.moderated, + "name": self.name, + "moderated": self.moderated, } | self.emoji._to_forum_tag_payload() if self.id: - payload['id'] = self.id + payload["id"] = self.id return payload diff --git a/discord/http.py b/discord/http.py index f1bfbfaff3..1ca5fed68d 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1154,7 +1154,7 @@ def start_forum_thread( auto_archive_duration: threads.ThreadArchiveDuration, rate_limit_per_user: int, invitable: bool = True, - applied_tags = SnowflakeList | None, + applied_tags=SnowflakeList | None, reason: str | None = None, embed: embed.Embed | None = None, embeds: list[embed.Embed] | None = None, diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index f290ce51e3..34d480b735 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -26,7 +26,7 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Any, Dict, Optional, Type, TypeVar, Union, TypedDict +from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, Union from . import utils from .asset import Asset, AssetMixin @@ -160,14 +160,16 @@ def to_dict(self) -> dict[str, Any]: def _to_partial(self) -> PartialEmoji: return self - def _to_forum_tag_payload(self) -> Union[ - TypedDict("TagPayload", {"emoji_id": int, "emoji_name": None}), - TypedDict("TagPayload", {"emoji_id": None, "emoji_name": str}) - ]: + def _to_forum_tag_payload( + self, + ) -> ( + TypedDict("TagPayload", {"emoji_id": int, "emoji_name": None}) + | TypedDict("TagPayload", {"emoji_id": None, "emoji_name": str}) + ): if self.id is None: return {"emoji_id": None, "emoji_name": self.name} else: - return {'emoji_id': self.id, 'emoji_name': None} + return {"emoji_id": self.id, "emoji_name": None} @classmethod def with_state( From e82d4dd812a235bd99caae40cf5892f384a90eef Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:22:24 -0500 Subject: [PATCH 06/34] Fix typehint syntax --- discord/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/http.py b/discord/http.py index 1ca5fed68d..94e24d1f08 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1154,7 +1154,7 @@ def start_forum_thread( auto_archive_duration: threads.ThreadArchiveDuration, rate_limit_per_user: int, invitable: bool = True, - applied_tags=SnowflakeList | None, + applied_tags: SnowflakeList | None, reason: str | None = None, embed: embed.Embed | None = None, embeds: list[embed.Embed] | None = None, From ed599df46f34f1b0ff2dc0874d360ecaefba170c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:23:49 +0000 Subject: [PATCH 07/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- discord/partial_emoji.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 34d480b735..0ed1dfd178 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -26,7 +26,7 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypedDict, TypeVar from . import utils from .asset import Asset, AssetMixin From 4d9af9c7402d2dac23c4d9f2af9f8e7fa25568e0 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:26:47 -0500 Subject: [PATCH 08/34] Fix typehints --- discord/channel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index fbd9ea5c67..9e5fc11694 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -147,7 +147,7 @@ def _update( self.default_auto_archive_duration: ThreadArchiveDuration = data.get( "default_auto_archive_duration", 1440 ) - self.available_tags: List[int] | None = ( + self.available_tags: list[int] | None = ( [int(tag_id) for tag_id in tag_ids] if (tag_ids := data.get("available_tags")) is not None else None @@ -236,7 +236,7 @@ async def edit( slowmode_delay: int = ..., default_auto_archive_duration: ThreadArchiveDuration = ..., default_thread_slowmode_delay: int = ..., - available_tags: List[int] = ..., + available_tags: list[int] = ..., type: ChannelType = ..., overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., ) -> TextChannel | None: @@ -914,8 +914,8 @@ def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> ForumTag self.emoji = PartialEmoji.with_state(state=state, name=emoji_name, id=emoji_id) return self - def to_dict(self) -> Dict[str, Any]: - payload: Dict[str, Any] = { + def to_dict(self) -> dict[str, Any]: + payload: dict[str, Any] = { "name": self.name, "moderated": self.moderated, } | self.emoji._to_forum_tag_payload() From d198ca94e3168c213944639f7d2fe47a730d48e3 Mon Sep 17 00:00:00 2001 From: Dorukyum <53639936+Dorukyum@users.noreply.github.com> Date: Wed, 19 Oct 2022 16:47:18 +0300 Subject: [PATCH 09/34] Update discord/http.py Co-authored-by: BobDotCom <71356958+BobDotCom@users.noreply.github.com> --- discord/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/http.py b/discord/http.py index 94e24d1f08..c626d23df1 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1154,7 +1154,7 @@ def start_forum_thread( auto_archive_duration: threads.ThreadArchiveDuration, rate_limit_per_user: int, invitable: bool = True, - applied_tags: SnowflakeList | None, + applied_tags: SnowflakeList | None = None, reason: str | None = None, embed: embed.Embed | None = None, embeds: list[embed.Embed] | None = None, From 0891c794fac5748f62d766e31c660642b8db417e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 17:12:55 +0000 Subject: [PATCH 10/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- discord/types/channel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/types/channel.py b/discord/types/channel.py index 11324a9d44..5f6b76e194 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -119,6 +119,7 @@ class StageChannel(_BaseGuildChannel): bitrate: int user_limit: int + class ThreadChannel(_BaseChannel): member: NotRequired[ThreadMember] owner_id: NotRequired[Snowflake] From 11afeb72f3a3e208be540b39bdd91b70ac7acc79 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Tue, 25 Oct 2022 19:14:55 +0200 Subject: [PATCH 11/34] Update channel.py --- discord/types/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/types/channel.py b/discord/types/channel.py index 5f6b76e194..19fba2dd9c 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -133,7 +133,7 @@ class ThreadChannel(_BaseChannel): message_count: int member_count: int thread_metadata: ThreadMetadata - applied_tags: NotRequired(List[Snowflake]) + applied_tags: NotRequired(list[Snowflake] | None) GuildChannel = Union[ From f716176a922ca937fdf5a07da6099e6fee683b3d Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Tue, 25 Oct 2022 19:19:06 +0200 Subject: [PATCH 12/34] Update channel.py --- discord/types/channel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/types/channel.py b/discord/types/channel.py index 19fba2dd9c..ffe16c0fe3 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -74,22 +74,22 @@ class TextChannel(_BaseGuildChannel, _TextChannelOptional): class DefaultReaction(TypedDict): - emoji_id: Optional[Snowflake] - emoji_name: Optional[str] + emoji_id: NotRequired(Snowflake | None) + emoji_name: NotRequired(str | None) class ForumTag(TypedDict): id: Snowflake name: str moderated: bool - emoji_id: Optional[Snowflake] - emoji_name: Optional[str] + emoji_id: NotRequired[Snowflake | None] + emoji_name: NotRequired[str | None] class ForumChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[15] - available_tags: List[ForumTag] - default_reaction_emoji: Optional[DefaultReaction] + available_tags: NotRequired(list[ForumTag] | None) + default_reaction_emoji: NotRequired(DefaultReaction | None) flags: int From 8f9ee26737b3b90829e1ca1f7a1211ca3bc1b289 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 7 Nov 2022 19:06:40 +0300 Subject: [PATCH 13/34] Update forum tags - Move available_tags to ForumChannel, turn type to `list[ForumTag]` - Fix versionadded --- discord/channel.py | 197 +++++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 96 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index 229cd0e2b1..56c203e913 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -88,6 +88,92 @@ from .webhook import Webhook +class ForumTag(Hashable): + """Represents a forum tag that can be added to a thread inside a :class:`ForumChannel` + . + .. versionadded:: 2.3 + + .. container:: operations + + .. describe:: x == y + + Checks if two forum tags are equal. + + .. describe:: x != y + + Checks if two forum tags are not equal. + + .. describe:: hash(x) + + Returns the forum tag's hash. + + .. describe:: str(x) + + Returns the forum tag's name. + + Attributes + ---------- + id: :class:`int` + The tag ID. + Note that if the object was created manually then this will be ``0``. + name: :class:`str` + The name of the tag. Can only be up to 20 characters. + moderated: :class:`bool` + Whether this tag can only be added or removed by a moderator with + the :attr:`~Permissions.manage_threads` permission. + emoji: :class:`PartialEmoji` + The emoji that is used to represent this tag. + Note that if the emoji is a custom emoji, it will *not* have name information. + """ + + __slots__ = ("name", "id", "moderated", "emoji") + + def __init__( + self, *, name: str, emoji: EmojiInputType, moderated: bool = False + ) -> None: + self.name: str = name + self.id: int = 0 + self.moderated: bool = moderated + self.emoji: PartialEmoji + if isinstance(emoji, _EmojiTag): + self.emoji = emoji._to_partial() + elif isinstance(emoji, str): + self.emoji = PartialEmoji.from_str(emoji) + else: + raise TypeError( + f"emoji must be a Emoji, PartialEmoji, or str and not {emoji.__class__!r}" + ) + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self.name + + @classmethod + def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> ForumTag: + self = cls.__new__(cls) + self.name = data["name"] + self.id = int(data["id"]) + self.moderated = data.get("moderated", False) + + emoji_name = data["emoji_name"] or "" + emoji_id = utils._get_as_snowflake(data, "emoji_id") or None + self.emoji = PartialEmoji.with_state(state=state, name=emoji_name, id=emoji_id) + return self + + def to_dict(self) -> dict[str, Any]: + payload: dict[str, Any] = { + "name": self.name, + "moderated": self.moderated, + } | self.emoji._to_forum_tag_payload() + + if self.id: + payload["id"] = self.id + + return payload + + class _TextChannel(discord.abc.GuildChannel, Hashable): __slots__ = ( "name", @@ -104,7 +190,7 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "last_message_id", "default_auto_archive_duration", "default_thread_slowmode_delay", - "available_tags", + "available_tags", # only available in forum channels "flags", ) @@ -147,11 +233,6 @@ def _update( self.default_auto_archive_duration: ThreadArchiveDuration = data.get( "default_auto_archive_duration", 1440 ) - self.available_tags: list[int] | None = ( - [int(tag_id) for tag_id in tag_ids] - if (tag_ids := data.get("available_tags")) is not None - else None - ) self.last_message_id: int | None = utils._get_as_snowflake( data, "last_message_id" ) @@ -236,7 +317,7 @@ async def edit( slowmode_delay: int = ..., default_auto_archive_duration: ThreadArchiveDuration = ..., default_thread_slowmode_delay: int = ..., - available_tags: list[int] = ..., + available_tags: list[ForumTag] = ..., type: ChannelType = ..., overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., ) -> TextChannel | None: @@ -295,11 +376,12 @@ async def edit(self, *, reason=None, **options): 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 - available_tags: List[:class:`int`] - The set of tags that can be used in a forum channel. + available_tags: List[:class:`ForumTag`] + The set of tags that can be used in a forum channel. Must be less than `20`. - .. versionadded:: 2.2 + .. versionadded:: 2.3 Returns ------- @@ -840,92 +922,6 @@ async def create_thread( return Thread(guild=self.guild, state=self._state, data=data) -class ForumTag(Hashable): - """Represents a forum tag that can be added to a thread inside a :class:`ForumChannel` - . - .. versionadded:: 2.3 - - .. container:: operations - - .. describe:: x == y - - Checks if two forum tags are equal. - - .. describe:: x != y - - Checks if two forum tags are not equal. - - .. describe:: hash(x) - - Returns the forum tag's hash. - - .. describe:: str(x) - - Returns the forum tag's name. - - Attributes - ---------- - id: :class:`int` - The tag ID. - Note that if the object was created manually then this will be ``0``. - name: :class:`str` - The name of the tag. Can only be up to 20 characters. - moderated: :class:`bool` - Whether this tag can only be added or removed by a moderator with - the :attr:`~Permissions.manage_threads` permission. - emoji: :class:`PartialEmoji` - The emoji that is used to represent this tag. - Note that if the emoji is a custom emoji, it will *not* have name information. - """ - - __slots__ = ("name", "id", "moderated", "emoji") - - def __init__( - self, *, name: str, emoji: EmojiInputType, moderated: bool = False - ) -> None: - self.name: str = name - self.id: int = 0 - self.moderated: bool = moderated - self.emoji: PartialEmoji - if isinstance(emoji, _EmojiTag): - self.emoji = emoji._to_partial() - elif isinstance(emoji, str): - self.emoji = PartialEmoji.from_str(emoji) - else: - raise TypeError( - f"emoji must be a Emoji, PartialEmoji, or str and not {emoji.__class__!r}" - ) - - def __repr__(self) -> str: - return f"" - - def __str__(self) -> str: - return self.name - - @classmethod - def from_data(cls, *, state: ConnectionState, data: ForumTagPayload) -> ForumTag: - self = cls.__new__(cls) - self.name = data["name"] - self.id = int(data["id"]) - self.moderated = data.get("moderated", False) - - emoji_name = data["emoji_name"] or "" - emoji_id = utils._get_as_snowflake(data, "emoji_id") or None - self.emoji = PartialEmoji.with_state(state=state, name=emoji_name, id=emoji_id) - return self - - def to_dict(self) -> dict[str, Any]: - payload: dict[str, Any] = { - "name": self.name, - "moderated": self.moderated, - } | self.emoji._to_forum_tag_payload() - - if self.id: - payload["id"] = self.id - - return payload - - class ForumChannel(_TextChannel): """Represents a Discord forum channel. @@ -988,12 +984,21 @@ class ForumChannel(_TextChannel): 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 """ def __init__( self, *, state: ConnectionState, guild: Guild, data: ForumChannelPayload ): super().__init__(state=state, guild=guild, data=data) + self.available_tags: list[ForumTag] = [ + ForumTag.from_data(state=state, data=tag) + for tag in data.get("available_tags", []) + ] def _update(self, guild: Guild, data: ForumChannelPayload) -> None: super()._update(guild, data) From b2483ad14264896272b5f680c87cd3b60313abf3 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 7 Nov 2022 19:06:56 +0300 Subject: [PATCH 14/34] Implement ForumChannel.get_tag --- discord/channel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/discord/channel.py b/discord/channel.py index 56c203e913..7e7b8fa9ad 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1008,6 +1008,14 @@ def guidelines(self) -> str | None: """Optional[:class:`str`]: The channel's guidelines. An alias of :attr:`topic`.""" return self.topic + def get_tag(self, id: int, /) -> ForumTag | None: + """Returns the :class:`ForumTag` from this forum channel with the + given ID, if any. + + .. versionadded:: 2.3 + """ + return utils.get(self.available_tags, id=id) + async def create_thread( self, name: str, From d1da9bea6b7c4a9653633ca45bac3f854aed59d5 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Mon, 7 Nov 2022 16:27:03 +0000 Subject: [PATCH 15/34] Add sort order, channel flags and total msg --- discord/enums.py | 10 ++++++++++ discord/threads.py | 11 +++++++++++ discord/types/channel.py | 8 +++++++- discord/types/threads.py | 3 +++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/discord/enums.py b/discord/enums.py index b5917417a5..4b8b3d19af 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -309,6 +309,16 @@ def __str__(self): return self.name +class SortOrder(Enum): + """Forum Channel Sort Order""" + + latest_activity = 1 + creation_date = 2 + + def __str__(self): + return self.name + + class ContentFilter(Enum, comparable=True): """Content Filter""" diff --git a/discord/threads.py b/discord/threads.py index b8605e3d2c..16514202b1 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -126,6 +126,10 @@ class Thread(Messageable, Hashable): Only available for threads created after 2022-01-09. flags: :class:`ChannelFlags` Extra features of the thread. + total_message_sent: :class:`int` + Number of messages ever sent in a thread. + It's similar to message_count on message creation, + but will not decrement the number when a message is deleted. .. versionadded:: 2.0 """ @@ -151,6 +155,7 @@ class Thread(Messageable, Hashable): "archive_timestamp", "created_at", "flags", + "total_message_sent", ) def __init__(self, *, guild: Guild, state: ConnectionState, data: ThreadPayload): @@ -189,6 +194,7 @@ def _from_data(self, data: ThreadPayload): self.message_count = data.get("message_count", None) self.member_count = data.get("member_count", None) self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) + self.total_message_sent = data.get("total_message_sent", None) # Here, we try to fill in potentially missing data if thread := self.guild.get_thread(self.id) and data.pop("_invoke_flag", False): @@ -203,6 +209,11 @@ def _from_data(self, data: ThreadPayload): if self.message_count is None else self.message_count ) + self.total_message_sent = ( + thread.total_message_sent + if self.total_message_sent is None + else self.total_message_sent + ) self.member_count = ( thread.member_count if self.member_count is None else self.member_count ) diff --git a/discord/types/channel.py b/discord/types/channel.py index ffe16c0fe3..c5eb9fe289 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -27,6 +27,8 @@ from typing import Literal, Union from .._typed_dict import NotRequired, TypedDict +from ..enums import SortOrder +from ..flags import ChannelFlags from .snowflake import Snowflake from .threads import ThreadArchiveDuration, ThreadMember, ThreadMetadata from .user import PartialUser @@ -67,6 +69,7 @@ class _TextChannelOptional(TypedDict, total=False): last_pin_timestamp: str rate_limit_per_user: int default_auto_archive_duration: ThreadArchiveDuration + default_thread_rate_limit_per_user: int class TextChannel(_BaseGuildChannel, _TextChannelOptional): @@ -90,7 +93,8 @@ class ForumChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[15] available_tags: NotRequired(list[ForumTag] | None) default_reaction_emoji: NotRequired(DefaultReaction | None) - flags: int + default_sort_order: NotRequired(SortOrder | None) + flags: ChannelFlags class NewsChannel(_BaseGuildChannel, _TextChannelOptional): @@ -134,6 +138,8 @@ class ThreadChannel(_BaseChannel): member_count: int thread_metadata: ThreadMetadata applied_tags: NotRequired(list[Snowflake] | None) + flags: ChannelFlags + total_message_sent: int GuildChannel = Union[ diff --git a/discord/types/threads.py b/discord/types/threads.py index 87abe9af4b..aab6d95794 100644 --- a/discord/types/threads.py +++ b/discord/types/threads.py @@ -28,6 +28,7 @@ from typing import Literal from .._typed_dict import NotRequired, TypedDict +from ..flags import ChannelFlags from .snowflake import Snowflake ThreadType = Literal[10, 11, 12] @@ -64,6 +65,8 @@ class Thread(TypedDict): message_count: int rate_limit_per_user: int thread_metadata: ThreadMetadata + flags: ChannelFlags + total_message_sent: int class ThreadPaginationPayload(TypedDict): From 0b1a082972ed4e9d9656ea960806db90e38dc88f Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 7 Nov 2022 20:04:55 +0300 Subject: [PATCH 16/34] Fix typehints --- discord/types/channel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/types/channel.py b/discord/types/channel.py index c5eb9fe289..bc825a3f38 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -77,8 +77,8 @@ class TextChannel(_BaseGuildChannel, _TextChannelOptional): class DefaultReaction(TypedDict): - emoji_id: NotRequired(Snowflake | None) - emoji_name: NotRequired(str | None) + emoji_id: NotRequired[Snowflake | None] + emoji_name: NotRequired[str | None] class ForumTag(TypedDict): @@ -91,9 +91,9 @@ class ForumTag(TypedDict): class ForumChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[15] - available_tags: NotRequired(list[ForumTag] | None) - default_reaction_emoji: NotRequired(DefaultReaction | None) - default_sort_order: NotRequired(SortOrder | None) + available_tags: NotRequired[list[ForumTag] | None] + default_reaction_emoji: NotRequired[DefaultReaction | None] + default_sort_order: NotRequired[SortOrder | None] flags: ChannelFlags @@ -137,7 +137,7 @@ class ThreadChannel(_BaseChannel): message_count: int member_count: int thread_metadata: ThreadMetadata - applied_tags: NotRequired(list[Snowflake] | None) + applied_tags: NotRequired[list[Snowflake] | None] flags: ChannelFlags total_message_sent: int From 6ec5b7e9c2301a2d926e42b7c60620c52591d1db Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 7 Nov 2022 20:18:13 +0300 Subject: [PATCH 17/34] Update Thread.applied_tags --- discord/threads.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/discord/threads.py b/discord/threads.py index 16514202b1..1e8a363bf0 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -41,7 +41,7 @@ if TYPE_CHECKING: from .abc import Snowflake, SnowflakeTime - from .channel import CategoryChannel, ForumChannel, TextChannel + from .channel import CategoryChannel, ForumChannel, ForumTag, TextChannel from .guild import Guild from .member import Member from .message import Message, PartialMessage @@ -141,6 +141,7 @@ class Thread(Messageable, Hashable): "_type", "_state", "_members", + "_applied_tags", "owner_id", "parent_id", "last_message_id", @@ -195,6 +196,7 @@ def _from_data(self, data: ThreadPayload): self.member_count = data.get("member_count", None) self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) self.total_message_sent = data.get("total_message_sent", None) + self._applied_tags: list[int] = data.get("applied_tags", []) # Here, we try to fill in potentially missing data if thread := self.guild.get_thread(self.id) and data.pop("_invoke_flag", False): @@ -287,6 +289,19 @@ def members(self) -> list[ThreadMember]: """ return list(self._members.values()) + @property + 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. + """ + if isinstance(self.parent, ForumChannel): + return [ + tag for tag_id in self._applied_tags + if (tag := self.parent.get_tag(tag_id)) is not None + ] + return [] + @property def last_message(self) -> Message | None: """Returns the last message from this thread in cache. From fb76350d4d6acb3066a18963ebd5a16abfd9ca8a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 17:19:10 +0000 Subject: [PATCH 18/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- discord/threads.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/discord/threads.py b/discord/threads.py index 1e8a363bf0..8393a39edf 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -297,7 +297,8 @@ def applied_tags(self) -> list[ForumTag]: """ if isinstance(self.parent, ForumChannel): return [ - tag for tag_id in self._applied_tags + tag + for tag_id in self._applied_tags if (tag := self.parent.get_tag(tag_id)) is not None ] return [] From 00084e21ad00d7d5808ad4ac9c267c320c7c9f27 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 9 Nov 2022 18:40:02 +0300 Subject: [PATCH 19/34] Implement ForumChannel.requires_tag --- discord/channel.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/discord/channel.py b/discord/channel.py index 308ce7670c..251bda7841 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1008,6 +1008,16 @@ def guidelines(self) -> str | None: """The channel's guidelines. An alias of :attr:`topic`.""" return self.topic + @property + def requires_tag(self) -> bool: + """Whether a tag is required to be specified when creating a thread in this forum channel. + + Tags are specified in :attr:`applied_tags`. + + .. versionadded:: 2.3 + """ + return self.flags.require_tag + def get_tag(self, id: int, /) -> ForumTag | None: """Returns the :class:`ForumTag` from this forum channel with the given ID, if any. From e0acd9038fadd6477a3444fad9cf98962224b3ae Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 9 Nov 2022 18:40:14 +0300 Subject: [PATCH 20/34] Implement Thread.is_pinned --- discord/threads.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/discord/threads.py b/discord/threads.py index 8801546a89..042dd9f2fc 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -384,6 +384,13 @@ 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. + + .. versionadded:: 2.3 + """ + return self.flags.pinned + def is_private(self) -> bool: """Whether the thread is a private thread. From 582e95cea44229a087263a1e5e9528b36db639dd Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Wed, 9 Nov 2022 18:42:34 +0300 Subject: [PATCH 21/34] Update `versionadded`s in docstring --- discord/threads.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/threads.py b/discord/threads.py index 042dd9f2fc..2547077f80 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -126,12 +126,14 @@ class Thread(Messageable, Hashable): Only available for threads created after 2022-01-09. flags: :class:`ChannelFlags` Extra features of the thread. + + .. versionadded:: 2.0 total_message_sent: :class:`int` Number of messages ever sent in a thread. It's similar to message_count on message creation, but will not decrement the number when a message is deleted. - .. versionadded:: 2.0 + .. versionadded:: 2.3 """ __slots__ = ( From 1001c3704112f4c2d1ecdb55546078d5c5ed7b72 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 14 Nov 2022 11:27:06 +0300 Subject: [PATCH 22/34] Update SortOrder to match API values --- discord/enums.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index ac6f41a8cf..322a408489 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -312,8 +312,8 @@ def __str__(self): class SortOrder(Enum): """Forum Channel Sort Order""" - latest_activity = 1 - creation_date = 2 + latest_activity = 0 + creation_date = 1 def __str__(self): return self.name From c4acddcddaacc4dd07deac11933b3e658cc25e8f Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 14 Nov 2022 11:27:37 +0300 Subject: [PATCH 23/34] Implement default_sort_order --- discord/channel.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index 251bda7841..05182cf60f 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -36,6 +36,7 @@ ChannelType, EmbeddedActivity, InviteTarget, + SortOrder, StagePrivacyLevel, VideoQualityMode, VoiceRegion, @@ -190,7 +191,8 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "last_message_id", "default_auto_archive_duration", "default_thread_slowmode_delay", - "available_tags", # only available in forum channels + "default_sort_order", + "available_tags", "flags", ) @@ -984,10 +986,13 @@ class ForumChannel(_TextChannel): 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 """ @@ -995,13 +1000,14 @@ def __init__( self, *, state: ConnectionState, guild: Guild, data: ForumChannelPayload ): super().__init__(state=state, guild=guild, data=data) - self.available_tags: list[ForumTag] = [ - ForumTag.from_data(state=state, data=tag) - for tag in data.get("available_tags", []) - ] def _update(self, guild: Guild, data: ForumChannelPayload) -> None: super()._update(guild, data) + self.available_tags: list[ForumTag] = [ + ForumTag.from_data(state=self._state, data=tag) + for tag in (data.get("available_tags") or []) + ] + self.default_sort_order: SortOrder | None = data.get("default_sort_order", None) @property def guidelines(self) -> str | None: From acb25b678b4b4df2c50f7bf008f36ff07748fc7c Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 14 Nov 2022 11:35:56 +0300 Subject: [PATCH 24/34] Implement default_thread_rate_limit_per_user --- discord/channel.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/discord/channel.py b/discord/channel.py index 05182cf60f..a4ad84083f 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -191,6 +191,7 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "last_message_id", "default_auto_archive_duration", "default_thread_slowmode_delay", + "default_thread_rate_limit_per_user", "default_sort_order", "available_tags", "flags", @@ -235,6 +236,9 @@ def _update( self.default_auto_archive_duration: ThreadArchiveDuration = data.get( "default_auto_archive_duration", 1440 ) + self.default_thread_rate_limit_per_user: int | None = data.get( + "default_thread_rate_limit_per_user" + ) self.last_message_id: int | None = utils._get_as_snowflake( data, "last_message_id" ) @@ -824,6 +828,10 @@ class TextChannel(discord.abc.Messageable, _TextChannel): Extra features of the channel. .. versionadded:: 2.0 + default_thread_rate_limit_per_user: Optional[:class:`int`] + The initial `rate_limit_per_user` to set on newly created threads in this channel. + + .. versionadded:: 2.3 """ def __init__( @@ -993,6 +1001,10 @@ class ForumChannel(_TextChannel): default_sort_order: Optional[:class:`SortOrder`] The default sort order type used to order posts in this channel. + .. versionadded:: 2.3 + default_thread_rate_limit_per_user: Optional[:class:`int`] + The initial `rate_limit_per_user` to set on newly created threads in this channel. + .. versionadded:: 2.3 """ From ee64f58b38f539ae209317ddd796fd741af7b1c9 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Mon, 14 Nov 2022 11:43:09 +0300 Subject: [PATCH 25/34] Add new fields to edit routes --- discord/channel.py | 12 +++++++++++- discord/http.py | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index a4ad84083f..931167b21c 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -323,6 +323,8 @@ async def edit( slowmode_delay: int = ..., default_auto_archive_duration: ThreadArchiveDuration = ..., default_thread_slowmode_delay: int = ..., + default_sort_order: SortOrder = ..., + default_thread_rate_limit_per_user: int = ..., available_tags: list[ForumTag] = ..., type: ChannelType = ..., overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., @@ -383,9 +385,17 @@ 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 forum channel. + + .. versionadded:: 2.3 + default_thread_rate_limit_per_user: Optional[:class:`int`] + The initial `rate_limit_per_user` to set on newly created threads in this forum channel. + .. versionadded:: 2.3 available_tags: List[:class:`ForumTag`] - The set of tags that can be used in a forum channel. Must be less than `20`. + The set of tags that can be used in this forum channel. Must be less than `20`. .. versionadded:: 2.3 diff --git a/discord/http.py b/discord/http.py index 45afb699cf..79455a0879 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1048,6 +1048,7 @@ def edit_channel( "default_reaction_emoji", "available_tags", "applied_tags", + "default_sort_order", ) payload = {k: v for k, v in options.items() if k in valid_keys} return self.request(r, reason=reason, json=payload) From 1afd1b2e7559b88b389199e7aaa42b1294626a6f Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Tue, 15 Nov 2022 12:29:48 +0300 Subject: [PATCH 26/34] Rename default_thread_rate_limit_per_user This will be called default_thread_slowmode_delay in Pycord. --- discord/channel.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index 931167b21c..08ba979f77 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -191,7 +191,6 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "last_message_id", "default_auto_archive_duration", "default_thread_slowmode_delay", - "default_thread_rate_limit_per_user", "default_sort_order", "available_tags", "flags", @@ -236,7 +235,7 @@ def _update( self.default_auto_archive_duration: ThreadArchiveDuration = data.get( "default_auto_archive_duration", 1440 ) - self.default_thread_rate_limit_per_user: int | None = data.get( + self.default_thread_slowmode_delay: int | None = data.get( "default_thread_rate_limit_per_user" ) self.last_message_id: int | None = utils._get_as_snowflake( @@ -324,7 +323,6 @@ async def edit( default_auto_archive_duration: ThreadArchiveDuration = ..., default_thread_slowmode_delay: int = ..., default_sort_order: SortOrder = ..., - default_thread_rate_limit_per_user: int = ..., available_tags: list[ForumTag] = ..., type: ChannelType = ..., overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., @@ -389,10 +387,6 @@ async def edit(self, *, reason=None, **options): default_sort_order: Optional[:class:`SortOrder`] The default sort order type to use to order posts in this forum channel. - .. versionadded:: 2.3 - default_thread_rate_limit_per_user: Optional[:class:`int`] - The initial `rate_limit_per_user` to set on newly created threads in this forum channel. - .. versionadded:: 2.3 available_tags: List[:class:`ForumTag`] The set of tags that can be used in this forum channel. Must be less than `20`. @@ -838,8 +832,8 @@ class TextChannel(discord.abc.Messageable, _TextChannel): Extra features of the channel. .. versionadded:: 2.0 - default_thread_rate_limit_per_user: Optional[:class:`int`] - The initial `rate_limit_per_user` to set on newly created threads in this channel. + default_thread_slowmode_delay: Optional[:class:`int`] + The initial slowmode delay to set on newly created threads in this channel. .. versionadded:: 2.3 """ @@ -1012,8 +1006,8 @@ class ForumChannel(_TextChannel): The default sort order type used to order posts in this channel. .. versionadded:: 2.3 - default_thread_rate_limit_per_user: Optional[:class:`int`] - The initial `rate_limit_per_user` to set on newly created threads in this channel. + default_thread_slowmode_delay: Optional[:class:`int`] + The initial slowmode delay to set on newly created threads in this channel. .. versionadded:: 2.3 """ From 6b71d555dfdf724593f169a3634b7dd825eb4c6c Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Tue, 15 Nov 2022 12:41:41 +0300 Subject: [PATCH 27/34] Seperate edit methods for text and forum channels --- discord/channel.py | 299 +++++++++++++++++++++++++++++---------------- 1 file changed, 193 insertions(+), 106 deletions(-) diff --git a/discord/channel.py b/discord/channel.py index 08ba979f77..3677b325d7 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -308,112 +308,9 @@ def last_message(self) -> Message | None: else None ) - @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 = ..., - available_tags: list[ForumTag] = ..., - type: ChannelType = ..., - overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., - ) -> TextChannel | None: - ... - - @overload - async def edit(self) -> TextChannel | None: - ... - - async def edit(self, *, reason=None, **options): - """|coro| - - Edits the channel. - - You must have the :attr:`~Permissions.manage_channels` permission to - use this. - - .. versionchanged:: 1.3 - The ``overwrites`` keyword-only parameter was added. - - .. versionchanged:: 1.4 - The ``type`` keyword-only parameter was added. - - .. versionchanged:: 2.0 - Edits are no longer in-place, the newly edited channel is returned instead. - - 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`. - type: :class:`ChannelType` - Change the type of this text channel. Currently, only conversion between - :attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This - is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`. - 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 forum channel. - - .. versionadded:: 2.3 - available_tags: List[:class:`ForumTag`] - The set of tags that can be used in this forum channel. Must be less than `20`. - - .. versionadded:: 2.3 - - Returns - ------- - Optional[:class:`.TextChannel`] - The newly edited text 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. - """ - - 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 + async def edit(self, **options) -> _TextChannel: + """Edits the channel.""" + raise NotImplementedError @utils.copy_doc(discord.abc.GuildChannel.clone) async def clone( @@ -862,6 +759,103 @@ def news(self) -> bool: """Equivalent to :meth:`is_news`.""" return self.is_news() + @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 = ..., + type: ChannelType = ..., + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., + ) -> TextChannel | None: + ... + + @overload + async def edit(self) -> TextChannel | None: + ... + + async def edit(self, *, reason=None, **options): + """|coro| + + Edits the channel. + + You must have the :attr:`~Permissions.manage_channels` permission to + use this. + + .. versionchanged:: 1.3 + The ``overwrites`` keyword-only parameter was added. + + .. versionchanged:: 1.4 + The ``type`` keyword-only parameter was added. + + .. versionchanged:: 2.0 + Edits are no longer in-place, the newly edited channel is returned instead. + + 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`. + type: :class:`ChannelType` + Change the type of this text channel. Currently, only conversion between + :attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This + is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`. + 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 + + Returns + ------- + Optional[:class:`.TextChannel`] + The newly edited text 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. + """ + + 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 + async def create_thread( self, *, @@ -1048,6 +1042,99 @@ def get_tag(self, id: int, /) -> ForumTag | None: """ return utils.get(self.available_tags, id=id) + @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 = ..., + available_tags: list[ForumTag] = ..., + overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., + ) -> "ForumChannel" | None: + ... + + @overload + async def edit(self) -> "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 + available_tags: List[:class:`ForumTag`] + The set of tags that can be used in this channel. Must be less than `20`. + + .. versionadded:: 2.3 + + Returns + ------- + Optional[:class:`.ForumChannel`] + The newly edited forum 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. + """ + + 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 + async def create_thread( self, name: str, From 29db389ee4398d8647e6843d0106cd793a1e1d27 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Tue, 15 Nov 2022 12:46:00 +0300 Subject: [PATCH 28/34] Map default_thread_slowmode_delay to valid field --- discord/abc.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/abc.py b/discord/abc.py index 8554383178..4d60b6fcec 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -400,6 +400,11 @@ async def _edit( except KeyError: pass + try: + options["default_thread_rate_limit_per_user"] = options.pop("default_thread_slowmode_delay") + except KeyError: + pass + try: rtc_region = options.pop("rtc_region") except KeyError: From dc2f63b68892ed95b71e93822eb4baaff767e6d5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 09:47:11 +0000 Subject: [PATCH 29/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- discord/abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/abc.py b/discord/abc.py index 4d60b6fcec..a312f57b13 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -401,7 +401,9 @@ async def _edit( pass try: - options["default_thread_rate_limit_per_user"] = options.pop("default_thread_slowmode_delay") + options["default_thread_rate_limit_per_user"] = options.pop( + "default_thread_slowmode_delay" + ) except KeyError: pass From 13ea4d8356df6ff85e1352fec71a2c4283e5fcb5 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Tue, 15 Nov 2022 17:10:44 +0300 Subject: [PATCH 30/34] Add require_tag field to ForumChannel.edit --- discord/abc.py | 6 ++++++ discord/channel.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/discord/abc.py b/discord/abc.py index 4d60b6fcec..ed71209639 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -405,6 +405,12 @@ async def _edit( except KeyError: pass + try: + if options.pop("require_tag"): + options["flags"] = ChannelFlags.require_tag.flag + except KeyError: + pass + try: rtc_region = options.pop("rtc_region") except KeyError: diff --git a/discord/channel.py b/discord/channel.py index 3677b325d7..beea5fe177 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1058,6 +1058,7 @@ async def edit( default_thread_slowmode_delay: int = ..., default_sort_order: SortOrder = ..., available_tags: list[ForumTag] = ..., + require_tag: bool = ..., overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ..., ) -> "ForumChannel" | None: ... @@ -1111,6 +1112,10 @@ async def edit(self, *, reason=None, **options): 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 Returns From 094477de97f971c61b601359e16134c6c3521547 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Tue, 15 Nov 2022 17:17:35 +0300 Subject: [PATCH 31/34] Parse available_tags to dicts in edit --- discord/abc.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/abc.py b/discord/abc.py index fff7a94400..ac5177ac57 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -413,6 +413,11 @@ async def _edit( except KeyError: pass + try: + options["available_tags"] = [tag.to_dict() for tag in options.pop("available_tags")] + except KeyError: + pass + try: rtc_region = options.pop("rtc_region") except KeyError: From 15f92ae2d462dc809e729b9f87fb378b477dbbe4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 14:18:33 +0000 Subject: [PATCH 32/34] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- discord/abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/abc.py b/discord/abc.py index ac5177ac57..491b161393 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -414,7 +414,9 @@ async def _edit( pass try: - options["available_tags"] = [tag.to_dict() for tag in options.pop("available_tags")] + options["available_tags"] = [ + tag.to_dict() for tag in options.pop("available_tags") + ] except KeyError: pass From da1a58db50eb87b9fbf0323b0d7c756b2afbb7d2 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Tue, 15 Nov 2022 21:51:16 +0300 Subject: [PATCH 33/34] fix: NameError due to TYPE_CHECKING imports --- discord/threads.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/threads.py b/discord/threads.py index 2547077f80..dbee6aa852 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -297,6 +297,8 @@ def applied_tags(self) -> list[ForumTag]: This is only available for threads in forum channels. """ + from .channel import ForumChannel # to prevent circular import + if isinstance(self.parent, ForumChannel): return [ tag From 51587bfa3c85e5ab0fd2033366cba81fd3805d09 Mon Sep 17 00:00:00 2001 From: Dorukyum Date: Tue, 15 Nov 2022 22:05:30 +0300 Subject: [PATCH 34/34] feat: applied_tags field in Thread.edit --- discord/threads.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/discord/threads.py b/discord/threads.py index dbee6aa852..96eee779ec 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -599,6 +599,7 @@ async def edit( slowmode_delay: int = MISSING, auto_archive_duration: ThreadArchiveDuration = MISSING, pinned: bool = MISSING, + applied_tags: list[ForumTag] = MISSING, reason: str | None = None, ) -> Thread: """|coro| @@ -633,6 +634,10 @@ async def edit( 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. + applied_tags: List[:class:`ForumTag`] + The set of tags to apply to the thread. Each tag object should have an ID set. + + .. versionadded:: 2.3 Returns ------- @@ -664,6 +669,8 @@ async def edit( flags = ChannelFlags._from_value(self.flags.value) flags.pinned = pinned payload["flags"] = flags.value + if applied_tags is not MISSING: + payload["applied_tags"] = [tag.id for tag in applied_tags] data = await self._state.http.edit_channel(self.id, **payload, reason=reason) # The data payload will always be a Thread payload