diff --git a/interactions/__init__.py b/interactions/__init__.py index f5826d5b6..4ad18cd3f 100644 --- a/interactions/__init__.py +++ b/interactions/__init__.py @@ -113,6 +113,7 @@ CustomEmojiConverter, DateTrigger, DefaultNotificationLevel, + DefaultReaction, DM, dm_only, DMChannel, @@ -233,12 +234,14 @@ process_color, process_colour, process_components, + process_default_reaction, process_embeds, process_emoji, process_emoji_req_format, process_message_payload, process_message_reference, process_permission_overwrites, + process_thread_tag, Reaction, ReactionUsers, Resolved, @@ -425,6 +428,7 @@ "CustomEmojiConverter", "DateTrigger", "DefaultNotificationLevel", + "DefaultReaction", "DISCORD_EPOCH", "DM", "dm_only", @@ -564,12 +568,14 @@ "process_color", "process_colour", "process_components", + "process_default_reaction", "process_embeds", "process_emoji", "process_emoji_req_format", "process_message_payload", "process_message_reference", "process_permission_overwrites", + "process_thread_tag", "Reaction", "ReactionUsers", "Resolved", diff --git a/interactions/models/__init__.py b/interactions/models/__init__.py index 978877a20..a3e3543aa 100644 --- a/interactions/models/__init__.py +++ b/interactions/models/__init__.py @@ -44,6 +44,7 @@ ComponentType, CustomEmoji, DefaultNotificationLevel, + DefaultReaction, DM, DMChannel, DMGroup, @@ -117,12 +118,14 @@ process_color, process_colour, process_components, + process_default_reaction, process_embeds, process_emoji, process_emoji_req_format, process_message_payload, process_message_reference, process_permission_overwrites, + process_thread_tag, Reaction, ReactionUsers, Role, @@ -373,6 +376,7 @@ "CustomEmojiConverter", "DateTrigger", "DefaultNotificationLevel", + "DefaultReaction", "DM", "dm_only", "DMChannel", @@ -493,12 +497,14 @@ "process_color", "process_colour", "process_components", + "process_default_reaction", "process_embeds", "process_emoji", "process_emoji_req_format", "process_message_payload", "process_message_reference", "process_permission_overwrites", + "process_thread_tag", "Reaction", "ReactionUsers", "Resolved", diff --git a/interactions/models/discord/__init__.py b/interactions/models/discord/__init__.py index d684118a8..5adf044de 100644 --- a/interactions/models/discord/__init__.py +++ b/interactions/models/discord/__init__.py @@ -160,7 +160,7 @@ from .stage_instance import StageInstance from .sticker import Sticker, StickerItem, StickerPack from .team import Team, TeamMember -from .thread import ThreadList, ThreadMember, ThreadTag +from .thread import ThreadList, ThreadMember, ThreadTag, DefaultReaction, process_thread_tag, process_default_reaction from .timestamp import Timestamp, TimestampStyles from .user import BaseUser, Member, User, ClientUser from .voice_state import VoiceRegion, VoiceState @@ -211,6 +211,7 @@ "ComponentType", "CustomEmoji", "DefaultNotificationLevel", + "DefaultReaction", "DM", "DMChannel", "DMGroup", @@ -284,12 +285,14 @@ "process_color", "process_colour", "process_components", + "process_default_reaction", "process_embeds", "process_emoji", "process_emoji_req_format", "process_message_payload", "process_message_reference", "process_permission_overwrites", + "process_thread_tag", "Reaction", "ReactionUsers", "Role", diff --git a/interactions/models/discord/channel.py b/interactions/models/discord/channel.py index 7b03e5118..e2a7a2bd8 100644 --- a/interactions/models/discord/channel.py +++ b/interactions/models/discord/channel.py @@ -23,7 +23,7 @@ to_optional_snowflake, SnowflakeObject, ) -from interactions.models.discord.thread import ThreadTag +from interactions.models.discord.thread import DefaultReaction, ThreadTag from interactions.models.misc.context_manager import Typing from interactions.models.misc.iterator import AsyncIterator from .enums import ( @@ -2387,6 +2387,8 @@ async def close_stage(self, reason: Absent[Optional[str]] = MISSING) -> None: class GuildForum(GuildChannel): available_tags: List[ThreadTag] = attrs.field(repr=False, factory=list) """A list of tags available to assign to threads""" + default_reaction_emoji: Optional[DefaultReaction] = attrs.field(repr=False, default=None) + """The default emoji to react with for posts""" last_message_id: Optional[Snowflake_Type] = attrs.field(repr=False, default=None) # TODO: Implement "template" once the API supports them diff --git a/interactions/models/discord/guild.py b/interactions/models/discord/guild.py index 54208bfb0..72052cbe6 100644 --- a/interactions/models/discord/guild.py +++ b/interactions/models/discord/guild.py @@ -12,7 +12,7 @@ from interactions.client.const import Absent, MISSING, PREMIUM_GUILD_LIMITS from interactions.client.errors import EventLocationNotProvided, NotFound from interactions.client.mixins.serialization import DictSerializationMixin -from interactions.client.utils.attr_converters import optional +from interactions.client.utils.attr_converters import optional, list_converter from interactions.client.utils.attr_converters import timestamp_converter from interactions.client.utils.attr_utils import docs from interactions.client.utils.deserialise_app_cmds import deserialize_app_cmds @@ -1011,6 +1011,8 @@ async def create_forum_channel( category: Union[Snowflake_Type, "models.GuildCategory"] = None, nsfw: bool = False, rate_limit_per_user: int = 0, + default_reaction_emoji: Absent[Union[dict, "models.PartialEmoji", "models.DefaultReaction", str]] = MISSING, + available_tags: Absent["list[dict | models.ThreadTag] | dict | models.ThreadTag"] = MISSING, layout: ForumLayoutType = ForumLayoutType.NOT_SET, reason: Absent[Optional[str]] = MISSING, ) -> "models.GuildForum": @@ -1025,6 +1027,8 @@ async def create_forum_channel( category: The category this forum channel should be within nsfw: Should this forum be marked nsfw rate_limit_per_user: The time users must wait between sending messages + default_reaction_emoji: The default emoji to react with when creating a thread + available_tags: The available tags for this forum channel layout: The layout of the forum channel reason: The reason for creating this channel @@ -1041,6 +1045,8 @@ async def create_forum_channel( category=category, nsfw=nsfw, rate_limit_per_user=rate_limit_per_user, + default_reaction_emoji=models.process_default_reaction(default_reaction_emoji), + available_tags=list_converter(models.process_thread_tag)(available_tags) if available_tags else MISSING, default_forum_layout=layout, reason=reason, ) diff --git a/interactions/models/discord/thread.py b/interactions/models/discord/thread.py index 16512a1d3..191b6811b 100644 --- a/interactions/models/discord/thread.py +++ b/interactions/models/discord/thread.py @@ -4,10 +4,11 @@ import interactions.models as models from interactions.client.const import MISSING +from interactions.client.mixins.serialization import DictSerializationMixin from interactions.client.mixins.send import SendMixin from interactions.client.utils.attr_converters import optional from interactions.client.utils.attr_converters import timestamp_converter -from interactions.models.discord.emoji import PartialEmoji +from interactions.models.discord.emoji import PartialEmoji, process_emoji from interactions.models.discord.snowflake import to_snowflake from interactions.models.discord.timestamp import Timestamp from .base import DiscordObject, ClientObject @@ -25,6 +26,9 @@ "ThreadMember", "ThreadList", "ThreadTag", + "DefaultReaction", + "process_thread_tag", + "process_default_reaction", ) @@ -122,11 +126,46 @@ class ThreadTag(DiscordObject): name: str = attrs.field( repr=False, ) - emoji_id: "Snowflake_Type" = attrs.field(repr=False, default=None) + moderated: bool = attrs.field(repr=False) + emoji_id: "Snowflake_Type | None" = attrs.field(repr=False, default=None) emoji_name: str | None = attrs.field(repr=False, default=None) _parent_channel_id: "Snowflake_Type" = attrs.field(repr=False, default=MISSING) + @classmethod + def create( + cls, + name: str, + *, + moderated: bool = False, + emoji: Union["models.PartialEmoji", dict, str, None] = None, + ) -> "ThreadTag": + """ + Create a new thread tag - this is useful if you're making a new forum + + !!! warning + This does not create the tag on Discord, it only creates a local object + Do not expect the tag to contain valid values or for its methods to work + + Args: + name: The name for this tag + moderated: Whether this tag is moderated + emoji: The emoji for this tag + + Returns: + This object + """ + if emoji := models.process_emoji(emoji): + return cls( + client=None, + moderated=moderated, + id=0, + name=name, + emoji_id=emoji.get("id"), + emoji_name=emoji.get("name"), + ) + return cls(client=None, moderated=moderated, id=0, name=name) + @property def parent_channel(self) -> "GuildForum": """The parent forum for this tag.""" @@ -170,3 +209,66 @@ async def delete(self) -> None: """Delete this tag.""" data = await self._client.http.delete_tag(self._parent_channel_id, self.id) self._client.cache.place_channel_data(data) + + +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class DefaultReaction(DictSerializationMixin): + """Represents a default reaction for a forum.""" + + emoji_id: "Snowflake_Type | None" = attrs.field(default=None) + emoji_name: str | None = attrs.field(default=None) + + @classmethod + def from_emoji(cls, emoji: PartialEmoji) -> "DefaultReaction": + """Create a default reaction from an emoji.""" + if emoji.id: + return cls(emoji_id=emoji.id) + return cls(emoji_name=emoji.name) + + +def process_thread_tag(tag: Optional[dict | ThreadTag]) -> Optional[dict]: + """ + Processes the tag parameter into the dictionary format required by the API. + + Args: + tag: The tag to process + + Returns: + formatted dictionary for discrd + """ + if not tag: + return tag + + if isinstance(tag, ThreadTag): + return tag.to_dict() + + if isinstance(tag, dict): + return tag + + raise ValueError(f"Invalid tag: {tag}") + + +def process_default_reaction(reaction: Optional[dict | DefaultReaction | PartialEmoji | str]) -> Optional[dict]: + """ + Processes the reaction parameter into the dictionary format required by the API. + + Args: + reaction: The reaction to process. + + Returns: + formatted dictionary for discrd + """ + if not reaction: + return reaction + + if isinstance(reaction, dict): + return reaction + + if not isinstance(reaction, DefaultReaction): + emoji = process_emoji(reaction) + if emoji_id := emoji.get("id"): + reaction = DefaultReaction(emoji_id=emoji_id) + else: + reaction = DefaultReaction(emoji_name=emoji["name"]) + + return reaction.to_dict()