diff --git a/interactions/api/models/guild.py b/interactions/api/models/guild.py index 608922f36..d1d5b2d82 100644 --- a/interactions/api/models/guild.py +++ b/interactions/api/models/guild.py @@ -654,8 +654,8 @@ async def create_thread( _invitable = None if invitable is MISSING else invitable _message_id = None if message_id is MISSING else message_id res = await self._client.create_thread( - channel_id=int(self.id), - thread_type=type.value, + channel_id=channel_id, + thread_type=type.value if not isinstance(type, int) else type, name=name, auto_archive_duration=_auto_archive_duration, invitable=_invitable, @@ -1480,6 +1480,58 @@ async def get_bans(self) -> List[dict]: ban["user"] = User(**ban["user"]) return res + async def get_emoji( + self, + emoji_id: int, + ) -> Emoji: + """ + Gets an emoji of the guild and returns it. + + :param emoji_id: The id of the emoji + :type emoji_id: int + :return: The specified Emoji, if found + :rtype: Emoji + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + + res = await self._client.get_guild_emoji(guild_id=int(self.id), emoji_id=emoji_id) + return Emoji(**res, _client=self._client) + + async def get_all_emojis(self) -> List[Emoji]: + """ + Gets all emojis of a guild. + + :return: All emojis of the guild + :rtype: List[Emoji] + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + res = await self._client.get_all_emoji(guild_id=int(self.id)) + return [Emoji(**emoji, _client=self._client) for emoji in res] + + async def delete_emoji( + self, + emoji: Union[Emoji, int], + reason: Optional[str] = None, + ) -> None: + """ + Deletes an emoji of the guild. + + :param emoji: The emoji or the id of the emoji to delete + :type emoji: Union[Emoji, int] + :param reason?: The reason of the deletion + :type reason?: Optional[str] + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + emoji_id = emoji.id if isinstance(emoji, Emoji) else emoji + return await self._client.delete_guild_emoji( + guild_id=int(self.id), + emoji_id=emoji_id, + reason=reason, + ) + class GuildPreview(DictSerializerMixin): """ @@ -1626,8 +1678,8 @@ def __init__(self, **kwargs): else None ) self.inviter = User(**self._json.get("inviter")) if self._json.get("inviter") else None - self.channel_id = int(self.channel_id) if self._json.get("channel_id") else None - self.guild_id = int(self.guild_id) if self._json.get("guild_id") else None + self.channel_id = Snowflake(self.channel_id) if self._json.get("channel_id") else None + self.guild_id = Snowflake(self.guild_id) if self._json.get("guild_id") else None self.target_user = ( User(**self._json.get("target_user")) if self._json.get("target_user") else None ) diff --git a/interactions/api/models/guild.pyi b/interactions/api/models/guild.pyi index 03d2b15da..b4bc97841 100644 --- a/interactions/api/models/guild.pyi +++ b/interactions/api/models/guild.pyi @@ -384,6 +384,16 @@ class Guild(DictSerializerMixin): reason: Optional[str] = None, ) -> List[Role]: ... async def get_bans(self) -> List[dict]: ... + async def get_emoji( + self, + emoji_id: int + ) -> Emoji: ... + async def get_all_emojis(self) -> List[Emoji]: ... + async def delete_emoji( + self, + emoji: Union[Emoji, int], + reason: Optional[str] = None, + ) -> None: ... class GuildPreview(DictSerializerMixin): _json: dict @@ -403,7 +413,7 @@ class Invite(DictSerializerMixin): _client: HTTPClient type: str guild_id: Snowflake - expires_at: str + expires_at: Optional[datetime] code: str channel_id: Snowflake uses: int diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index 2cdba00a4..459acf7a3 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -470,7 +470,6 @@ async def reply( if not components or components is MISSING: _components = [] - # TODO: Break this obfuscation pattern down to a "builder" method. else: _components = _build_components(components=components) @@ -552,6 +551,97 @@ async def create_thread( ) return Channel(**res, _client=self._client) + async def create_reaction( + self, + emoji: Union[str, "Emoji"], + ) -> None: + """ + Adds a reaction to the message. + + :param emoji: The Emoji as object or formatted as `name:id` + :type emoji: Union[str, Emoji] + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + + _emoji = ( + emoji if not isinstance(emoji, Emoji) else f":{emoji.name.replace(':', '')}:{emoji.id}" + ) + + return await self._client.create_reaction( + channel_id=int(self.channel_id), message_id=int(self.id), emoji=_emoji + ) + + async def remove_all_reactions(self) -> None: + """ + Removes all reactions of the message. + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + + return await self._client.remove_all_reactions( + channel_id=int(self.channel_id), message_id=int(self.id) + ) + + async def remove_all_reactions_of( + self, + emoji: Union[str, "Emoji"], + ) -> None: + """ + Removes all reactions of one emoji of the message. + + :param emoji: The Emoji as object or formatted as `name:id` + :type emoji: Union[str, Emoji] + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + + _emoji = ( + emoji if not isinstance(emoji, Emoji) else f":{emoji.name.replace(':', '')}:{emoji.id}" + ) + return await self._client.remove_all_reactions_of_emoji( + channel_id=int(self.channel_id), message_id=int(self.id), emoji=_emoji + ) + + async def remove_own_reaction_of( + self, + emoji: Union[str, "Emoji"], + ) -> None: + """ + Removes the own reaction of an emoji of the message. + + :param emoji: The Emoji as object or formatted as `name:id` + :type emoji: Union[str, Emoji] + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + + _emoji = ( + emoji if not isinstance(emoji, Emoji) else f"{emoji.name.replace(':', '')}:{emoji.id}" + ) + return await self._client.remove_self_reaction( + channel_id=int(self.channel_id), message_id=int(self.id), emoji=_emoji + ) + + async def remove_reaction_from( + self, emoji: Union[str, "Emoji"], user: Union[Member, User, int] + ) -> None: + """ + Removes another reaction of an emoji of the message. + + :param emoji: The Emoji as object or formatted as `name:id` + :type emoji: Union[str, Emoji] + :param user: The user or user_id to remove the reaction of + :type user: Union[Member, user, int] + """ + _emoji = ( + emoji if not isinstance(emoji, Emoji) else f":{emoji.name.replace(':', '')}:{emoji.id}" + ) + _user_id = user if isinstance(user, int) else user.id + return await self._client.remove_user_reaction( + channel_id=int(self.channel_id), message_id=int(self.id), user_id=_user_id, emoji=_emoji + ) + @classmethod async def get_from_url(cls, url: str, client: "HTTPClient") -> "Message": # noqa, """ @@ -590,6 +680,7 @@ class Emoji(DictSerializerMixin): """ __slots__ = ( + "_client", "_json", "id", "name", @@ -605,6 +696,66 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.id = Snowflake(self.id) if self._json.get("id") else None + @classmethod + async def get( + cls, + guild_id: int, + emoji_id: int, + client: "HTTPClient", # noqa + ) -> "Emoji": + """ + Gets an emoji. + + :param guild_id: The id of the guild of the emoji + :type guild_id: int + :param emoji_id: The id of the emoji + :type emoji_id: int + :param client: The HTTPClient of your bot. Equals to ``bot._http`` + :type client: HTTPClient + :return: The Emoji as object + :rtype: Emoji + """ + res = await client.get_guild_emoji(guild_id=guild_id, emoji_id=emoji_id) + return cls(**res, _client=client) + + @classmethod + async def get_all_of_guild( + cls, + guild_id: int, + client: "HTTPClient", # noqa + ) -> List["Emoji"]: + """ + Gets all emoji of a guild. + + :param guild_id: The id of the guild to get the emojis of + :type guild_id: int + :param client: The HTTPClient of your bot. Equals to ``bot._http`` + :type client: HTTPClient + :return: The Emoji as list + :rtype: List[Emoji] + """ + res = await client.get_all_emoji(guild_id=guild_id) + return [cls(**emoji, _client=client) for emoji in res] + + async def delete( + self, + guild_id: int, + reason: Optional[str] = None, + ) -> None: + """ + Deletes the emoji. + + :param guild_id: The guild id to delete the emoji from + :type guild_id: int + :param reason?: The reason of the deletion + :type reason?: Optional[str] + """ + if not self._client: + raise AttributeError("HTTPClient not found!") + return await self._client.delete_guild_emoji( + guild_id=guild_id, emoji_id=int(self.id), reason=reason + ) + class ReactionObject(DictSerializerMixin): """The reaction object. diff --git a/interactions/api/models/message.pyi b/interactions/api/models/message.pyi index eaa0845e6..84c2da295 100644 --- a/interactions/api/models/message.pyi +++ b/interactions/api/models/message.pyi @@ -145,6 +145,24 @@ class Message(DictSerializerMixin): invitable: Optional[bool] = MISSING, reason: Optional[str] = None, ) -> Channel: ... + async def create_reaction( + self, + emoji: Union[str, "Emoji"], + ) -> None: ... + async def remove_all_reactions(self) -> None: ... + async def remove_all_reactions_of( + self, + emoji: Union[str, "Emoji"], + ) -> None: ... + async def remove_own_reaction_of( + self, + emoji: Union[str, "Emoji"], + ) -> None: ... + async def remove_reaction_from( + self, + emoji: Union[str, "Emoji"], + user: Union[Member, User, int] + ) -> None: ... @classmethod async def get_from_url( cls, @@ -152,8 +170,8 @@ class Message(DictSerializerMixin): client: HTTPClient, ) -> "Message": ... - class Emoji(DictSerializerMixin): + _client: HTTPClient _json: dict id: Optional[Snowflake] name: Optional[str] @@ -164,6 +182,24 @@ class Emoji(DictSerializerMixin): animated: Optional[bool] available: Optional[bool] def __init__(self, **kwargs): ... + @classmethod + async def get( + cls, + guild_id: int, + emoji_id: int, + client: "HTTPClient", # noqa + ) -> "Emoji": ... + @classmethod + async def get_all_of_guild( + cls, + guild_id: int, + client: "HTTPClient", # noqa + ) -> List["Emoji"]: ... + async def delete( + self, + guild_id: int, + reason: Optional[str] = None, + ) -> None: ... class ReactionObject(DictSerializerMixin): _json: dict diff --git a/interactions/models/component.py b/interactions/models/component.py index 9bafe9547..86e9fd4bc 100644 --- a/interactions/models/component.py +++ b/interactions/models/component.py @@ -2,7 +2,7 @@ from ..api.error import InteractionException from ..api.models.message import Emoji -from ..api.models.misc import DictSerializerMixin +from ..api.models.misc import MISSING, DictSerializerMixin from ..enums import ButtonStyle, ComponentType, TextStyleType @@ -44,6 +44,18 @@ def __init__(self, **kwargs): if self.emoji: self._json.update({"emoji": self.emoji._json}) + def __setattr__(self, key, value) -> None: + super().__setattr__(key, value) + if key != "_json" and (key not in self._json or value != self._json.get(key)): + if value is not None and value is not MISSING: + try: + value = [val._json for val in value] if isinstance(value, list) else value._json + except AttributeError: + pass + self._json.update({key: value}) + elif value is None and key in self._json.keys(): + del self._json[key] + class SelectMenu(DictSerializerMixin): """ @@ -102,6 +114,18 @@ def __init__(self, **kwargs) -> None: self._json.update({"type": self.type.value}) self._json.update({"options": [option._json for option in self.options]}) + def __setattr__(self, key, value) -> None: + super().__setattr__(key, value) + if key != "_json" and (key not in self._json or value != self._json.get(key)): + if value is not None and value is not MISSING: + try: + value = [val._json for val in value] if isinstance(value, list) else value._json + except AttributeError: + pass + self._json.update({key: value}) + elif value is None and key in self._json.keys(): + del self._json[key] + class Button(DictSerializerMixin): """ @@ -143,6 +167,18 @@ def __init__(self, **kwargs) -> None: if self.emoji: self._json.update({"emoji": self.emoji._json}) + def __setattr__(self, key, value) -> None: + super().__setattr__(key, value) + if key != "_json" and (key not in self._json or value != self._json.get(key)): + if value is not None and value is not MISSING: + try: + value = [val._json for val in value] if isinstance(value, list) else value._json + except AttributeError: + pass + self._json.update({key: value}) + elif value is None and key in self._json.keys(): + del self._json[key] + class Component(DictSerializerMixin): """ @@ -283,6 +319,18 @@ def __init__(self, **kwargs): self.style = TextStyleType(self.style) self._json.update({"type": self.type.value, "style": self.style.value}) + def __setattr__(self, key, value) -> None: + super().__setattr__(key, value) + if key != "_json" and (key not in self._json or value != self._json.get(key)): + if value is not None and value is not MISSING: + try: + value = [val._json for val in value] if isinstance(value, list) else value._json + except AttributeError: + pass + self._json.update({key: value}) + elif value is None and key in self._json.keys(): + del self._json[key] + class Modal(DictSerializerMixin): """