Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
2 changes: 1 addition & 1 deletion changelog/1223.breaking.rst
Original file line number Diff line number Diff line change
@@ -1 +1 @@
:attr:`Emoji.guild_id` can now be ``None`` if the emoji is owned by an application. You can use :attr:`Emoji.is_guild_emoji` and :attr:`Emoji.is_app_emoji` to check if this is a Guild or App Emoji.
:attr:`Emoji.guild_id` can now be ``None`` if the emoji is owned by an application. You can use :meth:`Emoji.is_guild_emoji` and :meth:`Emoji.is_app_emoji` to check if this is a Guild or App Emoji.
6 changes: 3 additions & 3 deletions changelog/1223.feature.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Add support to :class:`.Emoji` to represent application emojis.
New methods on :class:`Client`: :meth:`Client.fetch_application_emoji`, :meth:`Client.fetch_application_emojis` and :meth:`Client.create_application_emoji`.
New attributes on :class:`.Emoji`: :attr:`Emoji.application_id`, :attr:`Emoji.is_guild_emoji` and :attr:`Emoji.is_app_emoji`.
Add support to :class:`.Emoji` to represent application-owned emojis.
- New methods on :class:`Client`: :meth:`Client.fetch_application_emoji`, :meth:`Client.fetch_application_emojis` and :meth:`Client.create_application_emoji`.
- New attributes/methods on :class:`.Emoji`: :attr:`Emoji.application_id`, :meth:`Emoji.is_guild_emoji` and :meth:`Emoji.is_app_emoji`.
3 changes: 3 additions & 0 deletions changelog/1388.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add support to :class:`.Emoji` to represent application-owned emojis.
- New methods on :class:`Client`: :meth:`Client.fetch_application_emoji`, :meth:`Client.fetch_application_emojis` and :meth:`Client.create_application_emoji`.
- New attributes/methods on :class:`.Emoji`: :attr:`Emoji.application_id`, :meth:`Emoji.is_guild_emoji` and :meth:`Emoji.is_app_emoji`.
18 changes: 7 additions & 11 deletions disnake/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2418,7 +2418,7 @@ async def application_info(self) -> AppInfo:
data = await self.http.application_info()
return AppInfo(self._connection, data)

async def fetch_application_emoji(self, emoji_id: int) -> Emoji:
async def fetch_application_emoji(self, emoji_id: int, /) -> Emoji:
"""|coro|

Retrieves an application level :class:`~disnake.Emoji` based on its ID.
Expand All @@ -2434,8 +2434,8 @@ async def fetch_application_emoji(self, emoji_id: int) -> Emoji:
------
NotFound
The app emoji couldn't be found.
Forbidden
You are not allowed to get the app emoji.
HTTPException
An error occurred fetching the app emoji.

Returns
-------
Expand Down Expand Up @@ -2464,10 +2464,8 @@ async def create_application_emoji(self, *, name: str, image: AssetBytes) -> Emo
------
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.
An error occurred creating the app emoji.
TypeError
The ``image`` asset is a lottie sticker (see :func:`Sticker.read <disnake.Sticker.read>`).
ValueError
Expand All @@ -2479,7 +2477,7 @@ async def create_application_emoji(self, *, name: str, image: AssetBytes) -> Emo
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)
data = await self.http.create_app_emoji(self.application_id, name=name, image=img)
return Emoji(guild=None, state=self._connection, data=data)

async def fetch_application_emojis(self) -> List[Emoji]:
Expand All @@ -2491,10 +2489,8 @@ async def fetch_application_emojis(self) -> List[Emoji]:

Raises
------
NotFound
The app emojis for this application ID couldn't be found.
Forbidden
You are not allowed to get app emojis.
HTTPException
An error occurred fetching the app emojis.

Returns
-------
Expand Down
75 changes: 46 additions & 29 deletions disnake/emoji.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class Emoji(_EmojiTag, AssetMixin):

.. versionchanged:: |vnext|

This class can now represents app emojis. Use :attr:`Emoji.is_app_emoji` to check for this.
To check if this is a guild emoji, use :attr:`Emoji.is_guild_emoji`.
This class can now represent app emojis. Use :meth:`Emoji.is_app_emoji` to check for this.
To check if this is a guild emoji, use :meth:`Emoji.is_guild_emoji`.

Attributes
----------
Expand All @@ -74,9 +74,12 @@ class Emoji(_EmojiTag, AssetMixin):
available: :class:`bool`
Whether the emoji is available for use.
user: Optional[:class:`User`]
The user that created this emoji. This can only be retrieved using
:meth:`Guild.fetch_emoji`/:meth:`Guild.fetch_emojis` while
The user that created this emoji. If this is a guild emoji, this can only be retrieved
using :meth:`Guild.fetch_emoji`/:meth:`Guild.fetch_emojis` while
having the :attr:`~Permissions.manage_guild_expressions` permission.

If this is an app emoji, this is the team member that uploaded the emoji,
or the bot user if created using :meth:`Client.create_application_emoji`.
"""

