Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Complete forum channel implementation #1636

Merged
merged 56 commits into from Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
78f58ea
Add ForumTag type
Dorukyum Sep 15, 2022
6d5dc2d
Make emoji_name nullable
Dorukyum Sep 16, 2022
512d107
Merge branch 'master' into forum-tags
Dorukyum Sep 24, 2022
83e6382
Add forum tag fields
Dorukyum Sep 28, 2022
6a3066e
Merge branch 'forum-tags' of https://github.com/Pycord-Development/py…
Dorukyum Sep 28, 2022
97427b1
Add missing attributes & create ForumTag
Dorukyum Oct 17, 2022
48cda11
Merge branch 'master' into forum-tags
BobDotCom Oct 17, 2022
344badc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 17, 2022
e82d4dd
Fix typehint syntax
BobDotCom Oct 17, 2022
ed599df
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 17, 2022
4d9af9c
Fix typehints
BobDotCom Oct 17, 2022
d198ca9
Update discord/http.py
Dorukyum Oct 19, 2022
114c181
Merge branch 'master' into forum-tags
Lulalaby Oct 25, 2022
0891c79
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 25, 2022
11afeb7
Update channel.py
Lulalaby Oct 25, 2022
f716176
Update channel.py
Lulalaby Oct 25, 2022
1cde7e1
Merge branch 'master' into forum-tags
Lulalaby Oct 30, 2022
3b72f1d
Merge branch 'master' into forum-tags
Lulalaby Nov 3, 2022
8f9ee26
Update forum tags
Dorukyum Nov 7, 2022
b2483ad
Implement ForumChannel.get_tag
Dorukyum Nov 7, 2022
3fd72af
Merge branch 'master' into forum-tags
Lulalaby Nov 7, 2022
d1da9be
Add sort order, channel flags and total msg
Lulalaby Nov 7, 2022
0b1a082
Fix typehints
Dorukyum Nov 7, 2022
6ec5b7e
Update Thread.applied_tags
Dorukyum Nov 7, 2022
fb76350
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 7, 2022
ad7ea51
Merge branch 'master' into forum-tags
BobDotCom Nov 7, 2022
7f3a5c5
Merge branch 'master' into forum-tags
BobDotCom Nov 7, 2022
253c305
Merge branch 'master' into forum-tags
BobDotCom Nov 8, 2022
00084e2
Implement ForumChannel.requires_tag
Dorukyum Nov 9, 2022
e0acd90
Implement Thread.is_pinned
Dorukyum Nov 9, 2022
582e95c
Update `versionadded`s in docstring
Dorukyum Nov 9, 2022
3714af0
Merge branch 'master' into forum-tags
Lulalaby Nov 9, 2022
ee9c279
Merge branch 'master' into forum-tags
BobDotCom Nov 10, 2022
b47cf02
Merge branch 'master' into forum-tags
Lulalaby Nov 13, 2022
45265c1
Merge branch 'master' into forum-tags
BobDotCom Nov 13, 2022
2d2b993
Merge branch 'master' into forum-tags
Lulalaby Nov 14, 2022
1001c37
Update SortOrder to match API values
Dorukyum Nov 14, 2022
c4acddc
Implement default_sort_order
Dorukyum Nov 14, 2022
acb25b6
Implement default_thread_rate_limit_per_user
Dorukyum Nov 14, 2022
ee64f58
Add new fields to edit routes
Dorukyum Nov 14, 2022
1afd1b2
Rename default_thread_rate_limit_per_user
Dorukyum Nov 15, 2022
6b71d55
Seperate edit methods for text and forum channels
Dorukyum Nov 15, 2022
29db389
Map default_thread_slowmode_delay to valid field
Dorukyum Nov 15, 2022
dc2f63b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 15, 2022
608ce47
Merge branch 'master' into forum-tags
Lulalaby Nov 15, 2022
13ea4d8
Add require_tag field to ForumChannel.edit
Dorukyum Nov 15, 2022
0f17df4
Merge branch 'forum-tags' of https://github.com/Pycord-Development/py…
Dorukyum Nov 15, 2022
094477d
Parse available_tags to dicts in edit
Dorukyum Nov 15, 2022
15f92ae
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 15, 2022
c194e51
Merge branch 'master' into forum-tags
BobDotCom Nov 15, 2022
a603ae8
Merge branch 'master' into forum-tags
BobDotCom Nov 15, 2022
da1a58d
fix: NameError due to TYPE_CHECKING imports
Dorukyum Nov 15, 2022
51587bf
feat: applied_tags field in Thread.edit
Dorukyum Nov 15, 2022
3d0c099
Merge branch 'forum-tags' of https://github.com/Pycord-Development/py…
Dorukyum Nov 15, 2022
86d6656
Merge branch 'master' into forum-tags
Lulalaby Nov 16, 2022
70a9814
Merge branch 'master' into forum-tags
Lulalaby Nov 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
114 changes: 113 additions & 1 deletion discord/channel.py
Expand Up @@ -48,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
Expand All @@ -62,19 +63,21 @@
"GroupChannel",
"PartialMessageable",
"ForumChannel",
"ForumTag",
)

