Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
15e40a8
Merge branch 'interactions-py:unstable' into unstable
FayeDel Apr 2, 2022
5f29455
Merge branch 'interactions-py:unstable' into unstable
FayeDel Apr 3, 2022
3583439
Merge branch 'interactions-py:unstable' into unstable
FayeDel Apr 4, 2022
870b1eb
Merge branch 'interactions-py:unstable' into unstable
FayeDel Apr 5, 2022
237b677
Merge branch 'interactions-py:unstable' into unstable
FayeDel Apr 9, 2022
11cdc5f
Merge branch 'interactions-py:unstable' into unstable
FayeDel Apr 28, 2022
d2ab67b
docs, feat: Implement barebones Forum channel support.
FayeDel Apr 28, 2022
2403dea
docs: Update channel attributes.
FayeDel Apr 28, 2022
3b168c2
feat: Implement barebones create thread in forum function.
FayeDel May 1, 2022
339fcad
feat: Implement tags support, implement creating post in Forums.
FayeDel May 5, 2022
fc81d73
fix: Include headers.
FayeDel May 5, 2022
d0a0702
chore: Rebase from unstable, convert to attrs
FayeDel Jun 8, 2022
098a8ef
chore: Remove redundant slots, update flag headers.
FayeDel Jun 8, 2022
2ef139c
chore: Update PR to latest unstable.
FayeDel Jun 23, 2022
b018285
chore: Update PR to latest unstable.
FayeDel Jul 11, 2022
212b31b
Merge branch 'unstable--upstream' into unstable-undoc-forums
FayeDel Jul 12, 2022
0d271c0
chore: Update to latest unstable commit.
FayeDel Sep 14, 2022
7dc7255
feat: Apply previous forum breaking change, document tags in http met…
FayeDel Sep 14, 2022
7bc9d85
feat: Implement forum tags object, document forum-specific attributes…
FayeDel Sep 14, 2022
4a034a4
fix: Fix tags attrs init typo.
FayeDel Sep 14, 2022
8b74309
chore: Remove uncommented __slots__ in ThreadMember
FayeDel Sep 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions interactions/api/http/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,72 @@ async def delete_stage_instance(self, channel_id: int, reason: Optional[str] = N
return await self._req.request(
Route("DELETE", f"/stage-instances/{channel_id}"), reason=reason
)

async def create_tag(
self,
channel_id: int,
name: str,
emoji_id: Optional[int] = None,
emoji_name: Optional[str] = None,
) -> dict:
"""
Create a new tag.

.. note::
Can either have an emoji_id or an emoji_name, but not both.
emoji_id is meant for custom emojis, emoji_name is meant for unicode emojis.

:param channel_id: Channel ID snowflake.
:param name: The name of the tag
:param emoji_id: The ID of the emoji to use for the tag
:param emoji_name: The name of the emoji to use for the tag
"""

_dct = {"name": name}
if emoji_id:
_dct["emoji_id"] = emoji_id
if emoji_name:
_dct["emoji_name"] = emoji_name

return await self._req.request(Route("POST", f"/channels/{channel_id}/tags"), json=_dct)

async def edit_tag(
self,
channel_id: int,
tag_id: int,
name: str,
emoji_id: Optional[int] = None,
emoji_name: Optional[str] = None,
) -> dict:
"""
Update a tag.

.. note::
Can either have an emoji_id or an emoji_name, but not both.
emoji_id is meant for custom emojis, emoji_name is meant for unicode emojis.

:param channel_id: Channel ID snowflake.
:param tag_id: The ID of the tag to update.
:param name: The new name of the tag
:param emoji_id: The ID of the emoji to use for the tag
:param emoji_name: The name of the emoji to use for the tag
"""

_dct = {"name": name}
if emoji_id:
_dct["emoji_id"] = emoji_id
if emoji_name:
_dct["emoji_name"] = emoji_name

return await self._req.request(
Route("PUT", f"/channels/{channel_id}/tags/{tag_id}"), json=_dct
)

async def delete_tag(self, channel_id: int, tag_id: int) -> dict:
"""
Delete a forum tag.

:param channel_id: Channel ID snowflake.
:param tag_id: The ID of the tag to delete
"""
return await self._req.request(Route("DELETE", f"/channels/{channel_id}/tags/{tag_id}"))
63 changes: 63 additions & 0 deletions interactions/api/http/thread.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from typing import Dict, List, Optional

from aiohttp import MultipartWriter

from ...api.cache import Cache
from ...utils.missing import MISSING
from ..models.channel import Channel
from ..models.misc import File
from .request import _Request
from .route import Route

Expand Down Expand Up @@ -189,3 +193,62 @@ async def create_thread(
self.cache[Channel].add(Channel(**request))

return request

async def create_thread_in_forum(
self,
channel_id: int,
name: str,
auto_archive_duration: int,
message_payload: dict,
applied_tags: List[str] = None,
files: Optional[List[File]] = MISSING,
rate_limit_per_user: Optional[int] = None,
reason: Optional[str] = None,
) -> dict:
"""
From a given Forum channel, create a Thread with a message to start with.

:param channel_id: The ID of the channel to create this thread in
:param name: The name of the thread
:param auto_archive_duration: duration in minutes to automatically archive the thread after recent activity,
can be set to: 60, 1440, 4320, 10080
:param message_payload: The payload/dictionary contents of the first message in the forum thread.
:param applied_tags: List of tag ids that can be applied to the forum, if any.
:param files: An optional list of files to send attached to the message.
:param rate_limit_per_user: Seconds a user has to wait before sending another message (0 to 21600), if given.
:param reason: An optional reason for the audit log
:return: Returns a Thread in a Forum object with a starting Message.
"""
query = {"use_nested_fields": 1}

payload = {"name": name, "auto_archive_duration": auto_archive_duration}
if rate_limit_per_user:
payload["rate_limit_per_user"] = rate_limit_per_user
if applied_tags:
payload["applied_tags"] = applied_tags

data = None
if files is not MISSING and len(files) > 0:

data = MultipartWriter("form-data")
part = data.append_json(payload)
part.set_content_disposition("form-data", name="payload_json")
payload = None

for id, file in enumerate(files):
part = data.append(
file._fp,
)
part.set_content_disposition(
"form-data", name=f"files[{str(id)}]", filename=file._filename
)
else:
payload.update(message_payload)

return await self._req.request(
Route("POST", f"/channels/{channel_id}/threads"),
json=payload,
data=data,
params=query,
reason=reason,
)
39 changes: 38 additions & 1 deletion interactions/api/models/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from ...utils.missing import MISSING
from ..error import LibraryException
from .emoji import Emoji
from .flags import Permissions
from .misc import AllowedMentions, File, IDMixin, Overwrite, Snowflake
from .user import User
Expand All @@ -36,6 +37,8 @@
"ThreadMember",
"ThreadMetadata",
"AsyncHistoryIterator",
"AsyncTypingContextManager",
"Tags",
)


Expand All @@ -53,6 +56,7 @@ class ChannelType(IntEnum):
PUBLIC_THREAD = 11
PRIVATE_THREAD = 12
GUILD_STAGE_VOICE = 13
GUILD_DIRECTORY = 14
GUILD_FORUM = 15


Expand Down Expand Up @@ -280,6 +284,30 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
self.__task.cancel()


@define()
class Tags(DictSerializerMixin):
"""
An object denoting a tag object within a forum channel.

.. note::
If the emoji is custom, it won't have name information.

:ivar str name: Name of the tag. The limit is up to 20 characters.
:ivar int id: ID of the tag. Can also be 0 if manually created.
:ivar bool moderated: A boolean denoting whether this tag can be removed/added by moderators with ``manage_threads`` permissions.
:ivar Optional[Emoji] emoji?: The emoji to represent the tag, if any.

"""

# TODO: Rename these to discord-docs
name: str = field()
id: int = field()
moderated: bool = field()
emoji: Optional[Emoji] = field(converter=Emoji, default=None)

# Maybe on post_attrs_init replace emoji object with one from cache for name population?


@define()
class Channel(ClientSerializerMixin, IDMixin):
"""
Expand Down Expand Up @@ -311,14 +339,20 @@ class Channel(ClientSerializerMixin, IDMixin):
:ivar Optional[int] video_quality_mode?: The set quality mode for video streaming in the channel.
:ivar int message_count: The amount of messages in the channel.
:ivar Optional[int] member_count?: The amount of members in the channel.
:ivar Optional[bool] newly_created?: Boolean representing if a thread is created.
:ivar Optional[ThreadMetadata] thread_metadata?: The thread metadata of the channel.
:ivar Optional[ThreadMember] member?: The member of the thread in the channel.
:ivar Optional[int] default_auto_archive_duration?: The set auto-archive time for all threads to naturally follow in the channel.
:ivar Optional[str] permissions?: The permissions of the channel.
:ivar Optional[int] flags?: The flags of the channel.
:ivar Optional[int] total_message_sent?: Number of messages ever sent in a thread.
:ivar Optional[int] default_thread_slowmode_delay?: The default slowmode delay in seconds for threads, if this channel is a forum.
:ivar Optional[List[Tags]] available_tags: Tags in a forum channel, if any.
:ivar Optional[Emoji] default_reaction_emoji: Default reaction emoji for threads created in a forum, if any.
"""

# Template attribute isn't live/documented, this line exists as a placeholder 'TODO' of sorts

__slots__ = (
# TODO: Document banner when Discord officially documents them.
"banner",
Expand Down Expand Up @@ -351,6 +385,7 @@ class Channel(ClientSerializerMixin, IDMixin):
video_quality_mode: Optional[int] = field(default=None, repr=False)
message_count: Optional[int] = field(default=None, repr=False)
member_count: Optional[int] = field(default=None, repr=False)
newly_created: Optional[int] = field(default=None, repr=False)
thread_metadata: Optional[ThreadMetadata] = field(converter=ThreadMetadata, default=None)
member: Optional[ThreadMember] = field(
converter=ThreadMember, default=None, add_client=True, repr=False
Expand All @@ -359,6 +394,9 @@ class Channel(ClientSerializerMixin, IDMixin):
permissions: Optional[str] = field(default=None, repr=False)
flags: Optional[int] = field(default=None, repr=False)
total_message_sent: Optional[int] = field(default=None, repr=False)
default_thread_slowmode_delay: Optional[int] = field(default=None, repr=False)
tags: Optional[List[Tags]] = field(converter=convert_list(Tags), default=None, repr=False)
default_reaction_emoji: Optional[Emoji] = field(converter=Emoji, default=None)

def __attrs_post_init__(self): # sourcery skip: last-if-guard
if self._client:
Expand Down Expand Up @@ -1505,7 +1543,6 @@ async def get_permissions_for(self, member: "Member") -> Permissions:
@define()
class Thread(Channel):
"""An object representing a thread.

.. note::
This is a derivation of the base Channel, since a
thread can be its own event.
Expand Down
4 changes: 4 additions & 0 deletions interactions/api/models/gw.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,10 @@ class MessageReactionRemove(MessageReaction):
# todo see if the missing member attribute affects anything


# Thread object typically used for ``THREAD_X`` is found in the channel models instead, as its identical.
# and all attributes of Thread are in Channel.


@define()
class ThreadList(DictSerializerMixin):
"""
Expand Down