From 6ef3bc092d18da8437928c84b546705817e0503b Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sat, 10 Aug 2024 23:56:21 +0200 Subject: [PATCH 01/14] implement app emojis --- disnake/client.py | 199 +++++++++++++++++++++++++++++++++++++++++++++- disnake/emoji.py | 102 +++++++++++++++++++++++- disnake/http.py | 49 ++++++++++++ disnake/state.py | 19 ++++- 4 files changed, 366 insertions(+), 3 deletions(-) diff --git a/disnake/client.py b/disnake/client.py index 156720b161..15f061a4e0 100644 --- a/disnake/client.py +++ b/disnake/client.py @@ -46,7 +46,7 @@ from .application_role_connection import ApplicationRoleConnectionMetadata from .backoff import ExponentialBackoff from .channel import PartialMessageable, _threaded_channel_factory -from .emoji import Emoji +from .emoji import ApplicationEmoji, Emoji from .entitlement import Entitlement from .enums import ApplicationCommandType, ChannelType, Event, Status from .errors import ( @@ -401,6 +401,7 @@ def __init__( intents: Optional[Intents] = None, chunk_guilds_at_startup: Optional[bool] = None, member_cache_flags: Optional[MemberCacheFlags] = None, + cache_app_emojis_at_startup: bool = False, ) -> None: # self.ws is set in the connect method self.ws: DiscordWebSocket = None # type: ignore @@ -444,6 +445,7 @@ def __init__( intents=intents, chunk_guilds_at_startup=chunk_guilds_at_startup, member_cache_flags=member_cache_flags, + cache_app_emojis_at_startup=cache_app_emojis_at_startup, ) self.shard_id: Optional[int] = shard_id self.shard_count: Optional[int] = shard_count @@ -498,6 +500,7 @@ def _get_state( intents: Optional[Intents], chunk_guilds_at_startup: Optional[bool], member_cache_flags: Optional[MemberCacheFlags], + cache_app_emojis_at_startup: bool, ) -> ConnectionState: return ConnectionState( dispatch=self.dispatch, @@ -515,6 +518,7 @@ def _get_state( intents=intents, chunk_guilds_at_startup=chunk_guilds_at_startup, member_cache_flags=member_cache_flags, + cache_app_emojis_at_startup=cache_app_emojis_at_startup, ) def _handle_ready(self) -> None: @@ -563,6 +567,11 @@ def emojis(self) -> List[Emoji]: """List[:class:`.Emoji`]: The emojis that the connected client has.""" return self._connection.emojis + @property + def application_emojis(self) -> List[ApplicationEmoji]: + """List[:class:`.ApplicationEmoji`]: The application emojis that the connected client has.""" + return self._connection.application_emojis + @property def stickers(self) -> List[GuildSticker]: """List[:class:`.GuildSticker`]: The stickers that the connected client has. @@ -711,6 +720,58 @@ async def get_or_fetch_user(self, user_id: int, *, strict: bool = False) -> Opti getch_user = get_or_fetch_user + @overload + async def get_or_fetch_application_emoji( + self, emoji_id: int, *, strict: Literal[False] = ... + ) -> Optional[ApplicationEmoji]: + ... + + @overload + async def get_or_fetch_application_emoji( + self, emoji_id: int, *, strict: Literal[True] = ... + ) -> ApplicationEmoji: + ... + + async def get_or_fetch_application_emoji( + self, emoji_id: int, *, strict: bool = False + ) -> Optional[ApplicationEmoji]: + """|coro| + + Tries to get the application emoji from the cache. If it fails, + fetches the app emoji from the API. + + This only propagates exceptions when the ``strict`` parameter is enabled. + + .. versionadded:: 2.10 + + Parameters + ---------- + emoji_id: :class:`int` + The ID to search for. + strict: :class:`bool` + Whether to propagate exceptions from :func:`fetch_application_emoji` + instead of returning ``None`` in case of failure + (e.g. if the app emoji wasn't found). + Defaults to ``False``. + + Returns + ------- + Optional[:class:`~disnake.ApplicationEmoji`] + The app emoji with the given ID, or ``None`` if not found and ``strict`` is set to ``False``. + """ + app_emoji = self.get_application_emoji(emoji_id) + if app_emoji is not None: + return app_emoji + try: + app_emoji = await self.fetch_application_emoji(emoji_id) + except Exception: + if strict: + raise + return None + return app_emoji + + getch_application_emoji = get_or_fetch_application_emoji + def is_ready(self) -> bool: """Whether the client's internal cache is ready for use. @@ -1489,6 +1550,28 @@ def get_emoji(self, id: int, /) -> Optional[Emoji]: """ return self._connection.get_emoji(id) + def get_application_emoji(self, emoji_id: int, /) -> Optional[ApplicationEmoji]: + """Returns an application emoji with the given ID. + + .. versionadded:: 2.10 + + .. note:: + + If this returns ``None`` consider executing :meth:`fetch_application_emoji` + or enable :attr:`disnake.Client.cache_app_emoji`. + + Parameters + ---------- + emoji_id: :class:`int` + The ID to search for. + + Returns + ------- + Optional[:class:`ApplicationEmoji`] + The returned application emoji or ``None`` if not found. + """ + return self._connection.get_application_emoji(emoji_id) + def get_sticker(self, id: int, /) -> Optional[GuildSticker]: """Returns a guild sticker with the given ID. @@ -2372,6 +2455,120 @@ async def application_info(self) -> AppInfo: data["rpc_origins"] = None return AppInfo(self._connection, data) + async def fetch_application_emoji( + self, emoji_id: int, /, cache: bool = False + ) -> ApplicationEmoji: + """|coro| + + Retrieves an application level :class:`~disnake.ApplicationEmoji` based on its ID. + + .. note:: + + This method is an API call. If you have :attr:`disnake.Client.cache_application_emojis` enabled, consider :meth:`get_application_emoji` instead. + + .. versionadded:: 2.10 + + Parameters + ---------- + emoji_id: :class:`int` + The ID of the emoji to retrieve. + cache: :class:`bool` + Whether to update the cache. + + Raises + ------ + NotFound + The app emoji couldn't be found. + Forbidden + You are not allowed to get the app emoji. + + Returns + ------- + :class:`ApplicationEmoji` + The application emoji you requested. + """ + data = await self.http.get_app_emoji(self.application_id, emoji_id) + + if cache: + return self._connection.store_application_emoji(data=data) + return ApplicationEmoji(app_id=self.application_id, state=self._connection, data=data) + + async def create_application_emoji(self, *, name: str, image: AssetBytes) -> ApplicationEmoji: + """|coro| + + Creates an application emoji. + + .. versionadded:: 2.10 + + Parameters + ---------- + name: :class:`str` + The name of the new string. + image: |resource_type| + The image data of the emoji. + Only JPG, PNG and GIF images are supported. + + Raises + ------ + NotFound + The ``image`` asset couldn't be found. + Forbidden + You are not allowed to create app emojis. + HTTPException + An error occurred creating an app emoji. + TypeError + The ``image`` asset is a lottie sticker (see :func:`Sticker.read`). + ValueError + Wrong image format passed for ``image``. + + Returns + ------- + :class:`ApplicationEmoji` + The newly created application emoji. + """ + img = await utils._assetbytes_to_base64_data(image) + data = await self.http.create_app_emoji(self.application_id, name, img) + return self._connection.store_application_emoji(data) + + async def fetch_application_emojis(self, *, cache: bool = False) -> List[ApplicationEmoji]: + """|coro| + + Retrieves all the :class:`ApplicationEmoji` of the application. + + .. versionadded:: 2.10 + + + Parameters + ---------- + cache: :class:`bool` + Whether to update the cache. + + Raises + ------ + NotFound + The app emojis for this application ID couldn't be found. + Forbidden + You are not allowed to get app emojis. + + Returns + ------- + List[:class:`ApplicationEmoji`] + The list of application emojis you requested. + """ + data = await self.http.get_all_app_emojis(self.application_id) + + if cache: + app_emojis = [] + for emoji_data in data: + app_emojis.append(self._connection.store_application_emoji(emoji_data)) + + return app_emojis + + return [ + ApplicationEmoji(app_id=self.application_id, state=self._connection, data=emoji_data) + for emoji_data in data + ] + async def fetch_user(self, user_id: int, /) -> User: """|coro| diff --git a/disnake/emoji.py b/disnake/emoji.py index badedbce86..02cd481724 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -9,7 +9,7 @@ from .user import User from .utils import MISSING, SnowflakeList, snowflake_time -__all__ = ("Emoji",) +__all__ = ("Emoji", "ApplicationEmoji", "AppEmoji") if TYPE_CHECKING: from datetime import datetime @@ -246,3 +246,103 @@ async def edit( self.guild.id, self.id, payload=payload, reason=reason ) return Emoji(guild=self.guild, data=data, state=self._state) + + +class ApplicationEmoji(Emoji): + """Represents an application custom emoji. + + .. versionadded:: 2.10 + + .. collapse:: operations + + .. describe:: x == y + + Checks if two emoji are the same. + + .. describe:: x != y + + Checks if two emoji are not the same. + + .. describe:: hash(x) + + Return the emoji's hash. + + .. describe:: iter(x) + + Returns an iterator of ``(field, value)`` pairs. This allows this class + to be used as an iterable in list/dict/etc constructions. + + .. describe:: str(x) + + Returns the emoji rendered for Discord. + + Attributes + ---------- + name: :class:`str` + The emoji's name. + id: :class:`int` + The emoji's ID. + application_id: :class:`int` + The apllication ID this emoji belongs to. + require_colons: :class:`bool` + Whether colons are required to use this emoji in the client (:PJSalt: vs PJSalt). + animated: :class:`bool` + Whether the emoji is animated or not. + managed: :class:`bool` + Whether the emoji is managed by a Twitch integration. + available: :class:`bool` + Whether the emoji is available for use. + user: :class:`User` + The user that created this emoji. + """ + + def __init__(self, *, app_id: int, state: ConnectionState, data: EmojiPayload) -> None: + self.guild_id: Optional[int] = None + self.application_id: int = app_id + self._state: ConnectionState = state + self._from_data(data) + + def __repr__(self) -> str: + return f"" + + async def edit(self, *, name: str) -> ApplicationEmoji: + """|coro| + + Edits the application custom emoji. + + Parameters + ---------- + name: :class:`str` + The new emoji name. + + Raises + ------ + Forbidden + You are not allowed to edit this emoji. + HTTPException + An error occurred editing the emoji. + + Returns + ------- + :class:`ApplicationEmoji` + The newly updated emoji. + """ + data = await self._state.http.edit_app_emoji(self.application_id, self.id, name) + return ApplicationEmoji(app_id=self.application_id, state=self._state, data=data) + + async def delete(self) -> None: + """|coro| + + Deletes the application custom emoji. + + Raises + ------ + Forbidden + You are not allowed to delete this emoji. + HTTPException + An error occurred deleting the emoji. + """ + await self._state.http.delete_app_emoji(self.application_id, self.id) + + +AppEmoji = ApplicationEmoji diff --git a/disnake/http.py b/disnake/http.py index f10cd3fdd8..1eff3824cf 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -1656,6 +1656,16 @@ def delete_guild_sticker( reason=reason, ) + def get_all_app_emojis(self, app_id: Snowflake) -> Response[List[emoji.Emoji]]: + return self.request(Route("GET", "/applications/{app_id}/emojis", app_id=app_id)) + + def get_app_emoji(self, app_id: Snowflake, emoji_id: Snowflake) -> Response[emoji.Emoji]: + return self.request( + Route( + "GET", "/applications/{app_id}/emojis/{emoji_id}", app_id=app_id, emoji_id=emoji_id + ) + ) + def get_all_custom_emojis(self, guild_id: Snowflake) -> Response[List[emoji.Emoji]]: return self.request(Route("GET", "/guilds/{guild_id}/emojis", guild_id=guild_id)) @@ -1666,6 +1676,45 @@ def get_custom_emoji(self, guild_id: Snowflake, emoji_id: Snowflake) -> Response ) ) + def create_app_emoji( + self, + app_id: Snowflake, + name: str, + image: str, + ) -> Response[emoji.Emoji]: + payload: Dict[str, Any] = { + "name": name, + "image": image, + } + + r = Route("POST", "/applications/{app_id}/emojis", app_id=app_id) + return self.request(r, json=payload) + + def edit_app_emoji( + self, + app_id: Snowflake, + emoji_id: Snowflake, + name: str, + ) -> Response[emoji.Emoji]: + payload: Dict[str, Any] = { + "name": name, + } + + r = Route( + "PATCH", "/applications/{app_id}/emojis/{emoji_id}", app_id=app_id, emoji_id=emoji_id + ) + return self.request(r, json=payload) + + def delete_app_emoji(self, app_id: Snowflake, emoji_id: Snowflake) -> Response[None]: + return self.request( + Route( + "DELETE", + "/applications/{app_id}/emojis/{emoji_id}", + app_id=app_id, + emoji_id=emoji_id, + ) + ) + def create_custom_emoji( self, guild_id: Snowflake, diff --git a/disnake/state.py b/disnake/state.py index f4885513d7..9fe946ba9c 100644 --- a/disnake/state.py +++ b/disnake/state.py @@ -44,7 +44,7 @@ VoiceChannel, _guild_channel_factory, ) -from .emoji import Emoji +from .emoji import ApplicationEmoji, Emoji from .entitlement import Entitlement from .enums import ApplicationCommandType, ChannelType, ComponentType, MessageType, Status, try_enum from .flags import ApplicationFlags, Intents, MemberCacheFlags @@ -204,6 +204,7 @@ def __init__( intents: Optional[Intents] = None, chunk_guilds_at_startup: Optional[bool] = None, member_cache_flags: Optional[MemberCacheFlags] = None, + cache_app_emojis_at_startup: bool = False, ) -> None: self.loop: asyncio.AbstractEventLoop = loop self.http: HTTPClient = http @@ -282,6 +283,8 @@ def __init__( if attr.startswith("parse_"): parsers[attr[6:].upper()] = func + self.cache_app_emojis_at_startup = cache_app_emojis_at_startup + self.clear() def clear( @@ -294,6 +297,7 @@ def clear( # - accesses on `_users` are slower, e.g. `__getitem__` takes ~1us with weakrefs and ~0.2us without self._users: weakref.WeakValueDictionary[int, User] = weakref.WeakValueDictionary() self._emojis: Dict[int, Emoji] = {} + self._application_emojis: Dict[int, ApplicationEmoji] = {} self._stickers: Dict[int, GuildSticker] = {} self._guilds: Dict[int, Guild] = {} @@ -400,6 +404,11 @@ def store_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji: self._emojis[emoji_id] = emoji = Emoji(guild=guild, state=self, data=data) return emoji + def store_application_emoji(self, data: EmojiPayload) -> ApplicationEmoji: + emoji_id = int(data["id"]) # type: ignore + self._application_emojis[emoji_id] = emoji = ApplicationEmoji(app_id=self.application_id, state=self, data=data) # type: ignore + return emoji + def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: sticker_id = int(data["id"]) self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data) @@ -511,6 +520,10 @@ def _get_guild_command_named( def emojis(self) -> List[Emoji]: return list(self._emojis.values()) + @property + def application_emojis(self) -> List[ApplicationEmoji]: + return list(self._application_emojis.values()) + @property def stickers(self) -> List[GuildSticker]: return list(self._stickers.values()) @@ -519,6 +532,10 @@ def get_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]: # the keys of self._emojis are ints return self._emojis.get(emoji_id) # type: ignore + def get_application_emoji(self, emoji_id: Optional[int]) -> Optional[ApplicationEmoji]: + # the keys of self._application_emojis are ints + return self._application_emojis.get(emoji_id) # type: ignore + def get_sticker(self, sticker_id: Optional[int]) -> Optional[GuildSticker]: # the keys of self._stickers are ints return self._stickers.get(sticker_id) # type: ignore From cae204a498580d2fda93f82e576d3415ee650c53 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 11 Aug 2024 00:03:26 +0200 Subject: [PATCH 02/14] add changelog file --- changelog/1223.feature.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/1223.feature.rst diff --git a/changelog/1223.feature.rst b/changelog/1223.feature.rst new file mode 100644 index 0000000000..0570970249 --- /dev/null +++ b/changelog/1223.feature.rst @@ -0,0 +1,2 @@ +Add new :class:`.ApplicationEmoji` representing application emojis. +Add new methods and properties on :class:`Client` to get, fetch and create application emojis: :meth:`Client.get_application_emoji`, :meth:`Client.fetch_application_emoji`, :meth:`Client.get_or_fetch_application_emoji`, :meth:`Client.getch_application_emoji`, :meth:`Client.fetch_application_emojis`, :attr:`Client.application_emojis` and :meth:`Client.create_application_emoji`. From ca003912f5ca8cdb8ff50d1da8e80e80e40ce660 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 11 Aug 2024 00:04:29 +0200 Subject: [PATCH 03/14] update docs --- docs/api/emoji.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api/emoji.rst b/docs/api/emoji.rst index 3b4756d85b..d9301cb45f 100644 --- a/docs/api/emoji.rst +++ b/docs/api/emoji.rst @@ -20,6 +20,15 @@ Emoji :members: :inherited-members: +ApplicationEmoji +~~~~~~~~~~~~~~~~ + +.. attributable:: ApplicationEmoji + +.. autoclass:: ApplicationEmoji() + :members: + :inherited-members: + Data Classes ------------- From 710c40fd8fb54015d3efc2458de302f27da0b7c2 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:55:39 +0200 Subject: [PATCH 04/14] edit Emoji to represent app emojis --- changelog/1223.feature.rst | 2 +- disnake/client.py | 46 +++++----- disnake/emoji.py | 168 +++++++++++++------------------------ disnake/state.py | 12 +-- docs/api/emoji.rst | 9 -- 5 files changed, 85 insertions(+), 152 deletions(-) diff --git a/changelog/1223.feature.rst b/changelog/1223.feature.rst index 0570970249..188ea62452 100644 --- a/changelog/1223.feature.rst +++ b/changelog/1223.feature.rst @@ -1,2 +1,2 @@ -Add new :class:`.ApplicationEmoji` representing application emojis. +Edit :class:`.Emoji` to represent application emojis. Add new methods and properties on :class:`Client` to get, fetch and create application emojis: :meth:`Client.get_application_emoji`, :meth:`Client.fetch_application_emoji`, :meth:`Client.get_or_fetch_application_emoji`, :meth:`Client.getch_application_emoji`, :meth:`Client.fetch_application_emojis`, :attr:`Client.application_emojis` and :meth:`Client.create_application_emoji`. diff --git a/disnake/client.py b/disnake/client.py index 229a86be26..b13c52cf42 100644 --- a/disnake/client.py +++ b/disnake/client.py @@ -46,7 +46,7 @@ from .application_role_connection import ApplicationRoleConnectionMetadata from .backoff import ExponentialBackoff from .channel import PartialMessageable, _threaded_channel_factory -from .emoji import ApplicationEmoji, Emoji +from .emoji import Emoji from .entitlement import Entitlement from .enums import ApplicationCommandType, ChannelType, Event, Status from .errors import ( @@ -568,8 +568,11 @@ def emojis(self) -> List[Emoji]: return self._connection.emojis @property - def application_emojis(self) -> List[ApplicationEmoji]: - """List[:class:`.ApplicationEmoji`]: The application emojis that the connected client has.""" + def application_emojis(self) -> List[Emoji]: + """List[:class:`.ApplicationEmoji`]: The application emojis that the connected client has. + + .. versionadded:: 2.10 + """ return self._connection.application_emojis @property @@ -723,18 +726,18 @@ async def get_or_fetch_user(self, user_id: int, *, strict: bool = False) -> Opti @overload async def get_or_fetch_application_emoji( self, emoji_id: int, *, strict: Literal[False] = ... - ) -> Optional[ApplicationEmoji]: + ) -> Optional[Emoji]: ... @overload async def get_or_fetch_application_emoji( self, emoji_id: int, *, strict: Literal[True] = ... - ) -> ApplicationEmoji: + ) -> Emoji: ... async def get_or_fetch_application_emoji( self, emoji_id: int, *, strict: bool = False - ) -> Optional[ApplicationEmoji]: + ) -> Optional[Emoji]: """|coro| Tries to get the application emoji from the cache. If it fails, @@ -756,7 +759,7 @@ async def get_or_fetch_application_emoji( Returns ------- - Optional[:class:`~disnake.ApplicationEmoji`] + Optional[:class:`~disnake.Emoji`] The app emoji with the given ID, or ``None`` if not found and ``strict`` is set to ``False``. """ app_emoji = self.get_application_emoji(emoji_id) @@ -1550,7 +1553,7 @@ def get_emoji(self, id: int, /) -> Optional[Emoji]: """ return self._connection.get_emoji(id) - def get_application_emoji(self, emoji_id: int, /) -> Optional[ApplicationEmoji]: + def get_application_emoji(self, emoji_id: int, /) -> Optional[Emoji]: """Returns an application emoji with the given ID. .. versionadded:: 2.10 @@ -1567,7 +1570,7 @@ def get_application_emoji(self, emoji_id: int, /) -> Optional[ApplicationEmoji]: Returns ------- - Optional[:class:`ApplicationEmoji`] + Optional[:class:`Emoji`] The returned application emoji or ``None`` if not found. """ return self._connection.get_application_emoji(emoji_id) @@ -2455,12 +2458,10 @@ async def application_info(self) -> AppInfo: data["rpc_origins"] = None return AppInfo(self._connection, data) - async def fetch_application_emoji( - self, emoji_id: int, /, cache: bool = False - ) -> ApplicationEmoji: + async def fetch_application_emoji(self, emoji_id: int, /, cache: bool = False) -> Emoji: """|coro| - Retrieves an application level :class:`~disnake.ApplicationEmoji` based on its ID. + Retrieves an application level :class:`~disnake.Emoji` based on its ID. .. note:: @@ -2484,16 +2485,16 @@ async def fetch_application_emoji( Returns ------- - :class:`ApplicationEmoji` + :class:`Emoji` The application emoji you requested. """ data = await self.http.get_app_emoji(self.application_id, emoji_id) if cache: return self._connection.store_application_emoji(data=data) - return ApplicationEmoji(app_id=self.application_id, state=self._connection, data=data) + return Emoji(guild=None, state=self._connection, data=data) - async def create_application_emoji(self, *, name: str, image: AssetBytes) -> ApplicationEmoji: + async def create_application_emoji(self, *, name: str, image: AssetBytes) -> Emoji: """|coro| Creates an application emoji. @@ -2523,17 +2524,17 @@ async def create_application_emoji(self, *, name: str, image: AssetBytes) -> App Returns ------- - :class:`ApplicationEmoji` + :class:`Emoji` The newly created application emoji. """ img = await utils._assetbytes_to_base64_data(image) data = await self.http.create_app_emoji(self.application_id, name, img) return self._connection.store_application_emoji(data) - async def fetch_application_emojis(self, *, cache: bool = False) -> List[ApplicationEmoji]: + async def fetch_application_emojis(self, *, cache: bool = False) -> List[Emoji]: """|coro| - Retrieves all the :class:`ApplicationEmoji` of the application. + Retrieves all the :class:`Emoji` of the application. .. versionadded:: 2.10 @@ -2552,7 +2553,7 @@ async def fetch_application_emojis(self, *, cache: bool = False) -> List[Applica Returns ------- - List[:class:`ApplicationEmoji`] + List[:class:`Emoji`] The list of application emojis you requested. """ data = await self.http.get_all_app_emojis(self.application_id) @@ -2564,10 +2565,7 @@ async def fetch_application_emojis(self, *, cache: bool = False) -> List[Applica return app_emojis - return [ - ApplicationEmoji(app_id=self.application_id, state=self._connection, data=emoji_data) - for emoji_data in data - ] + return [Emoji(guild=None, state=self._connection, data=emoji_data) for emoji_data in data] async def fetch_user(self, user_id: int, /) -> User: """|coro| diff --git a/disnake/emoji.py b/disnake/emoji.py index 02cd481724..db8f6462cf 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -9,7 +9,7 @@ from .user import User from .utils import MISSING, SnowflakeList, snowflake_time -__all__ = ("Emoji", "ApplicationEmoji", "AppEmoji") +__all__ = ("Emoji",) if TYPE_CHECKING: from datetime import datetime @@ -51,6 +51,10 @@ class Emoji(_EmojiTag, AssetMixin): Returns the emoji rendered for Discord. + .. versionchanched:: 2.10 + + This class can now represents app emojis too. Denoted by having :attr:`.Emoji.guild_id` as ``None``. + Attributes ---------- name: :class:`str` @@ -63,8 +67,8 @@ class Emoji(_EmojiTag, AssetMixin): Whether the emoji is animated or not. managed: :class:`bool` Whether the emoji is managed by a Twitch integration. - guild_id: :class:`int` - The guild ID the emoji belongs to. + guild_id: Optional[:class:`int`] + The guild ID the emoji belongs to. ``None`` if this is an app emoji. available: :class:`bool` Whether the emoji is available for use. user: Optional[:class:`User`] @@ -81,14 +85,20 @@ class Emoji(_EmojiTag, AssetMixin): "name", "_roles", "guild_id", + "_application_id", "user", "available", ) def __init__( - self, *, guild: Union[Guild, GuildPreview], state: ConnectionState, data: EmojiPayload + self, + *, + guild: Optional[Union[Guild, GuildPreview]], + state: ConnectionState, + data: EmojiPayload, ) -> None: - self.guild_id: int = guild.id + self.guild_id: Optional[int] = guild.id if guild else None + self._application_id: Optional[int] = state.application_id self._state: ConnectionState = state self._from_data(data) @@ -151,16 +161,34 @@ def roles(self) -> List[Role]: and count towards a separate limit of 25 emojis. """ guild = self.guild - if guild is None: # pyright: ignore[reportUnnecessaryComparison] + if guild is None: return [] return [role for role in guild.roles if self._roles.has(role.id)] @property - def guild(self) -> Guild: - """:class:`Guild`: The guild this emoji belongs to.""" + def guild(self) -> Optional[Guild]: + """Optional[:class:`Guild`]: The guild this emoji belongs to. + + ``None`` if this is an app emoji. + + .. versionchanged:: 2.10 + + This can now return ``None`` if the emoji is an + application owned emoji. + """ # this will most likely never return None but there's a possibility - return self._state._get_guild(self.guild_id) # type: ignore + return self._state._get_guild(self.guild_id) + + @property + def application_id(self) -> Optional[int]: + """:class:`int`: The ID of the application which owns this emoji. + + .. versionadded:: 2.10 + """ + if self.guild is None: + return + return self._application_id def is_usable(self) -> bool: """Whether the bot can use this emoji. @@ -173,6 +201,8 @@ def is_usable(self) -> bool: return False if not self._roles: return True + if not self.guild: + return self.available emoji_roles, my_roles = self._roles, self.guild.me._roles return any(my_roles.has(role_id) for role_id in emoji_roles) @@ -196,6 +226,13 @@ async def delete(self, *, reason: Optional[str] = None) -> None: HTTPException An error occurred deleting the emoji. """ + # this is an app emoji + if self.guild is None: + if self.application_id is None: + # should never happen + raise ValueError("Idk message about invalid state?! Pls catch this when reviewing") + + return await self._state.http.delete_app_emoji(self.application_id, self.id) await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason) async def edit( @@ -242,107 +279,14 @@ async def edit( if roles is not MISSING: payload["roles"] = [role.id for role in roles] - data = await self._state.http.edit_custom_emoji( - self.guild.id, self.id, payload=payload, reason=reason - ) - return Emoji(guild=self.guild, data=data, state=self._state) - - -class ApplicationEmoji(Emoji): - """Represents an application custom emoji. - - .. versionadded:: 2.10 - - .. collapse:: operations - - .. describe:: x == y - - Checks if two emoji are the same. - - .. describe:: x != y - - Checks if two emoji are not the same. - - .. describe:: hash(x) - - Return the emoji's hash. - - .. describe:: iter(x) - - Returns an iterator of ``(field, value)`` pairs. This allows this class - to be used as an iterable in list/dict/etc constructions. - - .. describe:: str(x) - - Returns the emoji rendered for Discord. - - Attributes - ---------- - name: :class:`str` - The emoji's name. - id: :class:`int` - The emoji's ID. - application_id: :class:`int` - The apllication ID this emoji belongs to. - require_colons: :class:`bool` - Whether colons are required to use this emoji in the client (:PJSalt: vs PJSalt). - animated: :class:`bool` - Whether the emoji is animated or not. - managed: :class:`bool` - Whether the emoji is managed by a Twitch integration. - available: :class:`bool` - Whether the emoji is available for use. - user: :class:`User` - The user that created this emoji. - """ - - def __init__(self, *, app_id: int, state: ConnectionState, data: EmojiPayload) -> None: - self.guild_id: Optional[int] = None - self.application_id: int = app_id - self._state: ConnectionState = state - self._from_data(data) - - def __repr__(self) -> str: - return f"" - - async def edit(self, *, name: str) -> ApplicationEmoji: - """|coro| - - Edits the application custom emoji. - - Parameters - ---------- - name: :class:`str` - The new emoji name. - - Raises - ------ - Forbidden - You are not allowed to edit this emoji. - HTTPException - An error occurred editing the emoji. - - Returns - ------- - :class:`ApplicationEmoji` - The newly updated emoji. - """ - data = await self._state.http.edit_app_emoji(self.application_id, self.id, name) - return ApplicationEmoji(app_id=self.application_id, state=self._state, data=data) - - async def delete(self) -> None: - """|coro| + if self.guild is None: + if self.application_id is None: + # should never happen + raise ValueError("Idk message about invalid state?! Pls catch this when reviewing") - Deletes the application custom emoji. - - Raises - ------ - Forbidden - You are not allowed to delete this emoji. - HTTPException - An error occurred deleting the emoji. - """ - await self._state.http.delete_app_emoji(self.application_id, self.id) - - -AppEmoji = ApplicationEmoji + data = await self._state.http.edit_app_emoji(self.application_id, self.id, name) + else: + data = await self._state.http.edit_custom_emoji( + self.guild.id, self.id, payload=payload, reason=reason + ) + return Emoji(guild=self.guild, data=data, state=self._state) diff --git a/disnake/state.py b/disnake/state.py index 9fe946ba9c..f23271f9f5 100644 --- a/disnake/state.py +++ b/disnake/state.py @@ -44,7 +44,7 @@ VoiceChannel, _guild_channel_factory, ) -from .emoji import ApplicationEmoji, Emoji +from .emoji import Emoji from .entitlement import Entitlement from .enums import ApplicationCommandType, ChannelType, ComponentType, MessageType, Status, try_enum from .flags import ApplicationFlags, Intents, MemberCacheFlags @@ -297,7 +297,7 @@ def clear( # - accesses on `_users` are slower, e.g. `__getitem__` takes ~1us with weakrefs and ~0.2us without self._users: weakref.WeakValueDictionary[int, User] = weakref.WeakValueDictionary() self._emojis: Dict[int, Emoji] = {} - self._application_emojis: Dict[int, ApplicationEmoji] = {} + self._application_emojis: Dict[int, Emoji] = {} self._stickers: Dict[int, GuildSticker] = {} self._guilds: Dict[int, Guild] = {} @@ -404,9 +404,9 @@ def store_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji: self._emojis[emoji_id] = emoji = Emoji(guild=guild, state=self, data=data) return emoji - def store_application_emoji(self, data: EmojiPayload) -> ApplicationEmoji: + def store_application_emoji(self, data: EmojiPayload) -> Emoji: emoji_id = int(data["id"]) # type: ignore - self._application_emojis[emoji_id] = emoji = ApplicationEmoji(app_id=self.application_id, state=self, data=data) # type: ignore + self._application_emojis[emoji_id] = emoji = Emoji(guild=None, state=self, data=data) return emoji def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker: @@ -521,7 +521,7 @@ def emojis(self) -> List[Emoji]: return list(self._emojis.values()) @property - def application_emojis(self) -> List[ApplicationEmoji]: + def application_emojis(self) -> List[Emoji]: return list(self._application_emojis.values()) @property @@ -532,7 +532,7 @@ def get_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]: # the keys of self._emojis are ints return self._emojis.get(emoji_id) # type: ignore - def get_application_emoji(self, emoji_id: Optional[int]) -> Optional[ApplicationEmoji]: + def get_application_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]: # the keys of self._application_emojis are ints return self._application_emojis.get(emoji_id) # type: ignore diff --git a/docs/api/emoji.rst b/docs/api/emoji.rst index d9301cb45f..3b4756d85b 100644 --- a/docs/api/emoji.rst +++ b/docs/api/emoji.rst @@ -20,15 +20,6 @@ Emoji :members: :inherited-members: -ApplicationEmoji -~~~~~~~~~~~~~~~~ - -.. attributable:: ApplicationEmoji - -.. autoclass:: ApplicationEmoji() - :members: - :inherited-members: - Data Classes ------------- From d405d7f5edf12e9d2ec044af4bfe6ee89b667f40 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:02:09 +0200 Subject: [PATCH 05/14] fix docs --- disnake/client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/disnake/client.py b/disnake/client.py index b13c52cf42..beddf86c94 100644 --- a/disnake/client.py +++ b/disnake/client.py @@ -569,7 +569,7 @@ def emojis(self) -> List[Emoji]: @property def application_emojis(self) -> List[Emoji]: - """List[:class:`.ApplicationEmoji`]: The application emojis that the connected client has. + """List[:class:`.Emoji`]: The application emojis that the connected client has. .. versionadded:: 2.10 """ @@ -1570,7 +1570,7 @@ def get_application_emoji(self, emoji_id: int, /) -> Optional[Emoji]: Returns ------- - Optional[:class:`Emoji`] + Optional[:class:`.Emoji`] The returned application emoji or ``None`` if not found. """ return self._connection.get_application_emoji(emoji_id) @@ -2485,7 +2485,7 @@ async def fetch_application_emoji(self, emoji_id: int, /, cache: bool = False) - Returns ------- - :class:`Emoji` + :class:`.Emoji` The application emoji you requested. """ data = await self.http.get_app_emoji(self.application_id, emoji_id) @@ -2524,7 +2524,7 @@ async def create_application_emoji(self, *, name: str, image: AssetBytes) -> Emo Returns ------- - :class:`Emoji` + :class:`.Emoji` The newly created application emoji. """ img = await utils._assetbytes_to_base64_data(image) @@ -2534,7 +2534,7 @@ async def create_application_emoji(self, *, name: str, image: AssetBytes) -> Emo async def fetch_application_emojis(self, *, cache: bool = False) -> List[Emoji]: """|coro| - Retrieves all the :class:`Emoji` of the application. + Retrieves all the :class:`.Emoji` of the application. .. versionadded:: 2.10 @@ -2553,7 +2553,7 @@ async def fetch_application_emojis(self, *, cache: bool = False) -> List[Emoji]: Returns ------- - List[:class:`Emoji`] + List[:class:`.Emoji`] The list of application emojis you requested. """ data = await self.http.get_all_app_emojis(self.application_id) From d397f8dce416e63b51c386089ae2d1400a3589ae Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:19:25 +0200 Subject: [PATCH 06/14] don't store application id in emoji --- disnake/emoji.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index db8f6462cf..e7499e48d5 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -98,7 +98,6 @@ def __init__( data: EmojiPayload, ) -> None: self.guild_id: Optional[int] = guild.id if guild else None - self._application_id: Optional[int] = state.application_id self._state: ConnectionState = state self._from_data(data) @@ -188,7 +187,7 @@ def application_id(self) -> Optional[int]: """ if self.guild is None: return - return self._application_id + return self._state.application_id def is_usable(self) -> bool: """Whether the bot can use this emoji. From 37f218de9b289482c54fa5943f2d5cadf9ec2cc1 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:20:12 +0200 Subject: [PATCH 07/14] update slots --- disnake/emoji.py | 1 - 1 file changed, 1 deletion(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index e7499e48d5..9c1c2e33c2 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -85,7 +85,6 @@ class Emoji(_EmojiTag, AssetMixin): "name", "_roles", "guild_id", - "_application_id", "user", "available", ) From 51dae3be77e973a66d79c4ec91ac6b84c619f08f Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:47:50 +0100 Subject: [PATCH 08/14] Update disnake/client.py Co-authored-by: Victor <67214928+Victorsitou@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/client.py b/disnake/client.py index beddf86c94..f99a86e373 100644 --- a/disnake/client.py +++ b/disnake/client.py @@ -2504,7 +2504,7 @@ async def create_application_emoji(self, *, name: str, image: AssetBytes) -> Emo Parameters ---------- name: :class:`str` - The name of the new string. + The emoji name. Must be at least 2 characters. image: |resource_type| The image data of the emoji. Only JPG, PNG and GIF images are supported. From b2d343ad71c16bed7b6111ce41d28af314169976 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:47:56 +0100 Subject: [PATCH 09/14] Update disnake/client.py Co-authored-by: Victor <67214928+Victorsitou@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/disnake/client.py b/disnake/client.py index f99a86e373..919827f13a 100644 --- a/disnake/client.py +++ b/disnake/client.py @@ -2538,7 +2538,6 @@ async def fetch_application_emojis(self, *, cache: bool = False) -> List[Emoji]: .. versionadded:: 2.10 - Parameters ---------- cache: :class:`bool` From 9cba32cbee8dec3d668482858f1b972463963940 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:48:18 +0100 Subject: [PATCH 10/14] Update disnake/emoji.py Co-authored-by: Victor <67214928+Victorsitou@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/emoji.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index 9c1c2e33c2..49e37ae1bc 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -180,7 +180,7 @@ def guild(self) -> Optional[Guild]: @property def application_id(self) -> Optional[int]: - """:class:`int`: The ID of the application which owns this emoji. + """Optional[:class:`int`]: The ID of the application which owns this emoji. .. versionadded:: 2.10 """ From 3b3cbb968ca5707e3a138010e5f7aabf61bc8299 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:48:28 +0100 Subject: [PATCH 11/14] Update disnake/emoji.py Co-authored-by: Victor <67214928+Victorsitou@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/emoji.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index 49e37ae1bc..b02e596c03 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -185,7 +185,7 @@ def application_id(self) -> Optional[int]: .. versionadded:: 2.10 """ if self.guild is None: - return + return None return self._state.application_id def is_usable(self) -> bool: From 8247fa3440ec43aef177fb8788917bbcfd98d7ee Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:48:38 +0100 Subject: [PATCH 12/14] Update disnake/emoji.py Co-authored-by: Victor <67214928+Victorsitou@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/emoji.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index b02e596c03..e33eeb9bfd 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -51,7 +51,7 @@ class Emoji(_EmojiTag, AssetMixin): Returns the emoji rendered for Discord. - .. versionchanched:: 2.10 + .. versionchanged:: 2.10 This class can now represents app emojis too. Denoted by having :attr:`.Emoji.guild_id` as ``None``. From e38bda8e87ff24c655e9051c22db103080c1590b Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:48:53 +0100 Subject: [PATCH 13/14] Update disnake/client.py Co-authored-by: Victor <67214928+Victorsitou@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/client.py b/disnake/client.py index 919827f13a..106667d1a9 100644 --- a/disnake/client.py +++ b/disnake/client.py @@ -2518,7 +2518,7 @@ async def create_application_emoji(self, *, name: str, image: AssetBytes) -> Emo HTTPException An error occurred creating an app emoji. TypeError - The ``image`` asset is a lottie sticker (see :func:`Sticker.read`). + The ``image`` asset is a lottie sticker (see :func:`Sticker.read `). ValueError Wrong image format passed for ``image``. From 74bdc757ebd9f0d8a6fbc3bebbdd2a77fa18ecbf Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Tue, 26 Nov 2024 17:49:17 +0100 Subject: [PATCH 14/14] Update disnake/emoji.py Co-authored-by: Victor <67214928+Victorsitou@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/emoji.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/disnake/emoji.py b/disnake/emoji.py index e33eeb9bfd..e44118160b 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -166,9 +166,7 @@ def roles(self) -> List[Role]: @property def guild(self) -> Optional[Guild]: - """Optional[:class:`Guild`]: The guild this emoji belongs to. - - ``None`` if this is an app emoji. + """Optional[:class:`Guild`]: The guild this emoji belongs to. ``None`` if this is an app emoji. .. versionchanged:: 2.10