From 2b79405d6afbcf406beadb571a7574b211125a84 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:51:26 +0100 Subject: [PATCH 01/12] fix!: fix EmbedImageStruct serialization --- interactions/api/models/message.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index 5c0bba76a..c1652b008 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -901,3 +901,12 @@ def __init__(self, **kwargs): if self.footer: self._json.update({"footer": self.footer._json}) + + if self.video: + self._json.update({"video": self.video._json}) + + if self.image: + self._json.update({"image": self.image._json}) + + if self.thumbnail: + self._json.update({"thumbnail": self.thumbnail._json}) From bf79b5d3ebc169e3fcbeedc831f57a4138b07af9 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Mon, 28 Feb 2022 11:34:37 +0100 Subject: [PATCH 02/12] fix!: Button emoji serialization --- interactions/models/component.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interactions/models/component.py b/interactions/models/component.py index 640635707..9bafe9547 100644 --- a/interactions/models/component.py +++ b/interactions/models/component.py @@ -140,6 +140,8 @@ def __init__(self, **kwargs) -> None: self.type = ComponentType.BUTTON self.style = ButtonStyle(self.style) self._json.update({"type": self.type.value, "style": self.style.value}) + if self.emoji: + self._json.update({"emoji": self.emoji._json}) class Component(DictSerializerMixin): From a429ffe0432e8380a7e8d783a00f4f36216845f7 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Mon, 28 Feb 2022 21:56:43 +0100 Subject: [PATCH 03/12] fix!: message serialization in context --- interactions/api/http.py | 2 +- interactions/context.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/interactions/api/http.py b/interactions/api/http.py index 015f1ece2..0dcfcf58f 100644 --- a/interactions/api/http.py +++ b/interactions/api/http.py @@ -2301,7 +2301,7 @@ async def delete_interaction_response( webhook_id=int(application_id), webhook_token=token, message_id=message_id ) - async def _post_followup(self, data: dict, token: str, application_id: str) -> None: + async def _post_followup(self, data: dict, token: str, application_id: str) -> dict: """ Send a followup to an interaction. diff --git a/interactions/context.py b/interactions/context.py index 47a06afc0..821706dea 100644 --- a/interactions/context.py +++ b/interactions/context.py @@ -453,13 +453,13 @@ async def send(self, content: Optional[str] = MISSING, **kwargs) -> Message: application_id=str(self.application_id), ) self.responded = True - self.message = msg = Message(**res, _client=self.client) else: - await self.client._post_followup( + res = await self.client._post_followup( data=payload._json, token=self.token, application_id=str(self.application_id), ) + self.message = msg = Message(**res, _client=self.client) else: await self.client.create_interaction_response( token=self.token, @@ -633,13 +633,14 @@ async def send(self, content: Optional[str] = MISSING, **kwargs) -> Message: application_id=str(self.application_id), ) self.responded = True - self.message = msg = Message(**res, _client=self.client) else: - await self.client._post_followup( + res = await self.client._post_followup( data=payload._json, token=self.token, application_id=str(self.application_id), ) + self.message = msg = Message(**res, _client=self.client) + else: await self.client.create_interaction_response( token=self.token, From 19ebbd3e0b148b41444c8f9e622c3640974c7bab Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Fri, 4 Mar 2022 08:31:52 +0100 Subject: [PATCH 04/12] Update guild.py --- interactions/api/models/guild.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interactions/api/models/guild.py b/interactions/api/models/guild.py index 608922f36..c5b6ca961 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, From 3db1295517d4106ff60d6d4a27456710c52d3194 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:13:50 +0100 Subject: [PATCH 05/12] Update message.py --- interactions/api/models/message.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index 2cdba00a4..168ea24e9 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -902,6 +902,15 @@ def __init__(self, **kwargs): if self.footer: self._json.update({"footer": self.footer._json}) + if self.thumbnail: + self._json.update({"thumbnail": self.thumbnail._json}) + + if self.image: + self._json.update({"image": self.image._json}) + + if self.video: + self._json.update({"video": self.video._json}) + def add_field(self, name: str, value: str, inline: Optional[bool] = False) -> None: """ Adds a field to the embed From 0661412a242f399038e4cf2a1b6a6816dbab2e37 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 6 Mar 2022 17:19:19 +0100 Subject: [PATCH 06/12] refactor: remove leftover slots --- interactions/api/models/message.py | 1 + interactions/api/models/message.pyi | 1 + 2 files changed, 2 insertions(+) diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index 168ea24e9..e3b319a69 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -590,6 +590,7 @@ class Emoji(DictSerializerMixin): """ __slots__ = ( + "_client", "_json", "id", "name", diff --git a/interactions/api/models/message.pyi b/interactions/api/models/message.pyi index eaa0845e6..2ef854b61 100644 --- a/interactions/api/models/message.pyi +++ b/interactions/api/models/message.pyi @@ -154,6 +154,7 @@ class Message(DictSerializerMixin): class Emoji(DictSerializerMixin): + _client: HTTPClient _json: dict id: Optional[Snowflake] name: Optional[str] From 9f277f93367e629b6a66f1fad507309bc0fd880c Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 6 Mar 2022 18:42:05 +0100 Subject: [PATCH 07/12] feat: implement Emoji helper methods --- interactions/api/models/guild.py | 56 +++++++++++++++++++++++++++++-- interactions/api/models/guild.pyi | 12 ++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/interactions/api/models/guild.py b/interactions/api/models/guild.py index c5b6ca961..d1d5b2d82 100644 --- a/interactions/api/models/guild.py +++ b/interactions/api/models/guild.py @@ -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 From ee302521cd8a59d4decfbc4d6538e92d5269a2c8 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 6 Mar 2022 19:22:28 +0100 Subject: [PATCH 08/12] feat: get for emoji --- interactions/api/models/message.py | 31 ++++++++++++++++++++--------- interactions/api/models/message.pyi | 7 +++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index e3b319a69..d12af370f 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -606,6 +606,28 @@ 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) + class ReactionObject(DictSerializerMixin): """The reaction object. @@ -903,15 +925,6 @@ def __init__(self, **kwargs): if self.footer: self._json.update({"footer": self.footer._json}) - if self.thumbnail: - self._json.update({"thumbnail": self.thumbnail._json}) - - if self.image: - self._json.update({"image": self.image._json}) - - if self.video: - self._json.update({"video": self.video._json}) - def add_field(self, name: str, value: str, inline: Optional[bool] = False) -> None: """ Adds a field to the embed diff --git a/interactions/api/models/message.pyi b/interactions/api/models/message.pyi index 2ef854b61..936b35326 100644 --- a/interactions/api/models/message.pyi +++ b/interactions/api/models/message.pyi @@ -165,6 +165,13 @@ 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": ... class ReactionObject(DictSerializerMixin): _json: dict From 3d9691290c6f4db052485dd9243306d9bae9e965 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 6 Mar 2022 21:19:52 +0100 Subject: [PATCH 09/12] feat: helper methods for emoji --- interactions/api/models/message.py | 38 +++++++++++++++++++++++++++++ interactions/api/models/message.pyi | 11 +++++++++ 2 files changed, 49 insertions(+) diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index d12af370f..4356f196b 100644 --- a/interactions/api/models/message.py +++ b/interactions/api/models/message.py @@ -628,6 +628,44 @@ async def get( 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 936b35326..4fbf25d9f 100644 --- a/interactions/api/models/message.pyi +++ b/interactions/api/models/message.pyi @@ -172,6 +172,17 @@ class Emoji(DictSerializerMixin): 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 From 4dfc898f704b7484e5020c41f4e3afb2eb3ed2a7 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Tue, 8 Mar 2022 19:16:03 +0100 Subject: [PATCH 10/12] feat: reaction methods --- interactions/api/models/message.py | 92 ++++++++++++++++++++++++++++- interactions/api/models/message.pyi | 18 ++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/interactions/api/models/message.py b/interactions/api/models/message.py index 4356f196b..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, """ diff --git a/interactions/api/models/message.pyi b/interactions/api/models/message.pyi index 4fbf25d9f..8c64040a2 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, From d4f349eed7886fa8384ab0538eba9f2719811dee Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Tue, 8 Mar 2022 19:17:35 +0100 Subject: [PATCH 11/12] feat: reaction methods --- interactions/api/models/message.pyi | 1 - 1 file changed, 1 deletion(-) diff --git a/interactions/api/models/message.pyi b/interactions/api/models/message.pyi index 8c64040a2..84c2da295 100644 --- a/interactions/api/models/message.pyi +++ b/interactions/api/models/message.pyi @@ -170,7 +170,6 @@ class Message(DictSerializerMixin): client: HTTPClient, ) -> "Message": ... - class Emoji(DictSerializerMixin): _client: HTTPClient _json: dict From 41b52822b56d12cfbb3f7fbc1b97b4026caf8322 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Tue, 8 Mar 2022 22:07:17 +0100 Subject: [PATCH 12/12] feat: reaction methods --- interactions/models/component.py | 50 +++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) 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): """