__slots__: Tuple[str, ...] = (
Expand Down Expand Up @@ -184,27 +187,30 @@ def guild(self) -> Optional[Guild]:

@property
def application_id(self) -> Optional[int]:
"""Optional[:class:`int`]: The ID of the application which owns this emoji.
"""Optional[:class:`int`]: The ID of the application which owns this emoji,
if this is an app emoji.

.. versionadded:: |vnext|
"""
if self.guild_id:
return None
return self._state.application_id

@property
def is_guild_emoji(self) -> bool:
""":class:`bool`: Whether this is a guild emoji.
"""Whether this is a guild emoji.

.. versionadded:: |vnext|

:return type: :class:`bool`
"""
return self.guild_id is not None

@property
def is_app_emoji(self) -> bool:
""":class:`bool`: Whether this is an application emoji.
"""Whether this is an application emoji.

.. versionadded:: |vnext|

:return type: :class:`bool`
"""
return self.guild_id is None

Expand All @@ -217,10 +223,8 @@ def is_usable(self) -> bool:
"""
if not self.available:
return False
if not self.guild:
# if we don't have a guild, this is an app emoji
return self.available
if not self._roles:
# if we don't have a guild, this is an app emoji
if not self.guild or not self._roles:
return True
emoji_roles, my_roles = self._roles, self.guild.me._roles
return any(my_roles.has(role_id) for role_id in emoji_roles)
Expand All @@ -231,13 +235,15 @@ async def delete(self, *, reason: Optional[str] = None) -> None:
Deletes the custom emoji.

You must have :attr:`~Permissions.manage_guild_expressions` permission to
do this.
do this, if this is not an app emoji.

Parameters
----------
reason: Optional[:class:`str`]
The reason for deleting this emoji. Shows up on the audit log.

Only applies to guild emojis, not app emojis.

Raises
------
Forbidden
Expand All @@ -247,15 +253,18 @@ async def delete(self, *, reason: Optional[str] = None) -> None:
InvalidData
The emoji data is invalid and cannot be processed.
"""
# this is an app emoji
if self.guild is None:
if self.guild_id is None:
# this is an app emoji
if self.application_id is None:
# should never happen
msg = f"guild and application_id are both None when attempting to delete emoji with ID {self.id} This may be a library bug! Open an issue on GitHub."
msg = (
f"guild_id and application_id are both None when attempting to delete emoji with ID {self.id}."
" This may be a library bug! Open an issue on GitHub."
)
raise InvalidData(msg)

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)
await self._state.http.delete_custom_emoji(self.guild_id, self.id, reason=reason)

async def edit(
self, *, name: str = MISSING, roles: List[Snowflake] = MISSING, reason: Optional[str] = None
Expand All @@ -265,7 +274,7 @@ async def edit(
Edits the custom emoji.

You must have :attr:`~Permissions.manage_guild_expressions` permission to
do this.
do this, if this is not an app emoji.

.. versionchanged:: 2.0
The newly updated emoji is returned.
Expand All @@ -280,9 +289,13 @@ async def edit(
An emoji cannot have both subscription roles (see :attr:`RoleTags.integration_id`) and
non-subscription roles, and emojis can't be converted between premium and non-premium
after creation.

Only applies to guild emojis, not app emojis.
reason: Optional[:class:`str`]
The reason for editing this emoji. Shows up on the audit log.

Only applies to guild emojis, not app emojis.

Raises
------
Forbidden
Expand All @@ -297,21 +310,25 @@ async def edit(
:class:`Emoji`
The newly updated emoji.
"""
payload = {}
if name is not MISSING:
payload["name"] = name
if roles is not MISSING:
payload["roles"] = [role.id for role in roles]

if self.guild is None:
if self.guild_id is None:
# this is an app emoji
if self.application_id is None:
# should never happen
msg = f"guild and application_id are both None when attempting to edit emoji with ID {self.id} This may be a library bug! Open an issue on GitHub."
msg = (
f"guild_id and application_id are both None when attempting to edit emoji with ID {self.id}."
" This may be a library bug! Open an issue on GitHub."
)
raise InvalidData(msg)

data = await self._state.http.edit_app_emoji(self.application_id, self.id, name)
data = await self._state.http.edit_app_emoji(self.application_id, self.id, name=name)
else:
payload = {}
if name is not MISSING:
payload["name"] = name
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
self.guild_id, self.id, payload=payload, reason=reason
)
return Emoji(guild=self.guild, data=data, state=self._state)
6 changes: 4 additions & 2 deletions disnake/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -1771,7 +1771,9 @@ 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]:
def create_app_emoji(
self, app_id: Snowflake, *, name: str, image: str
) -> Response[emoji.Emoji]:
payload: Dict[str, Any] = {
"name": name,
"image": image,
Expand All @@ -1781,7 +1783,7 @@ def create_app_emoji(self, app_id: Snowflake, name: str, image: str) -> Response
return self.request(r, json=payload)

def edit_app_emoji(
self, app_id: Snowflake, emoji_id: Snowflake, name: str
self, app_id: Snowflake, emoji_id: Snowflake, *, name: str
) -> Response[emoji.Emoji]:
payload: Dict[str, Any] = {
"name": name,
Expand Down
Loading