From 7dc189b675932ce916f00ccbc444197f0bf67f91 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Mon, 11 Apr 2022 11:06:06 -0500 Subject: [PATCH 01/12] Initial forum channel commit --- discord/abc.py | 3 +- discord/channel.py | 333 ++++++++++++++++++++++++++++++++------- discord/guild.py | 142 ++++++++++++++++- discord/http.py | 49 ++++++ discord/threads.py | 4 +- discord/types/channel.py | 5 + 6 files changed, 466 insertions(+), 70 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 03f86aff15..07b07ff8ba 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -81,7 +81,7 @@ GroupChannel, PartialMessageable, TextChannel, - VoiceChannel, + VoiceChannel ) from .client import Client from .embeds import Embed @@ -305,6 +305,7 @@ class GuildChannel: - :class:`~discord.VoiceChannel` - :class:`~discord.CategoryChannel` - :class:`~discord.StageChannel` + - :class:`~discord.ForumChannel` This ABC must also implement :class:`~discord.abc.Snowflake`. diff --git a/discord/channel.py b/discord/channel.py index 339a5a12ef..87a0c2405c 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -28,6 +28,7 @@ import asyncio import datetime import time +from abc import abstractmethod from typing import ( TYPE_CHECKING, Any, @@ -47,6 +48,7 @@ import discord.abc from . import utils +from .file import File from .asset import Asset from .enums import ( ChannelType, @@ -75,6 +77,7 @@ "CategoryChannel", "GroupChannel", "PartialMessageable", + "ForumChannel" ) if TYPE_CHECKING: @@ -91,13 +94,14 @@ 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 ForumChannel as ForumChannelPayload from .types.snowflake import SnowflakeList from .types.threads import ThreadArchiveDuration from .user import BaseUser, ClientUser, User from .webhook import Webhook -class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): +class _TextChannel(discord.abc.GuildChannel, Hashable): """Represents a Discord text channel. .. container:: operations @@ -169,25 +173,22 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable): "default_auto_archive_duration", ) - def __init__(self, *, state: ConnectionState, guild: Guild, data: TextChannelPayload): + def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayload]): self._state: ConnectionState = state self.id: int = int(data["id"]) self._type: int = data["type"] self._update(guild, data) + @property + def _repr_attrs(self) -> Tuple[str, ...]: + return "id", "name", "position", "nsfw", "category_id" + def __repr__(self) -> str: - attrs = [ - ("id", self.id), - ("name", self.name), - ("position", self.position), - ("nsfw", self.nsfw), - ("news", self.is_news()), - ("category_id", self.category_id), - ] + attrs = [(val, getattr(self, val)) for val in self._repr_attrs] joined = " ".join("%s=%r" % t for t in attrs) return f"<{self.__class__.__name__} {joined}>" - def _update(self, guild: Guild, data: TextChannelPayload) -> None: + def _update(self, guild: Guild, data: TextChannelPayload | ForumChannelPayload) -> None: self.guild: Guild = guild self.name: str = data["name"] self.category_id: Optional[int] = utils._get_as_snowflake(data, "parent_id") @@ -201,9 +202,6 @@ def _update(self, guild: Guild, data: TextChannelPayload) -> None: self.last_message_id: Optional[int] = utils._get_as_snowflake(data, "last_message_id") self._fill_overwrites(data) - async def _get_channel(self): - return self - @property def type(self) -> ChannelType: """:class:`ChannelType`: The channel's Discord type.""" @@ -239,10 +237,6 @@ def is_nsfw(self) -> bool: """:class:`bool`: Checks if the channel is NSFW.""" return self.nsfw - def is_news(self) -> bool: - """:class:`bool`: Checks if the channel is a news/anouncements channel.""" - return self._type == ChannelType.news.value - @property def last_message(self) -> Optional[Message]: """Fetches the last message from this channel in cache. @@ -653,14 +647,83 @@ def get_thread(self, thread_id: int, /) -> Optional[Thread]: """ return self.guild.get_thread(thread_id) - async def create_thread( + def archived_threads( self, *, - name: str, - message: Optional[Snowflake] = None, - auto_archive_duration: ThreadArchiveDuration = MISSING, - type: Optional[ChannelType] = None, - reason: Optional[str] = None, + private: bool = False, + joined: bool = False, + limit: Optional[int] = 50, + before: Optional[Union[Snowflake, datetime.datetime]] = None, + ) -> ArchivedThreadIterator: + """Returns an :class:`~discord.AsyncIterator` that iterates over all archived threads in the guild. + + You must have :attr:`~Permissions.read_message_history` to use this. If iterating over private threads + then :attr:`~Permissions.manage_threads` is also required. + + .. versionadded:: 2.0 + + Parameters + ----------- + limit: Optional[:class:`bool`] + The number of threads to retrieve. + If ``None``, retrieves every archived thread in the channel. Note, however, + that this would make it a slow operation. + before: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] + Retrieve archived channels before the given date or ID. + private: :class:`bool` + Whether to retrieve private archived threads. + joined: :class:`bool` + Whether to retrieve private archived threads that you've joined. + You cannot set ``joined`` to ``True`` and ``private`` to ``False``. + + Raises + ------ + Forbidden + You do not have permissions to get archived threads. + HTTPException + The request to get the archived threads failed. + + Yields + ------- + :class:`Thread` + The archived threads. + """ + return ArchivedThreadIterator( + self.id, + self.guild, + limit=limit, + joined=joined, + private=private, + before=before, + ) + + +class TextChannel(discord.abc.Messageable, _TextChannel): + def __init__(self, *, state: ConnectionState, guild: Guild, data: TextChannelPayload): + super().__init__(state=state, guild=guild, data=data) + + @property + def _repr_attrs(self) -> Tuple[str, ...]: + return super()._repr_attrs + ("news",) + + def _update(self, guild: Guild, data: TextChannelPayload) -> None: + super()._update(guild, data) + + def _get_channel(self) -> "TextChannel": + return self + + def is_news(self) -> bool: + """:class:`bool`: Checks if the channel is a news/anouncements channel.""" + return self._type == ChannelType.news.value + + async def create_thread( + self, + *, + name: str, + message: Optional[Snowflake] = None, + auto_archive_duration: ThreadArchiveDuration = MISSING, + type: Optional[ChannelType] = None, + reason: Optional[str] = None, ) -> Thread: """|coro| @@ -724,55 +787,177 @@ async def create_thread( return Thread(guild=self.guild, state=self._state, data=data) - def archived_threads( - self, - *, - private: bool = False, - joined: bool = False, - limit: Optional[int] = 50, - before: Optional[Union[Snowflake, datetime.datetime]] = None, - ) -> ArchivedThreadIterator: - """Returns an :class:`~discord.AsyncIterator` that iterates over all archived threads in the guild. - You must have :attr:`~Permissions.read_message_history` to use this. If iterating over private threads - then :attr:`~Permissions.manage_threads` is also required. +class ForumChannel(_TextChannel): + def __init__(self, *, state: ConnectionState, guild: Guild, data: ForumChannelPayload): + super().__init__(state=state, guild=guild, data=data) + + def _update(self, guild: Guild, data: ForumChannelPayload) -> None: + super()._update(guild, data) + + @property + def guidelines(self) -> Optional[str]: + """Optional[:class:`str`]: The channel's guidelines. An alias of :attr:`topic`. + """ + return self.topic + + async def create_post( + self, + name: str, # Could be renamed to title? + message_content=None, + *, + tts=None, + embed=None, + embeds=None, + file=None, + files=None, + stickers=None, + delete_message_after=None, + nonce=None, + allowed_mentions=None, + view=None, + auto_archive_duration: ThreadArchiveDuration = MISSING, + reason: Optional[str] = None, + ) -> Thread: + """|coro| + + Creates a thread in this forum channel. + + To create a public thread, you must have :attr:`~discord.Permissions.create_public_threads`. + For a private thread, :attr:`~discord.Permissions.create_private_threads` is needed instead. .. versionadded:: 2.0 Parameters ----------- - limit: Optional[:class:`bool`] - The number of threads to retrieve. - If ``None``, retrieves every archived thread in the channel. Note, however, - that this would make it a slow operation. - before: Optional[Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]] - Retrieve archived channels before the given date or ID. - private: :class:`bool` - Whether to retrieve private archived threads. - joined: :class:`bool` - Whether to retrieve private archived threads that you've joined. - You cannot set ``joined`` to ``True`` and ``private`` to ``False``. + name: :class:`str` + The name of the thread. + message: Optional[:class:`abc.Snowflake`] + A snowflake representing the message to create the thread with. + If ``None`` is passed then a private thread is created. + Defaults to ``None``. + 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. + type: Optional[:class:`ChannelType`] + The type of thread to create. If a ``message`` is passed then this parameter + is ignored, as a thread created with a message is always a public thread. + By default this creates a private thread if this is ``None``. + reason: :class:`str` + The reason for creating a new thread. Shows up on the audit log. Raises - ------ + ------- Forbidden - You do not have permissions to get archived threads. + You do not have permissions to create a thread. HTTPException - The request to get the archived threads failed. + Starting the thread failed. - Yields - ------- + Returns + -------- :class:`Thread` - The archived threads. + The created thread """ - return ArchivedThreadIterator( - self.id, - self.guild, - limit=limit, - joined=joined, - private=private, - before=before, - ) + state = self._state + content = str(message_content) if message_content is not None else None + + if embed is not None and embeds is not None: + raise InvalidArgument("cannot pass both embed and embeds parameter to create_post()") + + if embed is not None: + embed = embed.to_dict() + + elif embeds is not None: + if len(embeds) > 10: + raise InvalidArgument("embeds parameter must be a list of up to 10 elements") + embeds = [embed.to_dict() for embed in embeds] + + if stickers is not None: + stickers = [sticker.id for sticker in stickers] + + if allowed_mentions is None: + allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict() + elif state.allowed_mentions is not None: + allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict() + else: + allowed_mentions = allowed_mentions.to_dict() + + if view: + if not hasattr(view, "__discord_ui_view__"): + raise InvalidArgument(f"view parameter must be View not {view.__class__!r}") + + components = view.to_components() + else: + components = None + + if file is not None and files is not None: + raise InvalidArgument("cannot pass both file and files parameter to send()") + + if file is not None: + if not isinstance(file, File): + raise InvalidArgument("file parameter must be File") + + try: + data = await state.http.send_files( + self.id, + files=[file], + allowed_mentions=allowed_mentions, + content=content, + tts=tts, + embed=embed, + embeds=embeds, + nonce=nonce, + stickers=stickers, + components=components, + ) + finally: + file.close() + + elif files is not None: + if len(files) > 10: + raise InvalidArgument("files parameter must be a list of up to 10 elements") + elif not all(isinstance(file, File) for file in files): + raise InvalidArgument("files parameter must be a list of File") + + try: + data = await state.http.send_files( + self.id, + files=files, + content=content, + tts=tts, + embed=embed, + embeds=embeds, + nonce=nonce, + allowed_mentions=allowed_mentions, + stickers=stickers, + components=components, + ) + finally: + for f in files: + f.close() + else: + data = await state.http.start_forum_thread( + self.id, + content, + name=name, + tts=tts, + embed=embed, + embeds=embeds, + nonce=nonce, + allowed_mentions=allowed_mentions, + stickers=stickers, + components=components, + auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + reason=reason, + ) + ret = Thread(guild=self.guild, state=self._state, data=data) + msg = ret.get_partial_message(data['last_message_id']) + if view: + state.store_view(view, msg.id) + + if delete_message_after is not None: + await msg.delete(delay=delete_message_after) + return ret class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable): @@ -1784,7 +1969,7 @@ def channels(self) -> List[GuildChannelType]: """ def comparator(channel): - return (not isinstance(channel, TextChannel), channel.position) + return (not isinstance(channel, _TextChannel), channel.position) ret = [c for c in self.guild.channels if c.category_id == self.id] ret.sort(key=comparator) @@ -1814,6 +1999,16 @@ def stage_channels(self) -> List[StageChannel]: ret.sort(key=lambda c: (c.position, c.id)) return ret + @property + def forum_channels(self) -> List[ForumChannel]: + """List[:class:`ForumChannel`]: Returns the forum channels that are under this category. + + .. versionadded:: 2.0 + """ + ret = [c for c in self.guild.channels if c.category_id == self.id and isinstance(c, ForumChannel)] + ret.sort(key=lambda c: (c.position, c.id)) + return ret + async def create_text_channel(self, name: str, **options: Any) -> TextChannel: """|coro| @@ -1852,6 +2047,20 @@ async def create_stage_channel(self, name: str, **options: Any) -> StageChannel: """ return await self.guild.create_stage_channel(name, category=self, **options) + async def create_forum_channel(self, name: str, **options: Any) -> ForumChannel: + """|coro| + + A shortcut method to :meth:`Guild.create_forum_channel` to create a :class:`ForumChannel` in the category. + + .. versionadded:: 2.0 + + Returns + ------- + :class:`ForumChannel` + The channel that was just created. + """ + return await self.guild.create_forum_channel(name, category=self, **options) + DMC = TypeVar("DMC", bound="DMChannel") @@ -2209,6 +2418,8 @@ def _guild_channel_factory(channel_type: int): return TextChannel, value elif value is ChannelType.stage_voice: return StageChannel, value + elif value is ChannelType.forum: + return ForumChannel, value else: return None, value diff --git a/discord/guild.py b/discord/guild.py index 33862cd978..ca5f987701 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -93,6 +93,7 @@ StageChannel, TextChannel, VoiceChannel, + ForumChannel ) from .permissions import Permissions from .state import ConnectionState @@ -608,6 +609,18 @@ def stage_channels(self) -> List[StageChannel]: r.sort(key=lambda c: (c.position, c.id)) return r + @property + def forum_channels(self) -> List[ForumChannel]: + """List[:class:`ForumChannel`]: A list of forum channels that belongs to this guild. + + .. versionadded:: 2.0 + + This is sorted by the position and are in UI order from top to bottom. + """ + r = [ch for ch in self._channels.values() if isinstance(ch, ForumChannel)] + r.sort(key=lambda c: (c.position, c.id)) + return r + @property def me(self) -> Member: """:class:`Member`: Similar to :attr:`Client.user` except an instance of :class:`Member`. @@ -1343,13 +1356,130 @@ async def create_stage_channel( self._channels[channel.id] = channel return channel + async def create_forum_channel( + self, + name: str, + *, + reason: Optional[str] = None, + category: Optional[CategoryChannel] = None, + position: int = MISSING, + topic: str = MISSING, + slowmode_delay: int = MISSING, + nsfw: bool = MISSING, + overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, + ) -> ForumChannel: + """|coro| + + Creates a :class:`ForumChannel` for the guild. + + Note that you need the :attr:`~Permissions.manage_channels` permission + to create the channel. + + The ``overwrites`` parameter can be used to create a 'secret' + channel upon creation. This parameter expects a :class:`dict` of + overwrites with the target (either a :class:`Member` or a :class:`Role`) + as the key and a :class:`PermissionOverwrite` as the value. + + .. note:: + + Creating a channel of a specified position will not update the position of + other channels to follow suit. A follow-up call to :meth:`~ForumChannel.edit` + will be required to update the position of the channel in the channel list. + + Examples + ---------- + + Creating a basic channel: + + .. code-block:: python3 + + channel = await guild.create_forum_channel('cool-channel') + + Creating a "secret" channel: + + .. code-block:: python3 + + overwrites = { + guild.default_role: discord.PermissionOverwrite(read_messages=False), + guild.me: discord.PermissionOverwrite(read_messages=True) + } + + channel = await guild.create_forum_channel('secret', overwrites=overwrites) + + Parameters + ----------- + name: :class:`str` + The channel's name. + overwrites: Dict[Union[:class:`Role`, :class:`Member`], :class:`PermissionOverwrite`] + A :class:`dict` of target (either a role or a member) to + :class:`PermissionOverwrite` to apply upon creation of a channel. + Useful for creating secret channels. + category: Optional[:class:`CategoryChannel`] + The category to place the newly created channel under. + The permissions will be automatically synced to category if no + overwrites are provided. + position: :class:`int` + The position in the channel list. This is a number that starts + at 0. e.g. the top channel is position 0. + topic: :class:`str` + The new channel's topic. + slowmode_delay: :class:`int` + Specifies the slowmode rate limit for user in this channel, in seconds. + The maximum value possible is `21600`. + nsfw: :class:`bool` + To mark the channel as NSFW or not. + reason: Optional[:class:`str`] + The reason for creating this channel. Shows up on the audit log. + + Raises + ------- + Forbidden + You do not have the proper permissions to create this channel. + HTTPException + Creating the channel failed. + InvalidArgument + The permission overwrite information is not in proper form. + + Returns + ------- + :class:`ForumChannel` + The channel that was just created. + """ + + options = {} + if position is not MISSING: + options["position"] = position + + if topic is not MISSING: + options["topic"] = topic + + if slowmode_delay is not MISSING: + options["rate_limit_per_user"] = slowmode_delay + + if nsfw is not MISSING: + options["nsfw"] = nsfw + + data = await self._create_channel( + name, + overwrites=overwrites, + channel_type=ChannelType.forum, + category=category, + reason=reason, + **options, + ) + channel = ForumChannel(state=self._state, guild=self, data=data) + + # temporarily add to the cache + self._channels[channel.id] = channel + return channel + async def create_category( - self, - name: str, - *, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, - reason: Optional[str] = None, - position: int = MISSING, + self, + name: str, + *, + overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, + reason: Optional[str] = None, + position: int = MISSING, ) -> CategoryChannel: """|coro| diff --git a/discord/http.py b/discord/http.py index d26c2dc21d..b74b96c8ca 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1084,6 +1084,55 @@ def start_thread_without_message( route = Route("POST", "/channels/{channel_id}/threads", channel_id=channel_id) return self.request(route, json=payload, reason=reason) + def start_forum_thread( + self, + channel_id: Snowflake, + content: Optional[str], + *, + name: str, + auto_archive_duration: threads.ThreadArchiveDuration, + invitable: bool = True, + reason: Optional[str] = None, + tts: bool = False, + embed: Optional[embed.Embed] = None, + embeds: Optional[List[embed.Embed]] = None, + nonce: Optional[str] = None, + allowed_mentions: Optional[message.AllowedMentions] = None, + stickers: Optional[List[sticker.StickerItem]] = None, + components: Optional[List[components.Component]] = None, + ) -> Response[threads.Thread]: + payload = { + "name": name, + "auto_archive_duration": auto_archive_duration, + "invitable": invitable, + } + if content: + payload["content"] = content + + if tts: + payload["tts"] = True + + if embed: + payload["embeds"] = [embed] + + if embeds: + payload["embeds"] = embeds + + if nonce: + payload["nonce"] = nonce + + if allowed_mentions: + payload["allowed_mentions"] = allowed_mentions + + if components: + payload["components"] = components + + if stickers: + payload["sticker_ids"] = stickers + # TODO: Once supported by API, remove has_message=true query parameter + route = Route("POST", "/channels/{channel_id}/threads?has_message=true", channel_id=channel_id) + return self.request(route, json=payload, reason=reason) + def join_thread(self, channel_id: Snowflake) -> Response[None]: return self.request( Route( diff --git a/discord/threads.py b/discord/threads.py index 9aff03e6c1..653db91bd4 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -42,7 +42,7 @@ if TYPE_CHECKING: from .abc import Snowflake, SnowflakeTime - from .channel import CategoryChannel, TextChannel + from .channel import CategoryChannel, TextChannel, ForumChannel from .guild import Guild from .member import Member from .message import Message, PartialMessage @@ -209,7 +209,7 @@ def type(self) -> ChannelType: return self._type @property - def parent(self) -> Optional[TextChannel]: + def parent(self) -> Optional[Union[TextChannel, ForumChannel]]: """Optional[:class:`TextChannel`]: The parent channel this thread belongs to.""" return self.guild.get_channel(self.parent_id) # type: ignore diff --git a/discord/types/channel.py b/discord/types/channel.py index e707d32de3..e2f2de5d54 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -71,6 +71,10 @@ class TextChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[0] +class ForumChannel(_BaseGuildChannel, _TextChannelOptional): + type: Literal[15] + + class NewsChannel(_BaseGuildChannel, _TextChannelOptional): type: Literal[5] @@ -132,6 +136,7 @@ class ThreadChannel(_BaseChannel, _ThreadChannelOptional): CategoryChannel, StageChannel, ThreadChannel, + ForumChannel, ] From 3046fb1235fb4f0d415dbaa3aa086383706c6fec Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Wed, 20 Apr 2022 11:57:13 -0500 Subject: [PATCH 02/12] Update discord/channel.py Co-authored-by: plun1331 <49261529+plun1331@users.noreply.github.com> --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 9cc511b1c1..59957d198c 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -188,7 +188,7 @@ def __repr__(self) -> str: joined = " ".join("%s=%r" % t for t in attrs) return f"<{self.__class__.__name__} {joined}>" - def _update(self, guild: Guild, data: TextChannelPayload | ForumChannelPayload) -> None: + def _update(self, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayload]) -> None: self.guild: Guild = guild self.name: str = data["name"] self.category_id: Optional[int] = utils._get_as_snowflake(data, "parent_id") From 5a73a3df7c4782e8d4bf9c1834bb3a7dc453d588 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Thu, 21 Apr 2022 13:59:08 -0500 Subject: [PATCH 03/12] Update discord/abc.py Co-authored-by: Lala Sabathil --- discord/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/abc.py b/discord/abc.py index ebb3ac1e4f..50c9ca3b5f 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -81,7 +81,7 @@ GroupChannel, PartialMessageable, TextChannel, - VoiceChannel + VoiceChannel, ) from .client import Client from .embeds import Embed From 653053ee76736ba1fea58853fe9c92b25da4febd Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Thu, 21 Apr 2022 13:59:14 -0500 Subject: [PATCH 04/12] Update discord/channel.py Co-authored-by: Lala Sabathil --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 59957d198c..9ef2e0583c 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -77,7 +77,7 @@ "CategoryChannel", "GroupChannel", "PartialMessageable", - "ForumChannel" + "ForumChannel", ) if TYPE_CHECKING: From c2bc9fd1484fe7540c895fc87957c8c9bdd8246d Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Thu, 21 Apr 2022 13:59:26 -0500 Subject: [PATCH 05/12] Update discord/channel.py Co-authored-by: Lala Sabathil --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 9ef2e0583c..0c3217a5e7 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -804,7 +804,7 @@ def guidelines(self) -> Optional[str]: async def create_post( self, name: str, # Could be renamed to title? - message_content=None, + content=None, *, tts=None, embed=None, From c3e0f81e7961b4f8072c96ff5cf90acca0801e61 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Thu, 21 Apr 2022 13:59:34 -0500 Subject: [PATCH 06/12] Update discord/channel.py Co-authored-by: Lala Sabathil --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 0c3217a5e7..48a1bef31a 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -859,7 +859,7 @@ async def create_post( The created thread """ state = self._state - content = str(message_content) if message_content is not None else None + message_content = str(content) if content is not None else None if embed is not None and embeds is not None: raise InvalidArgument("cannot pass both embed and embeds parameter to create_post()") From 74bcb2b9df86e9732ac8746df888723e0723c0cd Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Thu, 21 Apr 2022 14:00:07 -0500 Subject: [PATCH 07/12] Update discord/channel.py Co-authored-by: Lala Sabathil --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 48a1bef31a..9f1149ebab 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -902,7 +902,7 @@ async def create_post( self.id, files=[file], allowed_mentions=allowed_mentions, - content=content, + content=message_content, tts=tts, embed=embed, embeds=embeds, From 205552a2af7530fa9c31613675d6f399ead326c5 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Thu, 21 Apr 2022 14:00:15 -0500 Subject: [PATCH 08/12] Update discord/channel.py Co-authored-by: Lala Sabathil --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 9f1149ebab..76c606c781 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -923,7 +923,7 @@ async def create_post( data = await state.http.send_files( self.id, files=files, - content=content, + content=message_content, tts=tts, embed=embed, embeds=embeds, From 7b96490175f36ab447ee85bc459e70988fbe4747 Mon Sep 17 00:00:00 2001 From: BobDotCom <71356958+BobDotCom@users.noreply.github.com> Date: Thu, 21 Apr 2022 14:00:21 -0500 Subject: [PATCH 09/12] Update discord/channel.py Co-authored-by: Lala Sabathil --- discord/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index 76c606c781..0473443f10 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -938,7 +938,7 @@ async def create_post( else: data = await state.http.start_forum_thread( self.id, - content, + content=message_content, name=name, tts=tts, embed=embed, From 17ee76269a0cc306a949ff8608be78b2efd35ceb Mon Sep 17 00:00:00 2001 From: plun1331 Date: Tue, 26 Apr 2022 16:36:31 -0700 Subject: [PATCH 10/12] Add more forum channel/thread features --- discord/abc.py | 2 ++ discord/channel.py | 70 +++++++++++++++++++++++++++++++++++++--------- discord/flags.py | 36 ++++++++++++++++++++++++ discord/http.py | 8 +++--- discord/threads.py | 16 +++++++++++ 5 files changed, 115 insertions(+), 17 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 50c9ca3b5f..5548c48653 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -97,6 +97,7 @@ from .types.channel import PermissionOverwrite as PermissionOverwritePayload from .ui.view import View from .user import ClientUser + from .flags import ChannelFlags PartialMessageableChannel = Union[TextChannel, VoiceChannel, Thread, DMChannel, PartialMessageable] MessageableChannel = Union[PartialMessageableChannel, GroupChannel] @@ -328,6 +329,7 @@ class GuildChannel: type: ChannelType position: int category_id: Optional[int] + flags: ChannelFlags _state: ConnectionState _overwrites: List[_Overwrites] diff --git a/discord/channel.py b/discord/channel.py index 0473443f10..c3519e8ac3 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -60,6 +60,7 @@ try_enum, ) from .errors import ClientException, InvalidArgument +from .flags import ChannelFlags from .invite import Invite from .iterators import ArchivedThreadIterator from .mixins import Hashable @@ -154,6 +155,10 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): 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 """ @@ -171,6 +176,7 @@ class _TextChannel(discord.abc.GuildChannel, Hashable): "_type", "last_message_id", "default_auto_archive_duration", + "flags", ) def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayload]): @@ -200,6 +206,7 @@ def _update(self, guild: Guild, data: Union[TextChannelPayload, ForumChannelPayl self.default_auto_archive_duration: ThreadArchiveDuration = data.get("default_auto_archive_duration", 1440) self._type: int = data.get("type", self._type) 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) @property @@ -801,12 +808,11 @@ def guidelines(self) -> Optional[str]: """ return self.topic - async def create_post( + async def create_thread( self, - name: str, # Could be renamed to title? + name: str, content=None, *, - tts=None, embed=None, embeds=None, file=None, @@ -817,6 +823,7 @@ async def create_post( allowed_mentions=None, view=None, auto_archive_duration: ThreadArchiveDuration = MISSING, + slowmode_delay: int = MISSING, reason: Optional[str] = None, ) -> Thread: """|coro| @@ -832,17 +839,39 @@ async def create_post( ----------- name: :class:`str` The name of the thread. - message: Optional[:class:`abc.Snowflake`] - A snowflake representing the message to create the thread with. - If ``None`` is passed then a private thread is created. - Defaults to ``None``. + content: :class:`str` + The content of the message to send. + embed: :class:`~discord.Embed` + The rich embed for the content. + file: :class:`~discord.File` + The file to upload. + files: List[:class:`~discord.File`] + A list of files to upload. Must be a maximum of 10. + nonce: :class:`int` + The nonce to use for sending this message. If the message was successfully sent, + then the message will have a nonce with this value. + allowed_mentions: :class:`~discord.AllowedMentions` + Controls the mentions being processed in this message. If this is + passed, then the object is merged with :attr:`~discord.Client.allowed_mentions`. + The merging behaviour only overrides attributes that have been explicitly passed + to the object, otherwise it uses the attributes set in :attr:`~discord.Client.allowed_mentions`. + If no object is passed at all then the defaults given by :attr:`~discord.Client.allowed_mentions` + are used instead. + view: :class:`discord.ui.View` + A Discord UI View to add to the message. + embeds: List[:class:`~discord.Embed`] + A list of embeds to upload. Must be a maximum of 10. + stickers: Sequence[Union[:class:`~discord.GuildSticker`, :class:`~discord.StickerItem`]] + A list of stickers to upload. Must be a maximum of 3. 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. - type: Optional[:class:`ChannelType`] - The type of thread to create. If a ``message`` is passed then this parameter - is ignored, as a thread created with a message is always a public thread. - By default this creates a private thread if this is ``None``. + slowmode_delay: :class:`int` + The number of seconds a member must wait between sending messages + in the new thread. A value of `0` denotes that it is disabled. + Bots and users with :attr:`~Permissions.manage_channels` or + :attr:`~Permissions.manage_messages` bypass slowmode. + If not provided, the forum channel's default slowmode is used. reason: :class:`str` The reason for creating a new thread. Shows up on the audit log. @@ -903,7 +932,6 @@ async def create_post( files=[file], allowed_mentions=allowed_mentions, content=message_content, - tts=tts, embed=embed, embeds=embeds, nonce=nonce, @@ -924,7 +952,6 @@ async def create_post( self.id, files=files, content=message_content, - tts=tts, embed=embed, embeds=embeds, nonce=nonce, @@ -948,6 +975,7 @@ async def create_post( stickers=stickers, components=components, auto_archive_duration=auto_archive_duration or self.default_auto_archive_duration, + rate_limit_per_user=slowmode_delay or self.slowmode_delay, reason=reason, ) ret = Thread(guild=self.guild, state=self._state, data=data) @@ -974,6 +1002,7 @@ class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hasha "rtc_region", "video_quality_mode", "last_message_id", + "flags", ) def __init__( @@ -1004,6 +1033,7 @@ def _update(self, guild: Guild, data: Union[VoiceChannelPayload, StageChannelPay self.position: int = data.get("position") self.bitrate: int = data.get("bitrate") self.user_limit: int = data.get("user_limit") + self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) self._fill_overwrites(data) @property @@ -1105,6 +1135,10 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): .. versionadded:: 2.0 last_message_id: Optional[:class:`int`] The ID of the last message sent to this channel. It may not always point to an existing or valid message. + .. versionadded:: 2.0 + flags: :class:`ChannelFlags` + Extra features of the channel. + .. versionadded:: 2.0 """ @@ -1572,6 +1606,10 @@ class StageChannel(VocalGuildChannel): video_quality_mode: :class:`VideoQualityMode` The camera video quality for the stage channel's participants. + .. versionadded:: 2.0 + flags: :class:`ChannelFlags` + Extra features of the channel. + .. versionadded:: 2.0 """ @@ -1845,6 +1883,10 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable): .. note:: To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead. + flags: :class:`ChannelFlags` + Extra features of the channel. + + .. versionadded:: 2.0 """ __slots__ = ( @@ -1856,6 +1898,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable): "position", "_overwrites", "category_id", + "flags", ) def __init__(self, *, state: ConnectionState, guild: Guild, data: CategoryChannelPayload): @@ -1872,6 +1915,7 @@ def _update(self, guild: Guild, data: CategoryChannelPayload) -> None: self.category_id: Optional[int] = utils._get_as_snowflake(data, "parent_id") self.nsfw: bool = data.get("nsfw", False) self.position: int = data.get("position") + self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) self._fill_overwrites(data) @property diff --git a/discord/flags.py b/discord/flags.py index 0a1d0724e2..c17790e647 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -1198,3 +1198,39 @@ def gateway_message_content_limited(self): and has hit the guild limit. """ return 1 << 19 + + +@fill_with_flags() +class ChannelFlags(BaseFlags): + r"""Wraps up the Discord Channel flags. + + .. container:: operations + + .. describe:: x == y + + Checks if two ChannelFlags are equal. + .. describe:: x != y + + Checks if two ChannelFlags are not equal. + .. describe:: hash(x) + + Return the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + .. versionadded:: 2.0 + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def pinned(self): + """:class:`bool`: Returns ``True`` if the thread is pinned to the top of its parent forum channel.""" + return 1 << 1 diff --git a/discord/http.py b/discord/http.py index 3afb78eca1..2ec52ffe81 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1091,9 +1091,9 @@ def start_forum_thread( *, name: str, auto_archive_duration: threads.ThreadArchiveDuration, + rate_limit_per_user: int, invitable: bool = True, reason: Optional[str] = None, - tts: bool = False, embed: Optional[embed.Embed] = None, embeds: Optional[List[embed.Embed]] = None, nonce: Optional[str] = None, @@ -1109,9 +1109,6 @@ def start_forum_thread( if content: payload["content"] = content - if tts: - payload["tts"] = True - if embed: payload["embeds"] = [embed] @@ -1129,6 +1126,9 @@ def start_forum_thread( if stickers: payload["sticker_ids"] = stickers + + if rate_limit_per_user: + payload["rate_limit_per_user"] = rate_limit_per_user # TODO: Once supported by API, remove has_message=true query parameter route = Route("POST", "/channels/{channel_id}/threads?has_message=true", channel_id=channel_id) return self.request(route, json=payload, reason=reason) diff --git a/discord/threads.py b/discord/threads.py index 322f50af4d..cafbc21ea5 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -32,6 +32,7 @@ from .abc import Messageable, _purge_messages_helper from .enums import ChannelType, try_enum from .errors import ClientException +from .flags import ChannelFlags from .mixins import Hashable from .utils import MISSING, _get_as_snowflake, parse_time @@ -121,6 +122,10 @@ class Thread(Messageable, Hashable): created_at: Optional[:class:`datetime.datetime`] An aware timestamp of when the thread was created. Only available for threads created after 2022-01-09. + flags: :class:`ChannelFlags` + Extra features of the thread. + + .. versionadded:: 2.0 """ __slots__ = ( @@ -143,6 +148,7 @@ class Thread(Messageable, Hashable): "auto_archive_duration", "archive_timestamp", "created_at", + "flags", ) def __init__(self, *, guild: Guild, state: ConnectionState, data: ThreadPayload): @@ -173,6 +179,7 @@ def _from_data(self, data: ThreadPayload): self.slowmode_delay = data.get("rate_limit_per_user", 0) self.message_count = data["message_count"] self.member_count = data["member_count"] + self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) self._unroll_metadata(data["thread_metadata"]) try: @@ -196,6 +203,7 @@ def _update(self, data): except KeyError: pass + self.flags: ChannelFlags = ChannelFlags._from_value(data.get("flags", 0)) self.slowmode_delay = data.get("rate_limit_per_user", 0) try: @@ -503,6 +511,7 @@ async def edit( invitable: bool = MISSING, slowmode_delay: int = MISSING, auto_archive_duration: ThreadArchiveDuration = MISSING, + pinned: bool = MISSING, reason: Optional[str] = None, ) -> Thread: """|coro| @@ -535,6 +544,8 @@ async def edit( A value of ``0`` disables slowmode. The maximum value possible is ``21600``. 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. Raises ------- @@ -561,6 +572,11 @@ async def edit( payload["invitable"] = invitable if slowmode_delay is not MISSING: payload["rate_limit_per_user"] = slowmode_delay + if pinned is not MISSING: + # copy the ChannelFlags object to avoid mutating the original + flags = ChannelFlags._from_value(self.flags.value) + flags.pinned = pinned + payload['flags'] = flags.value data = await self._state.http.edit_channel(self.id, **payload, reason=reason) # The data payload will always be a Thread payload From 171de4b8951b6da152f0b4c1a29426a7e8f2ba26 Mon Sep 17 00:00:00 2001 From: krittick Date: Sat, 30 Apr 2022 18:38:51 -0700 Subject: [PATCH 11/12] Update discord/channel.py --- discord/channel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/discord/channel.py b/discord/channel.py index c3519e8ac3..0ae1713ebe 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -967,7 +967,6 @@ async def create_thread( self.id, content=message_content, name=name, - tts=tts, embed=embed, embeds=embeds, nonce=nonce, From 116e00c8466c14b095c9731133424b1aec1ecfaa Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 30 Apr 2022 18:44:17 -0700 Subject: [PATCH 12/12] apply linting, fix indentation --- discord/abc.py | 2 +- discord/channel.py | 55 +++++++++++++++++++++++----------------------- discord/guild.py | 34 ++++++++++++++-------------- discord/http.py | 30 ++++++++++++------------- discord/threads.py | 4 ++-- 5 files changed, 62 insertions(+), 63 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 5548c48653..44657d26b4 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -86,6 +86,7 @@ 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 @@ -97,7 +98,6 @@ from .types.channel import PermissionOverwrite as PermissionOverwritePayload from .ui.view import View from .user import ClientUser - from .flags import ChannelFlags PartialMessageableChannel = Union[TextChannel, VoiceChannel, Thread, DMChannel, PartialMessageable] MessageableChannel = Union[PartialMessageableChannel, GroupChannel] diff --git a/discord/channel.py b/discord/channel.py index 0ae1713ebe..160341e427 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -48,7 +48,6 @@ import discord.abc from . import utils -from .file import File from .asset import Asset from .enums import ( ChannelType, @@ -60,6 +59,7 @@ try_enum, ) from .errors import ClientException, InvalidArgument +from .file import File from .flags import ChannelFlags from .invite import Invite from .iterators import ArchivedThreadIterator @@ -91,11 +91,11 @@ 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 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 ForumChannel as ForumChannelPayload from .types.snowflake import SnowflakeList from .types.threads import ThreadArchiveDuration from .user import BaseUser, ClientUser, User @@ -724,13 +724,13 @@ def is_news(self) -> bool: return self._type == ChannelType.news.value async def create_thread( - self, - *, - name: str, - message: Optional[Snowflake] = None, - auto_archive_duration: ThreadArchiveDuration = MISSING, - type: Optional[ChannelType] = None, - reason: Optional[str] = None, + self, + *, + name: str, + message: Optional[Snowflake] = None, + auto_archive_duration: ThreadArchiveDuration = MISSING, + type: Optional[ChannelType] = None, + reason: Optional[str] = None, ) -> Thread: """|coro| @@ -804,27 +804,26 @@ def _update(self, guild: Guild, data: ForumChannelPayload) -> None: @property def guidelines(self) -> Optional[str]: - """Optional[:class:`str`]: The channel's guidelines. An alias of :attr:`topic`. - """ + """Optional[:class:`str`]: The channel's guidelines. An alias of :attr:`topic`.""" return self.topic async def create_thread( - self, - name: str, - content=None, - *, - embed=None, - embeds=None, - file=None, - files=None, - stickers=None, - delete_message_after=None, - nonce=None, - allowed_mentions=None, - view=None, - auto_archive_duration: ThreadArchiveDuration = MISSING, - slowmode_delay: int = MISSING, - reason: Optional[str] = None, + self, + name: str, + content=None, + *, + embed=None, + embeds=None, + file=None, + files=None, + stickers=None, + delete_message_after=None, + nonce=None, + allowed_mentions=None, + view=None, + auto_archive_duration: ThreadArchiveDuration = MISSING, + slowmode_delay: int = MISSING, + reason: Optional[str] = None, ) -> Thread: """|coro| @@ -978,7 +977,7 @@ async def create_thread( reason=reason, ) ret = Thread(guild=self.guild, state=self._state, data=data) - msg = ret.get_partial_message(data['last_message_id']) + msg = ret.get_partial_message(data["last_message_id"]) if view: state.store_view(view, msg.id) diff --git a/discord/guild.py b/discord/guild.py index 9f17759171..271c3a049f 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -90,10 +90,10 @@ from .abc import Snowflake, SnowflakeTime from .channel import ( CategoryChannel, + ForumChannel, StageChannel, TextChannel, VoiceChannel, - ForumChannel, ) from .permissions import Permissions from .state import ConnectionState @@ -1365,16 +1365,16 @@ async def create_stage_channel( return channel async def create_forum_channel( - self, - name: str, - *, - reason: Optional[str] = None, - category: Optional[CategoryChannel] = None, - position: int = MISSING, - topic: str = MISSING, - slowmode_delay: int = MISSING, - nsfw: bool = MISSING, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, + self, + name: str, + *, + reason: Optional[str] = None, + category: Optional[CategoryChannel] = None, + position: int = MISSING, + topic: str = MISSING, + slowmode_delay: int = MISSING, + nsfw: bool = MISSING, + overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, ) -> ForumChannel: """|coro| @@ -1482,12 +1482,12 @@ async def create_forum_channel( return channel async def create_category( - self, - name: str, - *, - overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, - reason: Optional[str] = None, - position: int = MISSING, + self, + name: str, + *, + overwrites: Dict[Union[Role, Member], PermissionOverwrite] = MISSING, + reason: Optional[str] = None, + position: int = MISSING, ) -> CategoryChannel: """|coro| diff --git a/discord/http.py b/discord/http.py index 2ec52ffe81..ae6e667456 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1085,21 +1085,21 @@ def start_thread_without_message( return self.request(route, json=payload, reason=reason) def start_forum_thread( - self, - channel_id: Snowflake, - content: Optional[str], - *, - name: str, - auto_archive_duration: threads.ThreadArchiveDuration, - rate_limit_per_user: int, - invitable: bool = True, - reason: Optional[str] = None, - embed: Optional[embed.Embed] = None, - embeds: Optional[List[embed.Embed]] = None, - nonce: Optional[str] = None, - allowed_mentions: Optional[message.AllowedMentions] = None, - stickers: Optional[List[sticker.StickerItem]] = None, - components: Optional[List[components.Component]] = None, + self, + channel_id: Snowflake, + content: Optional[str], + *, + name: str, + auto_archive_duration: threads.ThreadArchiveDuration, + rate_limit_per_user: int, + invitable: bool = True, + reason: Optional[str] = None, + embed: Optional[embed.Embed] = None, + embeds: Optional[List[embed.Embed]] = None, + nonce: Optional[str] = None, + allowed_mentions: Optional[message.AllowedMentions] = None, + stickers: Optional[List[sticker.StickerItem]] = None, + components: Optional[List[components.Component]] = None, ) -> Response[threads.Thread]: payload = { "name": name, diff --git a/discord/threads.py b/discord/threads.py index cafbc21ea5..03cb327bac 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -43,7 +43,7 @@ if TYPE_CHECKING: from .abc import Snowflake, SnowflakeTime - from .channel import CategoryChannel, TextChannel, ForumChannel + from .channel import CategoryChannel, ForumChannel, TextChannel from .guild import Guild from .member import Member from .message import Message, PartialMessage @@ -576,7 +576,7 @@ async def edit( # copy the ChannelFlags object to avoid mutating the original flags = ChannelFlags._from_value(self.flags.value) flags.pinned = pinned - payload['flags'] = flags.value + payload["flags"] = flags.value data = await self._state.http.edit_channel(self.id, **payload, reason=reason) # The data payload will always be a Thread payload