if TYPE_CHECKING:
from .abc import Snowflake, SnowflakeTime
from .guild import Guild
from .guild import GuildChannel as GuildChannelType
from .member import Member, VoiceState
from .message import Message, PartialMessage
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
Expand All @@ -100,6 +103,8 @@ class _TextChannel(discord.abc.GuildChannel, Hashable):
"_type",
"last_message_id",
"default_auto_archive_duration",
"default_thread_slowmode_delay",
"available_tags",
"flags",
)

Expand Down Expand Up @@ -142,6 +147,11 @@ 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"
)
Expand Down Expand Up @@ -225,6 +235,8 @@ async def edit(
category: CategoryChannel | None = ...,
slowmode_delay: int = ...,
default_auto_archive_duration: ThreadArchiveDuration = ...,
default_thread_slowmode_delay: int = ...,
available_tags: List[int] = ...,
type: ChannelType = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
) -> TextChannel | None:
Expand Down Expand Up @@ -281,6 +293,13 @@ 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.

.. versionadded:: 2.2

Returns
-------
Expand Down Expand Up @@ -821,6 +840,92 @@ 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"<ForumTag id={self.id} name={self.name!r} emoji={self.emoji!r} moderated={self.moderated}>"

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.

Expand Down Expand Up @@ -912,6 +1017,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: str | None = None,
Expand Down Expand Up @@ -955,6 +1061,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.
Expand Down Expand Up @@ -1019,6 +1127,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()")

Expand Down Expand Up @@ -1078,6 +1189,7 @@ async def create_thread(
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)
Expand Down
9 changes: 9 additions & 0 deletions discord/flags.py
Expand Up @@ -1424,3 +1424,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
9 changes: 9 additions & 0 deletions discord/http.py
Expand Up @@ -1035,6 +1035,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)
Expand Down Expand Up @@ -1149,6 +1154,7 @@ def start_forum_thread(
auto_archive_duration: threads.ThreadArchiveDuration,
rate_limit_per_user: int,
invitable: bool = True,
applied_tags: SnowflakeList | None,
Dorukyum marked this conversation as resolved.
Show resolved Hide resolved
reason: str | None = None,
embed: embed.Embed | None = None,
embeds: list[embed.Embed] | None = None,
Expand All @@ -1165,6 +1171,9 @@ def start_forum_thread(
if content:
payload["content"] = content

if applied_tags:
payload["applied_tags"] = applied_tags

if embed:
payload["embeds"] = [embed]

Expand Down
22 changes: 17 additions & 5 deletions discord/partial_emoji.py
Expand Up @@ -26,7 +26,7 @@
from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any, TypeVar
from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, Union

from . import utils
from .asset import Asset, AssetMixin
Expand Down Expand Up @@ -160,6 +160,17 @@ def to_dict(self) -> dict[str, Any]:
def _to_partial(self) -> PartialEmoji:
return self

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}

@classmethod
def with_state(
cls: type[PE],
Expand All @@ -174,11 +185,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"<a:{self.name}:{self.id}>"
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}>"
Expand Down
18 changes: 18 additions & 0 deletions discord/types/channel.py
Expand Up @@ -71,8 +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: Optional[Snowflake]
emoji_name: Optional[str]


class ForumChannel(_BaseGuildChannel, _TextChannelOptional):
type: Literal[15]
available_tags: List[ForumTag]
default_reaction_emoji: Optional[DefaultReaction]
flags: int
Lulalaby marked this conversation as resolved.
Show resolved Hide resolved


class NewsChannel(_BaseGuildChannel, _TextChannelOptional):
Expand Down Expand Up @@ -114,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):
Expand Down