Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/Rapptz/discord.py into v2…
Browse files Browse the repository at this point in the history
…-martine
  • Loading branch information
PredaaA committed Aug 30, 2024
2 parents c5be536 + dee5bf6 commit 4fa164e
Show file tree
Hide file tree
Showing 18 changed files with 458 additions and 41 deletions.
9 changes: 6 additions & 3 deletions discord/app_commands/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,10 @@ def _get_command_error(
if key == 'options':
for index, d in remaining.items():
_get_command_error(index, d, children, messages, indent=indent + 2)
elif key == '_errors':
errors = [x.get('message', '') for x in remaining]

messages.extend(f'{indentation} {message}' for message in errors)
else:
if isinstance(remaining, dict):
try:
Expand All @@ -493,10 +497,9 @@ def _get_command_error(
errors = _flatten_error_dict(remaining, key=key)
else:
errors = {key: ' '.join(x.get('message', '') for x in inner_errors)}
else:
errors = _flatten_error_dict(remaining, key=key)

messages.extend(f'{indentation} {k}: {v}' for k, v in errors.items())
if isinstance(errors, dict):
messages.extend(f'{indentation} {k}: {v}' for k, v in errors.items())


class CommandSyncFailure(AppCommandError, HTTPException):
Expand Down
6 changes: 6 additions & 0 deletions discord/appinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ class AppInfo:
The approximate count of the guilds the bot was added to.
.. versionadded:: 2.4
approximate_user_install_count: Optional[:class:`int`]
The approximate count of the user-level installations the bot has.
.. versionadded:: 2.5
"""

__slots__ = (
Expand Down Expand Up @@ -175,6 +179,7 @@ class AppInfo:
'interactions_endpoint_url',
'redirect_uris',
'approximate_guild_count',
'approximate_user_install_count',
)

def __init__(self, state: ConnectionState, data: AppInfoPayload):
Expand Down Expand Up @@ -212,6 +217,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload):
self.interactions_endpoint_url: Optional[str] = data.get('interactions_endpoint_url')
self.redirect_uris: List[str] = data.get('redirect_uris', [])
self.approximate_guild_count: int = data.get('approximate_guild_count', 0)
self.approximate_user_install_count: Optional[int] = data.get('approximate_user_install_count')

def __repr__(self) -> str:
return (
Expand Down
141 changes: 140 additions & 1 deletion discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ class Client:
set to is ``30.0`` seconds.
.. versionadded:: 2.0
connector: Optional[:class:`aiohttp.BaseConnector`]
The aiohhtp connector to use for this client. This can be used to control underlying aiohttp
behavior, such as setting a dns resolver or sslcontext.
.. versionadded:: 2.5
Attributes
-----------
Expand All @@ -264,13 +269,15 @@ def __init__(self, *, intents: Intents, **options: Any) -> None:
self.shard_id: Optional[int] = options.get('shard_id')
self.shard_count: Optional[int] = options.get('shard_count')

connector: Optional[aiohttp.BaseConnector] = options.get('connector', None)
proxy: Optional[str] = options.pop('proxy', None)
proxy_auth: Optional[aiohttp.BasicAuth] = options.pop('proxy_auth', None)
unsync_clock: bool = options.pop('assume_unsync_clock', True)
http_trace: Optional[aiohttp.TraceConfig] = options.pop('http_trace', None)
max_ratelimit_timeout: Optional[float] = options.pop('max_ratelimit_timeout', None)
self.http: HTTPClient = HTTPClient(
self.loop,
connector,
proxy=proxy,
proxy_auth=proxy_auth,
unsync_clock=unsync_clock,
Expand Down Expand Up @@ -359,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 @@ -623,6 +636,11 @@ async def login(self, token: str) -> None:
if self._connection.application_id is None:
self._connection.application_id = self._application.id

if self._application.interactions_endpoint_url is not None:
_log.warning(
'Application has an interaction endpoint URL set, this means registered components and app commands will not be received by the library.'
)

if not self._connection.application_flags:
self._connection.application_flags = self._application.flags

Expand Down Expand Up @@ -2919,6 +2937,33 @@ async def fetch_premium_sticker_packs(self) -> List[StickerPack]:
data = await self.http.list_premium_sticker_packs()
return [StickerPack(state=self._connection, data=pack) for pack in data['sticker_packs']]

async def fetch_premium_sticker_pack(self, sticker_pack_id: int, /) -> StickerPack:
"""|coro|
Retrieves a premium sticker pack with the specified ID.
.. versionadded:: 2.5
Parameters
----------
sticker_pack_id: :class:`int`
The sticker pack's ID to fetch from.
Raises
-------
NotFound
A sticker pack with this ID does not exist.
HTTPException
Retrieving the sticker pack failed.
Returns
-------
:class:`.StickerPack`
The retrieved premium sticker pack.
"""
data = await self.http.get_sticker_pack(sticker_pack_id)
return StickerPack(state=self._connection, data=data)

async def create_dm(self, user: Snowflake) -> DMChannel:
"""|coro|
Expand Down Expand Up @@ -3039,3 +3084,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']]
11 changes: 5 additions & 6 deletions discord/embeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def from_dict(cls, data: Mapping[str, Any]) -> Self:
"""Converts a :class:`dict` to a :class:`Embed` provided it is in the
format that Discord expects it to be in.
You can find out about this format in the :ddocs:`official Discord documentation <resources/channel#embed-object>`.
You can find out about this format in the :ddocs:`official Discord documentation <resources/message#embed-object>`.
Parameters
-----------
Expand Down Expand Up @@ -413,8 +413,9 @@ def set_image(self, *, url: Optional[Any]) -> Self:
Parameters
-----------
url: :class:`str`
url: Optional[:class:`str`]
The source URL for the image. Only HTTP(S) is supported.
If ``None`` is passed, any existing image is removed.
Inline attachment URLs are also supported, see :ref:`local_image`.
"""

Expand Down Expand Up @@ -452,13 +453,11 @@ def set_thumbnail(self, *, url: Optional[Any]) -> Self:
This function returns the class instance to allow for fluent-style
chaining.
.. versionchanged:: 1.4
Passing ``None`` removes the thumbnail.
Parameters
-----------
url: :class:`str`
url: Optional[:class:`str`]
The source URL for the thumbnail. Only HTTP(S) is supported.
If ``None`` is passed, any existing thumbnail is removed.
Inline attachment URLs are also supported, see :ref:`local_image`.
"""

Expand Down
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
Loading

0 comments on commit 4fa164e

Please sign in to comment.