Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
102 changes: 101 additions & 1 deletion discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,13 @@ def guilds(self) -> Sequence[Guild]:

@property
def emojis(self) -> Sequence[Emoji]:
"""Sequence[:class:`.Emoji`]: The emojis that the connected client has."""
"""Sequence[:class:`.Emoji`]: The emojis that the connected client has.

.. note::

This not include the emojis that are owned by the application.
Use :meth:`.fetch_application_emoji` to get those.
"""
return self._connection.emojis

@property
Expand Down Expand Up @@ -3073,3 +3079,97 @@ def persistent_views(self) -> Sequence[View]:
.. versionadded:: 2.0
"""
return self._connection.persistent_views

async def create_application_emoji(
self,
*,
name: str,
image: bytes,
) -> Emoji:
"""|coro|

Create an emoji for the current application.

.. versionadded:: 2.5

Parameters
----------
name: :class:`str`
The emoji name. Must be at least 2 characters.
image: :class:`bytes`
The :term:`py:bytes-like object` representing the image data to use.
Only JPG, PNG and GIF images are supported.

Raises
------
MissingApplicationID
The application ID could not be found.
HTTPException
Creating the emoji failed.

Returns
-------
:class:`.Emoji`
The emoji that was created.
"""
if self.application_id is None:
raise MissingApplicationID

img = utils._bytes_to_base64_data(image)
data = await self.http.create_application_emoji(self.application_id, name, img)
return Emoji(guild=Object(0), state=self._connection, data=data)

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

Retrieves an emoji for the current application.

.. versionadded:: 2.5

Parameters
----------
emoji_id: :class:`int`
The emoji ID to retrieve.

Raises
------
MissingApplicationID
The application ID could not be found.
HTTPException
Retrieving the emoji failed.

Returns
-------
:class:`.Emoji`
The emoji requested.
"""
if self.application_id is None:
raise MissingApplicationID

data = await self.http.get_application_emoji(self.application_id, emoji_id)
return Emoji(guild=Object(0), state=self._connection, data=data)

async def fetch_application_emojis(self) -> List[Emoji]:
"""|coro|

Retrieves all emojis for the current application.

.. versionadded:: 2.5

Raises
-------
MissingApplicationID
The application ID could not be found.
HTTPException
Retrieving the emojis failed.

Returns
-------
List[:class:`.Emoji`]
The list of emojis for the current application.
"""
if self.application_id is None:
raise MissingApplicationID

data = await self.http.get_application_emojis(self.application_id)
return [Emoji(guild=Object(0), state=self._connection, data=emoji) for emoji in data['items']]
49 changes: 47 additions & 2 deletions discord/emoji.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
from .utils import SnowflakeList, snowflake_time, MISSING
from .partial_emoji import _EmojiTag, PartialEmoji
from .user import User
from .app_commands.errors import MissingApplicationID
from .object import Object

# fmt: off
__all__ = (
Expand Down Expand Up @@ -93,6 +95,10 @@ class Emoji(_EmojiTag, AssetMixin):
user: Optional[:class:`User`]
The user that created the emoji. This can only be retrieved using :meth:`Guild.fetch_emoji` and
having :attr:`~Permissions.manage_emojis`.

Or if :meth:`.is_application_owned` is ``True``, this is the team member that uploaded
the emoji, or the bot user if it was uploaded using the API and this can
only be retrieved using :meth:`~discord.Client.fetch_application_emoji` or :meth:`~discord.Client.fetch_application_emojis`.
"""

__slots__: Tuple[str, ...] = (
Expand All @@ -108,7 +114,7 @@ class Emoji(_EmojiTag, AssetMixin):
'available',
)

def __init__(self, *, guild: Guild, state: ConnectionState, data: EmojiPayload) -> None:
def __init__(self, *, guild: Snowflake, state: ConnectionState, data: EmojiPayload) -> None:
self.guild_id: int = guild.id
self._state: ConnectionState = state
self._from_data(data)
Expand Down Expand Up @@ -196,20 +202,32 @@ async def delete(self, *, reason: Optional[str] = None) -> None:

Deletes the custom emoji.

You must have :attr:`~Permissions.manage_emojis` to do this.
You must have :attr:`~Permissions.manage_emojis` to do this if
:meth:`.is_application_owned` is ``False``.

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

This does not apply if :meth:`.is_application_owned` is ``True``.

Raises
-------
Forbidden
You are not allowed to delete emojis.
HTTPException
An error occurred deleting the emoji.
MissingApplicationID
The emoji is owned by an application but the application ID is missing.
"""
if self.is_application_owned():
application_id = self._state.application_id
if application_id is None:
raise MissingApplicationID

await self._state.http.delete_application_emoji(application_id, self.id)
return

await self._state.http.delete_custom_emoji(self.guild_id, self.id, reason=reason)

Expand All @@ -231,15 +249,22 @@ async def edit(
The new emoji name.
roles: List[:class:`~discord.abc.Snowflake`]
A list of roles that can use this emoji. An empty list can be passed to make it available to everyone.

This does not apply if :meth:`.is_application_owned` is ``True``.

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

This does not apply if :meth:`.is_application_owned` is ``True``.

Raises
-------
Forbidden
You are not allowed to edit emojis.
HTTPException
An error occurred editing the emoji.
MissingApplicationID
The emoji is owned by an application but the application ID is missing

Returns
--------
Expand All @@ -253,5 +278,25 @@ async def edit(
if roles is not MISSING:
payload['roles'] = [role.id for role in roles]

if self.is_application_owned():
application_id = self._state.application_id
if application_id is None:
raise MissingApplicationID

payload.pop('roles', None)
data = await self._state.http.edit_application_emoji(
application_id,
self.id,
payload=payload,
)
return Emoji(guild=Object(0), data=data, state=self._state)

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) # type: ignore # if guild is None, the http request would have failed

def is_application_owned(self) -> bool:
""":class:`bool`: Whether the emoji is owned by an application.

.. versionadded:: 2.5
"""
return self.guild_id == 0
57 changes: 56 additions & 1 deletion discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -2515,7 +2515,7 @@ def delete_entitlement(self, application_id: Snowflake, entitlement_id: Snowflak
),
)

# Misc
# Application

def application_info(self) -> Response[appinfo.AppInfo]:
return self.request(Route('GET', '/oauth2/applications/@me'))
Expand All @@ -2536,6 +2536,59 @@ def edit_application_info(self, *, reason: Optional[str], payload: Any) -> Respo
payload = {k: v for k, v in payload.items() if k in valid_keys}
return self.request(Route('PATCH', '/applications/@me'), json=payload, reason=reason)

def get_application_emojis(self, application_id: Snowflake) -> Response[appinfo.ListAppEmojis]:
return self.request(Route('GET', '/applications/{application_id}/emojis', application_id=application_id))

def get_application_emoji(self, application_id: Snowflake, emoji_id: Snowflake) -> Response[emoji.Emoji]:
return self.request(
Route(
'GET', '/applications/{application_id}/emojis/{emoji_id}', application_id=application_id, emoji_id=emoji_id
)
)

def create_application_emoji(
self,
application_id: Snowflake,
name: str,
image: str,
) -> Response[emoji.Emoji]:
payload = {
'name': name,
'image': image,
}

return self.request(
Route('POST', '/applications/{application_id}/emojis', application_id=application_id), json=payload
)

def edit_application_emoji(
self,
application_id: Snowflake,
emoji_id: Snowflake,
*,
payload: Dict[str, Any],
) -> Response[emoji.Emoji]:
r = Route(
'PATCH', '/applications/{application_id}/emojis/{emoji_id}', application_id=application_id, emoji_id=emoji_id
)
return self.request(r, json=payload)

def delete_application_emoji(
self,
application_id: Snowflake,
emoji_id: Snowflake,
) -> Response[None]:
return self.request(
Route(
'DELETE',
'/applications/{application_id}/emojis/{emoji_id}',
application_id=application_id,
emoji_id=emoji_id,
)
)

# Poll

def get_poll_answer_voters(
self,
channel_id: Snowflake,
Expand Down Expand Up @@ -2573,6 +2626,8 @@ def end_poll(self, channel_id: Snowflake, message_id: Snowflake) -> Response[mes
)
)

# Misc

async def get_gateway(self, *, encoding: str = 'json', zlib: bool = True) -> str:
try:
data = await self.request(Route('GET', '/gateway'))
Expand Down
5 changes: 5 additions & 0 deletions discord/types/appinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .user import User
from .team import Team
from .snowflake import Snowflake
from .emoji import Emoji


class InstallParams(TypedDict):
Expand Down Expand Up @@ -79,3 +80,7 @@ class PartialAppInfo(BaseAppInfo, total=False):
class GatewayAppInfo(TypedDict):
id: Snowflake
flags: int


class ListAppEmojis(TypedDict):
items: List[Emoji]