Browse Source

Merge branch 'master' of https://github.com/rapptz/discord.py

pull/10166/head
DA-344 3 months ago
parent
commit
b2f77b9498
  1. 2
      .github/workflows/lint.yml
  2. 4
      discord/__init__.py
  3. 10
      discord/__main__.py
  4. 7
      discord/abc.py
  5. 10
      discord/activity.py
  6. 8
      discord/app_commands/commands.py
  7. 2
      discord/app_commands/transformers.py
  8. 2
      discord/app_commands/tree.py
  9. 129
      discord/appinfo.py
  10. 8
      discord/audit_logs.py
  11. 254
      discord/client.py
  12. 6
      discord/components.py
  13. 21
      discord/embeds.py
  14. 12
      discord/enums.py
  15. 4
      discord/ext/commands/bot.py
  16. 9
      discord/ext/commands/context.py
  17. 6
      discord/ext/commands/converter.py
  18. 2
      discord/ext/commands/core.py
  19. 7
      discord/ext/commands/errors.py
  20. 2
      discord/ext/commands/flags.py
  21. 13
      discord/ext/commands/hybrid.py
  22. 89
      discord/flags.py
  23. 4
      discord/gateway.py
  24. 5
      discord/guild.py
  25. 1
      discord/http.py
  26. 214
      discord/interactions.py
  27. 2
      discord/invite.py
  28. 10
      discord/member.py
  29. 23
      discord/message.py
  30. 9
      discord/poll.py
  31. 14
      discord/raw_models.py
  32. 110
      discord/role.py
  33. 8
      discord/state.py
  34. 2
      discord/threads.py
  35. 7
      discord/types/appinfo.py
  36. 2
      discord/types/embed.py
  37. 4
      discord/types/guild.py
  38. 34
      discord/types/interactions.py
  39. 5
      discord/ui/select.py
  40. 7
      discord/ui/view.py
  41. 6
      discord/utils.py
  42. 2
      discord/voice_state.py
  43. 45
      discord/webhook/async_.py
  44. 26
      discord/webhook/sync.py
  45. 2
      discord/widget.py
  46. 16
      docs/api.rst
  47. 5
      docs/ext/commands/api.rst
  48. 16
      docs/interactions/api.rst
  49. 154
      docs/whats_new.rst

2
.github/workflows/lint.yml

@ -38,7 +38,7 @@ jobs:
- name: Run Pyright
uses: jakebailey/pyright-action@v1
with:
version: '1.1.351'
version: '1.1.394'
warnings: false
no-comments: ${{ matrix.python-version != '3.x' }}

4
discord/__init__.py

@ -13,7 +13,7 @@ __title__ = 'discord'
__author__ = 'Rapptz'
__license__ = 'MIT'
__copyright__ = 'Copyright 2015-present Rapptz'
__version__ = '2.5.0a'
__version__ = '2.6.0a'
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
@ -83,7 +83,7 @@ class VersionInfo(NamedTuple):
serial: int
version_info: VersionInfo = VersionInfo(major=2, minor=5, micro=0, releaselevel='alpha', serial=0)
version_info: VersionInfo = VersionInfo(major=2, minor=6, micro=0, releaselevel='alpha', serial=0)
logging.getLogger(__name__).addHandler(logging.NullHandler())

10
discord/__main__.py

@ -28,7 +28,7 @@ from typing import Optional, Tuple, Dict
import argparse
import sys
from pathlib import Path
from pathlib import Path, PurePath, PureWindowsPath
import discord
import importlib.metadata
@ -225,8 +225,14 @@ def to_path(parser: argparse.ArgumentParser, name: str, *, replace_spaces: bool
)
if len(name) <= 4 and name.upper() in forbidden:
parser.error('invalid directory name given, use a different one')
path = PurePath(name)
if isinstance(path, PureWindowsPath) and path.drive:
drive, rest = path.parts[0], path.parts[1:]
transformed = tuple(map(lambda p: p.translate(_translation_table), rest))
name = drive + '\\'.join(transformed)
name = name.translate(_translation_table)
else:
name = name.translate(_translation_table)
if replace_spaces:
name = name.replace(' ', '-')
return Path(name)

7
discord/abc.py

@ -102,6 +102,9 @@ if TYPE_CHECKING:
GuildChannel as GuildChannelPayload,
OverwriteType,
)
from .types.guild import (
ChannelPositionUpdate,
)
from .types.snowflake import (
SnowflakeList,
)
@ -1232,11 +1235,11 @@ class GuildChannel:
raise ValueError('Could not resolve appropriate move position')
channels.insert(max((index + offset), 0), self)
payload = []
payload: List[ChannelPositionUpdate] = []
lock_permissions = kwargs.get('sync_permissions', False)
reason = kwargs.get('reason')
for index, channel in enumerate(channels):
d = {'id': channel.id, 'position': index}
d: ChannelPositionUpdate = {'id': channel.id, 'position': index}
if parent_id is not MISSING and channel.id == self.id:
d.update(parent_id=parent_id, lock_permissions=lock_permissions)
payload.append(d)

10
discord/activity.py

@ -273,7 +273,7 @@ class Activity(BaseActivity):
def start(self) -> Optional[datetime.datetime]:
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable."""
try:
timestamp = self.timestamps['start'] / 1000
timestamp = self.timestamps['start'] / 1000 # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
return None
else:
@ -283,7 +283,7 @@ class Activity(BaseActivity):
def end(self) -> Optional[datetime.datetime]:
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable."""
try:
timestamp = self.timestamps['end'] / 1000
timestamp = self.timestamps['end'] / 1000 # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
return None
else:
@ -293,7 +293,7 @@ class Activity(BaseActivity):
def large_image_url(self) -> Optional[str]:
"""Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity, if applicable."""
try:
large_image = self.assets['large_image']
large_image = self.assets['large_image'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
return None
else:
@ -303,7 +303,7 @@ class Activity(BaseActivity):
def small_image_url(self) -> Optional[str]:
"""Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity, if applicable."""
try:
small_image = self.assets['small_image']
small_image = self.assets['small_image'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
return None
else:
@ -525,7 +525,7 @@ class Streaming(BaseActivity):
"""
try:
name = self.assets['large_image']
name = self.assets['large_image'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
return None
else:

8
discord/app_commands/commands.py

@ -903,7 +903,7 @@ class Command(Generic[GroupT, P, T]):
predicates = getattr(param.autocomplete, '__discord_app_commands_checks__', [])
if predicates:
try:
passed = await async_all(f(interaction) for f in predicates)
passed = await async_all(f(interaction) for f in predicates) # type: ignore
except Exception:
passed = False
@ -1014,7 +1014,7 @@ class Command(Generic[GroupT, P, T]):
if not predicates:
return True
return await async_all(f(interaction) for f in predicates)
return await async_all(f(interaction) for f in predicates) # type: ignore
def error(self, coro: Error[GroupT]) -> Error[GroupT]:
"""A decorator that registers a coroutine as a local error handler.
@ -1308,7 +1308,7 @@ class ContextMenu:
if not predicates:
return True
return await async_all(f(interaction) for f in predicates)
return await async_all(f(interaction) for f in predicates) # type: ignore
def _has_any_error_handlers(self) -> bool:
return self.on_error is not None
@ -1842,7 +1842,7 @@ class Group:
if len(params) != 2:
raise TypeError('The error handler must have 2 parameters.')
self.on_error = coro
self.on_error = coro # type: ignore
return coro
async def interaction_check(self, interaction: Interaction, /) -> bool:

2
discord/app_commands/transformers.py

@ -235,7 +235,7 @@ class Transformer(Generic[ClientT]):
pass
def __or__(self, rhs: Any) -> Any:
return Union[self, rhs] # type: ignore
return Union[self, rhs]
@property
def type(self) -> AppCommandOptionType:

2
discord/app_commands/tree.py

@ -859,7 +859,7 @@ class CommandTree(Generic[ClientT]):
if len(params) != 2:
raise TypeError('error handler must have 2 parameters')
self.on_error = coro
self.on_error = coro # type: ignore
return coro
def command(

129
discord/appinfo.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
from typing import List, TYPE_CHECKING, Optional
from typing import List, TYPE_CHECKING, Literal, Optional
from . import utils
from .asset import Asset
@ -41,6 +41,7 @@ if TYPE_CHECKING:
PartialAppInfo as PartialAppInfoPayload,
Team as TeamPayload,
InstallParams as InstallParamsPayload,
AppIntegrationTypeConfig as AppIntegrationTypeConfigPayload,
)
from .user import User
from .state import ConnectionState
@ -49,6 +50,7 @@ __all__ = (
'AppInfo',
'PartialAppInfo',
'AppInstallParams',
'IntegrationTypeConfig',
)
@ -180,6 +182,7 @@ class AppInfo:
'redirect_uris',
'approximate_guild_count',
'approximate_user_install_count',
'_integration_types_config',
)
def __init__(self, state: ConnectionState, data: AppInfoPayload):
@ -218,6 +221,9 @@ class AppInfo:
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')
self._integration_types_config: Dict[Literal['0', '1'], AppIntegrationTypeConfigPayload] = data.get(
'integration_types_config', {}
)
def __repr__(self) -> str:
return (
@ -260,6 +266,36 @@ class AppInfo:
"""
return ApplicationFlags._from_value(self._flags)
@property
def guild_integration_config(self) -> Optional[IntegrationTypeConfig]:
"""Optional[:class:`IntegrationTypeConfig`]: The default settings for the
application's installation context in a guild.
.. versionadded:: 2.5
"""
if not self._integration_types_config:
return None
try:
return IntegrationTypeConfig(self._integration_types_config['0'])
except KeyError:
return None
@property
def user_integration_config(self) -> Optional[IntegrationTypeConfig]:
"""Optional[:class:`IntegrationTypeConfig`]: The default settings for the
application's installation context as a user.
.. versionadded:: 2.5
"""
if not self._integration_types_config:
return None
try:
return IntegrationTypeConfig(self._integration_types_config['1'])
except KeyError:
return None
async def edit(
self,
*,
@ -274,6 +310,10 @@ class AppInfo:
cover_image: Optional[bytes] = MISSING,
interactions_endpoint_url: Optional[str] = MISSING,
tags: Optional[List[str]] = MISSING,
guild_install_scopes: Optional[List[str]] = MISSING,
guild_install_permissions: Optional[Permissions] = MISSING,
user_install_scopes: Optional[List[str]] = MISSING,
user_install_permissions: Optional[Permissions] = MISSING,
) -> AppInfo:
r"""|coro|
@ -315,6 +355,24 @@ class AppInfo:
over the gateway. Can be ``None`` to remove the URL.
tags: Optional[List[:class:`str`]]
The new list of tags describing the functionality of the application. Can be ``None`` to remove the tags.
guild_install_scopes: Optional[List[:class:`str`]]
The new list of :ddocs:`OAuth2 scopes <topics/oauth2#shared-resources-oauth2-scopes>` of
the default guild installation context. Can be ``None`` to remove the scopes.
.. versionadded: 2.5
guild_install_permissions: Optional[:class:`Permissions`]
The new permissions of the default guild installation context. Can be ``None`` to remove the permissions.
.. versionadded: 2.5
user_install_scopes: Optional[List[:class:`str`]]
The new list of :ddocs:`OAuth2 scopes <topics/oauth2#shared-resources-oauth2-scopes>` of
the default user installation context. Can be ``None`` to remove the scopes.
.. versionadded: 2.5
user_install_permissions: Optional[:class:`Permissions`]
The new permissions of the default user installation context. Can be ``None`` to remove the permissions.
.. versionadded: 2.5
reason: Optional[:class:`str`]
The reason for editing the application. Shows up on the audit log.
@ -324,7 +382,8 @@ class AppInfo:
Editing the application failed
ValueError
The image format passed in to ``icon`` or ``cover_image`` is invalid. This is also raised
when ``install_params_scopes`` and ``install_params_permissions`` are incompatible with each other.
when ``install_params_scopes`` and ``install_params_permissions`` are incompatible with each other,
or when ``guild_install_scopes`` and ``guild_install_permissions`` are incompatible with each other.
Returns
-------
@ -364,7 +423,7 @@ class AppInfo:
else:
if install_params_permissions is not MISSING:
raise ValueError("install_params_scopes must be set if install_params_permissions is set")
raise ValueError('install_params_scopes must be set if install_params_permissions is set')
if flags is not MISSING:
if flags is None:
@ -389,6 +448,51 @@ class AppInfo:
if tags is not MISSING:
payload['tags'] = tags
integration_types_config: Dict[str, Any] = {}
if guild_install_scopes is not MISSING or guild_install_permissions is not MISSING:
guild_install_params: Optional[Dict[str, Any]] = {}
if guild_install_scopes in (None, MISSING):
guild_install_scopes = []
if 'bot' not in guild_install_scopes and guild_install_permissions is not MISSING:
raise ValueError("'bot' must be in guild_install_scopes if guild_install_permissions is set")
if guild_install_permissions in (None, MISSING):
guild_install_params['permissions'] = 0
else:
guild_install_params['permissions'] = guild_install_permissions.value
guild_install_params['scopes'] = guild_install_scopes
integration_types_config['0'] = {'oauth2_install_params': guild_install_params or None}
else:
if guild_install_permissions is not MISSING:
raise ValueError('guild_install_scopes must be set if guild_install_permissions is set')
if user_install_scopes is not MISSING or user_install_permissions is not MISSING:
user_install_params: Optional[Dict[str, Any]] = {}
if user_install_scopes in (None, MISSING):
user_install_scopes = []
if 'bot' not in user_install_scopes and user_install_permissions is not MISSING:
raise ValueError("'bot' must be in user_install_scopes if user_install_permissions is set")
if user_install_permissions in (None, MISSING):
user_install_params['permissions'] = 0
else:
user_install_params['permissions'] = user_install_permissions.value
user_install_params['scopes'] = user_install_scopes
integration_types_config['1'] = {'oauth2_install_params': user_install_params or None}
else:
if user_install_permissions is not MISSING:
raise ValueError('user_install_scopes must be set if user_install_permissions is set')
if integration_types_config:
payload['integration_types_config'] = integration_types_config
data = await self._state.http.edit_application_info(reason=reason, payload=payload)
return AppInfo(data=data, state=self._state)
@ -520,3 +624,22 @@ class AppInstallParams:
def __init__(self, data: InstallParamsPayload) -> None:
self.scopes: List[str] = data.get('scopes', [])
self.permissions: Permissions = Permissions(int(data['permissions']))
class IntegrationTypeConfig:
"""Represents the default settings for the application's installation context.
.. versionadded:: 2.5
Attributes
----------
oauth2_install_params: Optional[:class:`AppInstallParams`]
The install params for this installation context's default in-app authorization link.
"""
def __init__(self, data: AppIntegrationTypeConfigPayload) -> None:
self.oauth2_install_params: Optional[AppInstallParams] = None
try:
self.oauth2_install_params = AppInstallParams(data['oauth2_install_params']) # type: ignore # EAFP
except KeyError:
pass

8
discord/audit_logs.py

@ -874,7 +874,13 @@ class AuditLogEntry(Hashable):
def _convert_target_emoji(self, target_id: int) -> Union[Emoji, Object]:
return self._state.get_emoji(target_id) or Object(id=target_id, type=Emoji)
def _convert_target_message(self, target_id: int) -> Union[Member, User, Object]:
def _convert_target_message(self, target_id: Optional[int]) -> Optional[Union[Member, User, Object]]:
# The message_pin and message_unpin action types do not have a
# non-null target_id so safeguard against that
if target_id is None:
return None
return self._get_member(target_id) or Object(id=target_id, type=Member)
def _convert_target_stage_instance(self, target_id: int) -> Union[StageInstance, Object]:

254
discord/client.py

@ -1213,8 +1213,8 @@ class Client:
event: Literal['raw_app_command_permissions_update'],
/,
*,
check: Optional[Callable[[RawAppCommandPermissionsUpdateEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawAppCommandPermissionsUpdateEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawAppCommandPermissionsUpdateEvent:
...
@ -1224,8 +1224,8 @@ class Client:
event: Literal['app_command_completion'],
/,
*,
check: Optional[Callable[[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]]:
...
@ -1237,8 +1237,8 @@ class Client:
event: Literal['automod_rule_create', 'automod_rule_update', 'automod_rule_delete'],
/,
*,
check: Optional[Callable[[AutoModRule], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[AutoModRule], bool]] = ...,
timeout: Optional[float] = ...,
) -> AutoModRule:
...
@ -1248,8 +1248,8 @@ class Client:
event: Literal['automod_action'],
/,
*,
check: Optional[Callable[[AutoModAction], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[AutoModAction], bool]] = ...,
timeout: Optional[float] = ...,
) -> AutoModAction:
...
@ -1261,8 +1261,8 @@ class Client:
event: Literal['private_channel_update'],
/,
*,
check: Optional[Callable[[GroupChannel, GroupChannel], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[GroupChannel, GroupChannel], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[GroupChannel, GroupChannel]:
...
@ -1272,8 +1272,8 @@ class Client:
event: Literal['private_channel_pins_update'],
/,
*,
check: Optional[Callable[[PrivateChannel, datetime.datetime], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[PrivateChannel, datetime.datetime], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[PrivateChannel, datetime.datetime]:
...
@ -1283,8 +1283,8 @@ class Client:
event: Literal['guild_channel_delete', 'guild_channel_create'],
/,
*,
check: Optional[Callable[[GuildChannel], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[GuildChannel], bool]] = ...,
timeout: Optional[float] = ...,
) -> GuildChannel:
...
@ -1294,8 +1294,8 @@ class Client:
event: Literal['guild_channel_update'],
/,
*,
check: Optional[Callable[[GuildChannel, GuildChannel], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[GuildChannel, GuildChannel], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[GuildChannel, GuildChannel]:
...
@ -1311,7 +1311,7 @@ class Client:
bool,
]
],
timeout: Optional[float] = None,
timeout: Optional[float] = ...,
) -> Tuple[Union[GuildChannel, Thread], Optional[datetime.datetime]]:
...
@ -1321,8 +1321,8 @@ class Client:
event: Literal['typing'],
/,
*,
check: Optional[Callable[[Messageable, Union[User, Member], datetime.datetime], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Messageable, Union[User, Member], datetime.datetime], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Messageable, Union[User, Member], datetime.datetime]:
...
@ -1332,8 +1332,8 @@ class Client:
event: Literal['raw_typing'],
/,
*,
check: Optional[Callable[[RawTypingEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawTypingEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawTypingEvent:
...
@ -1345,8 +1345,8 @@ class Client:
event: Literal['connect', 'disconnect', 'ready', 'resumed'],
/,
*,
check: Optional[Callable[[], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[], bool]] = ...,
timeout: Optional[float] = ...,
) -> None:
...
@ -1356,8 +1356,8 @@ class Client:
event: Literal['shard_connect', 'shard_disconnect', 'shard_ready', 'shard_resumed'],
/,
*,
check: Optional[Callable[[int], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[int], bool]] = ...,
timeout: Optional[float] = ...,
) -> int:
...
@ -1367,8 +1367,8 @@ class Client:
event: Literal['socket_event_type', 'socket_raw_receive'],
/,
*,
check: Optional[Callable[[str], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[str], bool]] = ...,
timeout: Optional[float] = ...,
) -> str:
...
@ -1378,8 +1378,8 @@ class Client:
event: Literal['socket_raw_send'],
/,
*,
check: Optional[Callable[[Union[str, bytes]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Union[str, bytes]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Union[str, bytes]:
...
@ -1390,8 +1390,8 @@ class Client:
event: Literal['entitlement_create', 'entitlement_update', 'entitlement_delete'],
/,
*,
check: Optional[Callable[[Entitlement], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Entitlement], bool]] = ...,
timeout: Optional[float] = ...,
) -> Entitlement:
...
@ -1408,8 +1408,8 @@ class Client:
],
/,
*,
check: Optional[Callable[[Guild], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Guild], bool]] = ...,
timeout: Optional[float] = ...,
) -> Guild:
...
@ -1419,8 +1419,8 @@ class Client:
event: Literal['guild_update'],
/,
*,
check: Optional[Callable[[Guild, Guild], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Guild, Guild], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Guild, Guild]:
...
@ -1430,8 +1430,8 @@ class Client:
event: Literal['guild_emojis_update'],
/,
*,
check: Optional[Callable[[Guild, Sequence[Emoji], Sequence[Emoji]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Guild, Sequence[Emoji], Sequence[Emoji]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Guild, Sequence[Emoji], Sequence[Emoji]]:
...
@ -1441,8 +1441,8 @@ class Client:
event: Literal['guild_stickers_update'],
/,
*,
check: Optional[Callable[[Guild, Sequence[GuildSticker], Sequence[GuildSticker]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Guild, Sequence[GuildSticker], Sequence[GuildSticker]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Guild, Sequence[GuildSticker], Sequence[GuildSticker]]:
...
@ -1452,8 +1452,8 @@ class Client:
event: Literal['invite_create', 'invite_delete'],
/,
*,
check: Optional[Callable[[Invite], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Invite], bool]] = ...,
timeout: Optional[float] = ...,
) -> Invite:
...
@ -1463,8 +1463,8 @@ class Client:
event: Literal['audit_log_entry_create'],
/,
*,
check: Optional[Callable[[AuditLogEntry], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[AuditLogEntry], bool]] = ...,
timeout: Optional[float] = ...,
) -> AuditLogEntry:
...
@ -1476,8 +1476,8 @@ class Client:
event: Literal['integration_create', 'integration_update'],
/,
*,
check: Optional[Callable[[Integration], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Integration], bool]] = ...,
timeout: Optional[float] = ...,
) -> Integration:
...
@ -1487,8 +1487,8 @@ class Client:
event: Literal['guild_integrations_update'],
/,
*,
check: Optional[Callable[[Guild], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Guild], bool]] = ...,
timeout: Optional[float] = ...,
) -> Guild:
...
@ -1498,8 +1498,8 @@ class Client:
event: Literal['webhooks_update'],
/,
*,
check: Optional[Callable[[GuildChannel], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[GuildChannel], bool]] = ...,
timeout: Optional[float] = ...,
) -> GuildChannel:
...
@ -1509,8 +1509,8 @@ class Client:
event: Literal['raw_integration_delete'],
/,
*,
check: Optional[Callable[[RawIntegrationDeleteEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawIntegrationDeleteEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawIntegrationDeleteEvent:
...
@ -1522,8 +1522,8 @@ class Client:
event: Literal['interaction'],
/,
*,
check: Optional[Callable[[Interaction[Self]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Interaction[Self]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Interaction[Self]:
...
@ -1535,8 +1535,8 @@ class Client:
event: Literal['member_join', 'member_remove'],
/,
*,
check: Optional[Callable[[Member], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Member], bool]] = ...,
timeout: Optional[float] = ...,
) -> Member:
...
@ -1546,8 +1546,8 @@ class Client:
event: Literal['raw_member_remove'],
/,
*,
check: Optional[Callable[[RawMemberRemoveEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawMemberRemoveEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawMemberRemoveEvent:
...
@ -1557,8 +1557,8 @@ class Client:
event: Literal['member_update', 'presence_update'],
/,
*,
check: Optional[Callable[[Member, Member], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Member, Member], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Member, Member]:
...
@ -1568,8 +1568,8 @@ class Client:
event: Literal['user_update'],
/,
*,
check: Optional[Callable[[User, User], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[User, User], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[User, User]:
...
@ -1579,8 +1579,8 @@ class Client:
event: Literal['member_ban'],
/,
*,
check: Optional[Callable[[Guild, Union[User, Member]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Guild, Union[User, Member]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Guild, Union[User, Member]]:
...
@ -1590,8 +1590,8 @@ class Client:
event: Literal['member_unban'],
/,
*,
check: Optional[Callable[[Guild, User], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Guild, User], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Guild, User]:
...
@ -1603,8 +1603,8 @@ class Client:
event: Literal['message', 'message_delete'],
/,
*,
check: Optional[Callable[[Message], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Message], bool]] = ...,
timeout: Optional[float] = ...,
) -> Message:
...
@ -1614,8 +1614,8 @@ class Client:
event: Literal['message_edit'],
/,
*,
check: Optional[Callable[[Message, Message], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Message, Message], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Message, Message]:
...
@ -1625,8 +1625,8 @@ class Client:
event: Literal['bulk_message_delete'],
/,
*,
check: Optional[Callable[[List[Message]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[List[Message]], bool]] = ...,
timeout: Optional[float] = ...,
) -> List[Message]:
...
@ -1636,8 +1636,8 @@ class Client:
event: Literal['raw_message_edit'],
/,
*,
check: Optional[Callable[[RawMessageUpdateEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawMessageUpdateEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawMessageUpdateEvent:
...
@ -1647,8 +1647,8 @@ class Client:
event: Literal['raw_message_delete'],
/,
*,
check: Optional[Callable[[RawMessageDeleteEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawMessageDeleteEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawMessageDeleteEvent:
...
@ -1658,8 +1658,8 @@ class Client:
event: Literal['raw_bulk_message_delete'],
/,
*,
check: Optional[Callable[[RawBulkMessageDeleteEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawBulkMessageDeleteEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawBulkMessageDeleteEvent:
...
@ -1671,8 +1671,8 @@ class Client:
event: Literal['reaction_add', 'reaction_remove'],
/,
*,
check: Optional[Callable[[Reaction, Union[Member, User]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Reaction, Union[Member, User]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Reaction, Union[Member, User]]:
...
@ -1682,8 +1682,8 @@ class Client:
event: Literal['reaction_clear'],
/,
*,
check: Optional[Callable[[Message, List[Reaction]], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Message, List[Reaction]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Message, List[Reaction]]:
...
@ -1693,8 +1693,8 @@ class Client:
event: Literal['reaction_clear_emoji'],
/,
*,
check: Optional[Callable[[Reaction], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Reaction], bool]] = ...,
timeout: Optional[float] = ...,
) -> Reaction:
...
@ -1704,8 +1704,8 @@ class Client:
event: Literal['raw_reaction_add', 'raw_reaction_remove'],
/,
*,
check: Optional[Callable[[RawReactionActionEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawReactionActionEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawReactionActionEvent:
...
@ -1715,8 +1715,8 @@ class Client:
event: Literal['raw_reaction_clear'],
/,
*,
check: Optional[Callable[[RawReactionClearEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawReactionClearEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawReactionClearEvent:
...
@ -1726,8 +1726,8 @@ class Client:
event: Literal['raw_reaction_clear_emoji'],
/,
*,
check: Optional[Callable[[RawReactionClearEmojiEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawReactionClearEmojiEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawReactionClearEmojiEvent:
...
@ -1739,8 +1739,8 @@ class Client:
event: Literal['guild_role_create', 'guild_role_delete'],
/,
*,
check: Optional[Callable[[Role], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Role], bool]] = ...,
timeout: Optional[float] = ...,
) -> Role:
...
@ -1750,8 +1750,8 @@ class Client:
event: Literal['guild_role_update'],
/,
*,
check: Optional[Callable[[Role, Role], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Role, Role], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Role, Role]:
...
@ -1763,8 +1763,8 @@ class Client:
event: Literal['scheduled_event_create', 'scheduled_event_delete'],
/,
*,
check: Optional[Callable[[ScheduledEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[ScheduledEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> ScheduledEvent:
...
@ -1774,8 +1774,8 @@ class Client:
event: Literal['scheduled_event_user_add', 'scheduled_event_user_remove'],
/,
*,
check: Optional[Callable[[ScheduledEvent, User], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[ScheduledEvent, User], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[ScheduledEvent, User]:
...
@ -1787,8 +1787,8 @@ class Client:
event: Literal['stage_instance_create', 'stage_instance_delete'],
/,
*,
check: Optional[Callable[[StageInstance], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[StageInstance], bool]] = ...,
timeout: Optional[float] = ...,
) -> StageInstance:
...
@ -1798,8 +1798,8 @@ class Client:
event: Literal['stage_instance_update'],
/,
*,
check: Optional[Callable[[StageInstance, StageInstance], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[StageInstance, StageInstance], bool]] = ...,
timeout: Optional[float] = ...,
) -> Coroutine[Any, Any, Tuple[StageInstance, StageInstance]]:
...
@ -1810,8 +1810,8 @@ class Client:
event: Literal['subscription_create', 'subscription_update', 'subscription_delete'],
/,
*,
check: Optional[Callable[[Subscription], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Subscription], bool]] = ...,
timeout: Optional[float] = ...,
) -> Subscription:
...
@ -1822,8 +1822,8 @@ class Client:
event: Literal['thread_create', 'thread_join', 'thread_remove', 'thread_delete'],
/,
*,
check: Optional[Callable[[Thread], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Thread], bool]] = ...,
timeout: Optional[float] = ...,
) -> Thread:
...
@ -1833,8 +1833,8 @@ class Client:
event: Literal['thread_update'],
/,
*,
check: Optional[Callable[[Thread, Thread], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Thread, Thread], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Thread, Thread]:
...
@ -1844,8 +1844,8 @@ class Client:
event: Literal['raw_thread_update'],
/,
*,
check: Optional[Callable[[RawThreadUpdateEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawThreadUpdateEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawThreadUpdateEvent:
...
@ -1855,8 +1855,8 @@ class Client:
event: Literal['raw_thread_delete'],
/,
*,
check: Optional[Callable[[RawThreadDeleteEvent], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawThreadDeleteEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawThreadDeleteEvent:
...
@ -1866,8 +1866,8 @@ class Client:
event: Literal['thread_member_join', 'thread_member_remove'],
/,
*,
check: Optional[Callable[[ThreadMember], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[ThreadMember], bool]] = ...,
timeout: Optional[float] = ...,
) -> ThreadMember:
...
@ -1877,8 +1877,8 @@ class Client:
event: Literal['raw_thread_member_remove'],
/,
*,
check: Optional[Callable[[RawThreadMembersUpdate], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[RawThreadMembersUpdate], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawThreadMembersUpdate:
...
@ -1890,8 +1890,8 @@ class Client:
event: Literal['voice_state_update'],
/,
*,
check: Optional[Callable[[Member, VoiceState, VoiceState], bool]],
timeout: Optional[float] = None,
check: Optional[Callable[[Member, VoiceState, VoiceState], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Member, VoiceState, VoiceState]:
...
@ -1903,8 +1903,8 @@ class Client:
event: Literal['poll_vote_add', 'poll_vote_remove'],
/,
*,
check: Optional[Callable[[Union[User, Member], PollAnswer], bool]] = None,
timeout: Optional[float] = None,
check: Optional[Callable[[Union[User, Member], PollAnswer], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Union[User, Member], PollAnswer]:
...
@ -1914,8 +1914,8 @@ class Client:
event: Literal['raw_poll_vote_add', 'raw_poll_vote_remove'],
/,
*,
check: Optional[Callable[[RawPollVoteActionEvent], bool]] = None,
timeout: Optional[float] = None,
check: Optional[Callable[[RawPollVoteActionEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> RawPollVoteActionEvent:
...
@ -1927,8 +1927,8 @@ class Client:
event: Literal["command", "command_completion"],
/,
*,
check: Optional[Callable[[Context[Any]], bool]] = None,
timeout: Optional[float] = None,
check: Optional[Callable[[Context[Any]], bool]] = ...,
timeout: Optional[float] = ...,
) -> Context[Any]:
...
@ -1938,8 +1938,8 @@ class Client:
event: Literal["command_error"],
/,
*,
check: Optional[Callable[[Context[Any], CommandError], bool]] = None,
timeout: Optional[float] = None,
check: Optional[Callable[[Context[Any], CommandError], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[Context[Any], CommandError]:
...
@ -1949,8 +1949,8 @@ class Client:
event: str,
/,
*,
check: Optional[Callable[..., bool]] = None,
timeout: Optional[float] = None,
check: Optional[Callable[..., bool]] = ...,
timeout: Optional[float] = ...,
) -> Any:
...

6
discord/components.py

@ -226,12 +226,12 @@ class Button(Component):
self.label: Optional[str] = data.get('label')
self.emoji: Optional[PartialEmoji]
try:
self.emoji = PartialEmoji.from_dict(data['emoji'])
self.emoji = PartialEmoji.from_dict(data['emoji']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.emoji = None
try:
self.sku_id: Optional[int] = int(data['sku_id'])
self.sku_id: Optional[int] = int(data['sku_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.sku_id = None
@ -451,7 +451,7 @@ class SelectOption:
@classmethod
def from_dict(cls, data: SelectOptionPayload) -> SelectOption:
try:
emoji = PartialEmoji.from_dict(data['emoji'])
emoji = PartialEmoji.from_dict(data['emoji']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
emoji = None

21
discord/embeds.py

@ -29,6 +29,7 @@ from typing import Any, Dict, List, Mapping, Optional, Protocol, TYPE_CHECKING,
from . import utils
from .colour import Colour
from .flags import AttachmentFlags, EmbedFlags
# fmt: off
__all__ = (
@ -76,6 +77,7 @@ if TYPE_CHECKING:
proxy_url: Optional[str]
height: Optional[int]
width: Optional[int]
flags: Optional[AttachmentFlags]
class _EmbedVideoProxy(Protocol):
url: Optional[str]
@ -131,7 +133,7 @@ class Embed:
The type of embed. Usually "rich".
This can be set during initialisation.
Possible strings for embed types can be found on discord's
:ddocs:`api docs <resources/channel#embed-object-embed-types>`
:ddocs:`api docs <resources/message#embed-object-embed-types>`
description: Optional[:class:`str`]
The description of the embed.
This can be set during initialisation.
@ -146,6 +148,10 @@ class Embed:
colour: Optional[Union[:class:`Colour`, :class:`int`]]
The colour code of the embed. Aliased to ``color`` as well.
This can be set during initialisation.
flags: Optional[:class:`EmbedFlags`]
The flags of this embed.
.. versionadded:: 2.5
"""
__slots__ = (
@ -162,6 +168,7 @@ class Embed:
'_author',
'_fields',
'description',
'flags',
)
def __init__(
@ -181,6 +188,7 @@ class Embed:
self.type: EmbedType = type
self.url: Optional[str] = url
self.description: Optional[str] = description
self.flags: Optional[EmbedFlags] = None
if self.title is not None:
self.title = str(self.title)
@ -245,6 +253,11 @@ class Embed:
else:
setattr(self, '_' + attr, value)
try:
self.flags = EmbedFlags._from_value(data['flags'])
except KeyError:
pass
return self
def copy(self) -> Self:
@ -399,11 +412,15 @@ class Embed:
- ``proxy_url``
- ``width``
- ``height``
- ``flags``
If the attribute has no value then ``None`` is returned.
"""
# Lying to the type checker for better developer UX.
return EmbedProxy(getattr(self, '_image', {})) # type: ignore
data = getattr(self, '_image', {})
if 'flags' in data:
data['flags'] = AttachmentFlags._from_value(data['flags'])
return EmbedProxy(data) # type: ignore
def set_image(self, *, url: Optional[Any]) -> Self:
"""Sets the image for the embed content.

12
discord/enums.py

@ -86,13 +86,13 @@ def _create_value_cls(name: str, comparable: bool):
# All the type ignores here are due to the type checker being unable to recognise
# Runtime type creation without exploding.
cls = namedtuple('_EnumValue_' + name, 'name value')
cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>' # type: ignore
cls.__str__ = lambda self: f'{name}.{self.name}' # type: ignore
cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>'
cls.__str__ = lambda self: f'{name}.{self.name}'
if comparable:
cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value # type: ignore
cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value # type: ignore
cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value # type: ignore
cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value # type: ignore
cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value
cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value
cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value
cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value
return cls

4
discord/ext/commands/bot.py

@ -172,7 +172,7 @@ class BotBase(GroupMixin[None]):
**options: Any,
) -> None:
super().__init__(intents=intents, **options)
self.command_prefix: PrefixType[BotT] = command_prefix
self.command_prefix: PrefixType[BotT] = command_prefix # type: ignore
self.extra_events: Dict[str, List[CoroFunc]] = {}
# Self doesn't have the ClientT bound, but since this is a mixin it technically does
self.__tree: app_commands.CommandTree[Self] = tree_cls(self) # type: ignore
@ -487,7 +487,7 @@ class BotBase(GroupMixin[None]):
if len(data) == 0:
return True
return await discord.utils.async_all(f(ctx) for f in data)
return await discord.utils.async_all(f(ctx) for f in data) # type: ignore
async def is_owner(self, user: User, /) -> bool:
"""|coro|

9
discord/ext/commands/context.py

@ -82,7 +82,7 @@ def is_cog(obj: Any) -> TypeGuard[Cog]:
return hasattr(obj, '__cog_commands__')
class DeferTyping:
class DeferTyping(Generic[BotT]):
def __init__(self, ctx: Context[BotT], *, ephemeral: bool):
self.ctx: Context[BotT] = ctx
self.ephemeral: bool = ephemeral
@ -1078,8 +1078,11 @@ class Context(discord.abc.Messageable, Generic[BotT]):
if self.interaction.response.is_done():
msg = await self.interaction.followup.send(**kwargs, wait=True)
else:
await self.interaction.response.send_message(**kwargs)
msg = await self.interaction.original_response()
response = await self.interaction.response.send_message(**kwargs)
if not isinstance(response.resource, discord.InteractionMessage):
msg = await self.interaction.original_response()
else:
msg = response.resource
if delete_after is not None:
await msg.delete(delay=delete_after)

6
discord/ext/commands/converter.py

@ -1125,7 +1125,7 @@ class Greedy(List[T]):
args = getattr(converter, '__args__', ())
if discord.utils.PY_310 and converter.__class__ is types.UnionType: # type: ignore
converter = Union[args] # type: ignore
converter = Union[args]
origin = getattr(converter, '__origin__', None)
@ -1138,7 +1138,7 @@ class Greedy(List[T]):
if origin is Union and type(None) in args:
raise TypeError(f'Greedy[{converter!r}] is invalid.')
return cls(converter=converter)
return cls(converter=converter) # type: ignore
@property
def constructed_converter(self) -> Any:
@ -1325,7 +1325,7 @@ async def _actual_conversion(ctx: Context[BotT], converter: Any, argument: str,
else:
return await converter().convert(ctx, argument)
elif isinstance(converter, Converter):
return await converter.convert(ctx, argument) # type: ignore
return await converter.convert(ctx, argument)
except CommandError:
raise
except Exception as exc:

2
discord/ext/commands/core.py

@ -1285,7 +1285,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
# since we have no checks, then we just return True.
return True
return await discord.utils.async_all(predicate(ctx) for predicate in predicates)
return await discord.utils.async_all(predicate(ctx) for predicate in predicates) # type: ignore
finally:
ctx.command = original

7
discord/ext/commands/errors.py

@ -24,18 +24,19 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union, Generic
from discord.errors import ClientException, DiscordException
from discord.utils import _human_join
from ._types import BotT
if TYPE_CHECKING:
from discord.abc import GuildChannel
from discord.threads import Thread
from discord.types.snowflake import Snowflake, SnowflakeList
from discord.app_commands import AppCommandError
from ._types import BotT
from .context import Context
from .converter import Converter
from .cooldowns import BucketType, Cooldown
@ -235,7 +236,7 @@ class CheckFailure(CommandError):
pass
class CheckAnyFailure(CheckFailure):
class CheckAnyFailure(Generic[BotT], CheckFailure):
"""Exception raised when all predicates in :func:`check_any` fail.
This inherits from :exc:`CheckFailure`.

2
discord/ext/commands/flags.py

@ -443,7 +443,7 @@ async def convert_flag(ctx: Context[BotT], argument: str, flag: Flag, annotation
return await convert_flag(ctx, argument, flag, annotation)
elif origin is Union and type(None) in annotation.__args__:
# typing.Optional[x]
annotation = Union[tuple(arg for arg in annotation.__args__ if arg is not type(None))] # type: ignore
annotation = Union[tuple(arg for arg in annotation.__args__ if arg is not type(None))]
return await run_converters(ctx, annotation, argument, param)
elif origin is dict:
# typing.Dict[K, V] -> typing.Tuple[K, V]

13
discord/ext/commands/hybrid.py

@ -203,9 +203,9 @@ def replace_parameter(
# Fallback to see if the behaviour needs changing
origin = getattr(converter, '__origin__', None)
args = getattr(converter, '__args__', [])
if isinstance(converter, Range):
if isinstance(converter, Range): # type: ignore # Range is not an Annotation at runtime
r = converter
param = param.replace(annotation=app_commands.Range[r.annotation, r.min, r.max])
param = param.replace(annotation=app_commands.Range[r.annotation, r.min, r.max]) # type: ignore
elif isinstance(converter, Greedy):
# Greedy is "optional" in ext.commands
# However, in here, it probably makes sense to make it required.
@ -257,7 +257,7 @@ def replace_parameter(
inner = args[0]
is_inner_transformer = is_transformer(inner)
if is_converter(inner) and not is_inner_transformer:
param = param.replace(annotation=Optional[ConverterTransformer(inner, original)]) # type: ignore
param = param.replace(annotation=Optional[ConverterTransformer(inner, original)])
else:
raise
elif origin:
@ -424,10 +424,10 @@ class HybridAppCommand(discord.app_commands.Command[CogT, P, T]):
if not ret:
return False
if self.checks and not await async_all(f(interaction) for f in self.checks):
if self.checks and not await async_all(f(interaction) for f in self.checks): # type: ignore
return False
if self.wrapped.checks and not await async_all(f(ctx) for f in self.wrapped.checks):
if self.wrapped.checks and not await async_all(f(ctx) for f in self.wrapped.checks): # type: ignore
return False
return True
@ -915,7 +915,8 @@ def hybrid_command(
def decorator(func: CommandCallback[CogT, ContextT, P, T]) -> HybridCommand[CogT, P, T]:
if isinstance(func, Command):
raise TypeError('Callback is already a command.')
return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs)
# Pyright does not allow Command[Any] to be assigned to Command[CogT] despite it being okay here
return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs) # type: ignore
return decorator

89
discord/flags.py

@ -63,6 +63,7 @@ __all__ = (
'RoleFlags',
'AppInstallationType',
'SKUFlags',
'EmbedFlags',
)
BF = TypeVar('BF', bound='BaseFlags')
@ -2181,6 +2182,30 @@ class AttachmentFlags(BaseFlags):
""":class:`bool`: Returns ``True`` if the attachment has been edited using the remix feature."""
return 1 << 2
@flag_value
def spoiler(self):
""":class:`bool`: Returns ``True`` if the attachment was marked as a spoiler.
.. versionadded:: 2.5
"""
return 1 << 3
@flag_value
def contains_explicit_media(self):
""":class:`bool`: Returns ``True`` if the attachment was flagged as sensitive content.
.. versionadded:: 2.5
"""
return 1 << 4
@flag_value
def animated(self):
""":class:`bool`: Returns ``True`` if the attachment is an animated image.
.. versionadded:: 2.5
"""
return 1 << 5
@fill_with_flags()
class RoleFlags(BaseFlags):
@ -2316,3 +2341,67 @@ class SKUFlags(BaseFlags):
def user_subscription(self):
""":class:`bool`: Returns ``True`` if the SKU is a user subscription."""
return 1 << 8
@fill_with_flags()
class EmbedFlags(BaseFlags):
r"""Wraps up the Discord Embed flags
.. versionadded:: 2.5
.. container:: operations
.. describe:: x == y
Checks if two EmbedFlags are equal.
.. describe:: x != y
Checks if two EmbedFlags are not equal.
.. describe:: x | y, x |= y
Returns an EmbedFlags instance with all enabled flags from
both x and y.
.. describe:: x ^ y, x ^= y
Returns an EmbedFlags instance with only flags enabled on
only one of x or y, not on both.
.. describe:: ~x
Returns an EmbedFlags instance with all flags inverted from x.
.. describe:: hash(x)
Returns the flag's hash.
.. describe:: iter(x)
Returns an iterator of ``(name, value)`` pairs. This allows it
to be, for example, constructed as a dict or a list of pairs.
Note that aliases are not shown.
.. describe:: bool(b)
Returns whether any flag is set to ``True``.
Attributes
----------
value: :class:`int`
The raw value. You should query flags via the properties
rather than using this raw value.
"""
@flag_value
def contains_explicit_media(self):
""":class:`bool`: Returns ``True`` if the embed was flagged as sensitive content."""
return 1 << 4
@flag_value
def content_inventory_entry(self):
""":class:`bool`: Returns ``True`` if the embed is a reply to an activity card, and is no
longer displayed.
"""
return 1 << 5

4
discord/gateway.py

@ -831,7 +831,7 @@ class DiscordVoiceWebSocket:
self._close_code: Optional[int] = None
self.secret_key: Optional[List[int]] = None
if hook:
self._hook = hook
self._hook = hook # type: ignore
async def _hook(self, *args: Any) -> None:
pass
@ -893,7 +893,7 @@ class DiscordVoiceWebSocket:
return ws
async def select_protocol(self, ip: str, port: int, mode: int) -> None:
async def select_protocol(self, ip: str, port: int, mode: str) -> None:
payload = {
'op': self.SELECT_PROTOCOL,
'd': {

5
discord/guild.py

@ -551,7 +551,8 @@ class Guild(Hashable):
member = self.get_member(user_id)
if member is None:
try:
member = Member(data=data['member'], state=self._state, guild=self)
member_data = data['member'] # pyright: ignore[reportTypedDictNotRequiredAccess]
member = Member(data=member_data, state=self._state, guild=self)
except KeyError:
member = None
@ -573,7 +574,7 @@ class Guild(Hashable):
def _from_data(self, guild: GuildPayload) -> None:
try:
self._member_count = guild['member_count']
self._member_count = guild['member_count'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass

1
discord/http.py

@ -2620,6 +2620,7 @@ class HTTPClient:
'cover_image',
'interactions_endpoint_url ',
'tags',
'integration_types_config',
)
payload = {k: v for k, v in payload.items() if k in valid_keys}

214
discord/interactions.py

@ -54,6 +54,8 @@ __all__ = (
'Interaction',
'InteractionMessage',
'InteractionResponse',
'InteractionCallbackResponse',
'InteractionCallbackActivityInstance',
)
if TYPE_CHECKING:
@ -61,6 +63,8 @@ if TYPE_CHECKING:
Interaction as InteractionPayload,
InteractionData,
ApplicationCommandInteractionData,
InteractionCallback as InteractionCallbackPayload,
InteractionCallbackActivity as InteractionCallbackActivityPayload,
)
from .types.webhook import (
Webhook as WebhookPayload,
@ -90,6 +94,10 @@ if TYPE_CHECKING:
DMChannel,
GroupChannel,
]
InteractionCallbackResource = Union[
"InteractionMessage",
"InteractionCallbackActivityInstance",
]
MISSING: Any = utils.MISSING
@ -211,14 +219,15 @@ class Interaction(Generic[ClientT]):
int(k): int(v) for k, v in data.get('authorizing_integration_owners', {}).items()
}
try:
self.context = AppCommandContext._from_value([data['context']])
value = data['context'] # pyright: ignore[reportTypedDictNotRequiredAccess]
self.context = AppCommandContext._from_value([value])
except KeyError:
self.context = AppCommandContext()
self.locale: Locale = try_enum(Locale, data.get('locale', 'en-US'))
self.guild_locale: Optional[Locale]
try:
self.guild_locale = try_enum(Locale, data['guild_locale'])
self.guild_locale = try_enum(Locale, data['guild_locale']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.guild_locale = None
@ -469,6 +478,7 @@ class Interaction(Generic[ClientT]):
attachments: Sequence[Union[Attachment, File]] = MISSING,
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None,
poll: Poll = MISSING,
) -> InteractionMessage:
"""|coro|
@ -503,6 +513,14 @@ class Interaction(Generic[ClientT]):
view: Optional[:class:`~discord.ui.View`]
The updated view to update this message with. If ``None`` is passed then
the view is removed.
poll: :class:`Poll`
The poll to create when editing the message.
.. versionadded:: 2.5
.. note::
This is only accepted when the response type is :attr:`InteractionResponseType.deferred_channel_message`.
Raises
-------
@ -532,6 +550,7 @@ class Interaction(Generic[ClientT]):
view=view,
allowed_mentions=allowed_mentions,
previous_allowed_mentions=previous_mentions,
poll=poll,
) as params:
adapter = async_context.get()
http = self._state.http
@ -624,6 +643,106 @@ class Interaction(Generic[ClientT]):
return await translator.translate(string, locale=locale, context=context)
class InteractionCallbackActivityInstance:
"""Represents an activity instance launched as an interaction response.
.. versionadded:: 2.5
Attributes
----------
id: :class:`str`
The activity instance ID.
"""
__slots__ = ('id',)
def __init__(self, data: InteractionCallbackActivityPayload) -> None:
self.id: str = data['id']
class InteractionCallbackResponse(Generic[ClientT]):
"""Represents an interaction response callback.
.. versionadded:: 2.5
Attributes
----------
id: :class:`int`
The interaction ID.
type: :class:`InteractionResponseType`
The interaction callback response type.
resource: Optional[Union[:class:`InteractionMessage`, :class:`InteractionCallbackActivityInstance`]]
The resource that the interaction response created. If a message was sent, this will be
a :class:`InteractionMessage`. If an activity was launched this will be a
:class:`InteractionCallbackActivityInstance`. In any other case, this will be ``None``.
message_id: Optional[:class:`int`]
The message ID of the resource. Only available if the resource is a :class:`InteractionMessage`.
activity_id: Optional[:class:`str`]
The activity ID of the resource. Only available if the resource is a :class:`InteractionCallbackActivityInstance`.
"""
__slots__ = (
'_state',
'_parent',
'type',
'id',
'_thinking',
'_ephemeral',
'message_id',
'activity_id',
'resource',
)
def __init__(
self,
*,
data: InteractionCallbackPayload,
parent: Interaction[ClientT],
state: ConnectionState,
type: InteractionResponseType,
) -> None:
self._state: ConnectionState = state
self._parent: Interaction[ClientT] = parent
self.type: InteractionResponseType = type
self._update(data)
def _update(self, data: InteractionCallbackPayload) -> None:
interaction = data['interaction']
self.id: int = int(interaction['id'])
self._thinking: bool = interaction.get('response_message_loading', False)
self._ephemeral: bool = interaction.get('response_message_ephemeral', False)
self.message_id: Optional[int] = utils._get_as_snowflake(interaction, 'response_message_id')
self.activity_id: Optional[str] = interaction.get('activity_instance_id')
self.resource: Optional[InteractionCallbackResource] = None
resource = data.get('resource')
if resource is not None:
self.type = try_enum(InteractionResponseType, resource['type'])
message = resource.get('message')
activity_instance = resource.get('activity_instance')
if message is not None:
self.resource = InteractionMessage(
state=self._state,
channel=self._parent.channel, # type: ignore # channel should be the correct type here
data=message,
)
elif activity_instance is not None:
self.resource = InteractionCallbackActivityInstance(activity_instance)
def is_thinking(self) -> bool:
""":class:`bool`: Whether the response was a thinking defer."""
return self._thinking
def is_ephemeral(self) -> bool:
""":class:`bool`: Whether the response was ephemeral."""
return self._ephemeral
class InteractionResponse(Generic[ClientT]):
"""Represents a Discord interaction response.
@ -653,7 +772,12 @@ class InteractionResponse(Generic[ClientT]):
""":class:`InteractionResponseType`: The type of response that was sent, ``None`` if response is not done."""
return self._response_type
async def defer(self, *, ephemeral: bool = False, thinking: bool = False) -> None:
async def defer(
self,
*,
ephemeral: bool = False,
thinking: bool = False,
) -> Optional[InteractionCallbackResponse[ClientT]]:
"""|coro|
Defers the interaction response.
@ -667,6 +791,9 @@ class InteractionResponse(Generic[ClientT]):
- :attr:`InteractionType.component`
- :attr:`InteractionType.modal_submit`
.. versionchanged:: 2.5
This now returns a :class:`InteractionCallbackResponse` instance.
Parameters
-----------
ephemeral: :class:`bool`
@ -685,6 +812,11 @@ class InteractionResponse(Generic[ClientT]):
Deferring the interaction failed.
InteractionResponded
This interaction has already been responded to before.
Returns
-------
Optional[:class:`InteractionCallbackResponse`]
The interaction callback resource, or ``None``.
"""
if self._response_type:
raise InteractionResponded(self._parent)
@ -709,7 +841,7 @@ class InteractionResponse(Generic[ClientT]):
adapter = async_context.get()
params = interaction_response_params(type=defer_type, data=data)
http = parent._state.http
await adapter.create_interaction_response(
response = await adapter.create_interaction_response(
parent.id,
parent.token,
session=parent._session,
@ -718,6 +850,12 @@ class InteractionResponse(Generic[ClientT]):
params=params,
)
self._response_type = InteractionResponseType(defer_type)
return InteractionCallbackResponse(
data=response,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
async def pong(self) -> None:
"""|coro|
@ -767,11 +905,14 @@ class InteractionResponse(Generic[ClientT]):
silent: bool = False,
delete_after: Optional[float] = None,
poll: Poll = MISSING,
) -> None:
) -> InteractionCallbackResponse[ClientT]:
"""|coro|
Responds to this interaction by sending a message.
.. versionchanged:: 2.5
This now returns a :class:`InteractionCallbackResponse` instance.
Parameters
-----------
content: Optional[:class:`str`]
@ -825,6 +966,11 @@ class InteractionResponse(Generic[ClientT]):
The length of ``embeds`` was invalid.
InteractionResponded
This interaction has already been responded to before.
Returns
-------
:class:`InteractionCallbackResponse`
The interaction callback data.
"""
if self._response_type:
raise InteractionResponded(self._parent)
@ -855,7 +1001,7 @@ class InteractionResponse(Generic[ClientT]):
)
http = parent._state.http
await adapter.create_interaction_response(
response = await adapter.create_interaction_response(
parent.id,
parent.token,
session=parent._session,
@ -886,6 +1032,13 @@ class InteractionResponse(Generic[ClientT]):
asyncio.create_task(inner_call())
return InteractionCallbackResponse(
data=response,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
async def edit_message(
self,
*,
@ -897,12 +1050,15 @@ class InteractionResponse(Generic[ClientT]):
allowed_mentions: Optional[AllowedMentions] = MISSING,
delete_after: Optional[float] = None,
suppress_embeds: bool = MISSING,
) -> None:
) -> Optional[InteractionCallbackResponse[ClientT]]:
"""|coro|
Responds to this interaction by editing the original message of
a component or modal interaction.
.. versionchanged:: 2.5
This now returns a :class:`InteractionCallbackResponse` instance.
Parameters
-----------
content: Optional[:class:`str`]
@ -948,6 +1104,11 @@ class InteractionResponse(Generic[ClientT]):
You specified both ``embed`` and ``embeds``.
InteractionResponded
This interaction has already been responded to before.
Returns
-------
Optional[:class:`InteractionCallbackResponse`]
The interaction callback data, or ``None`` if editing the message was not possible.
"""
if self._response_type:
raise InteractionResponded(self._parent)
@ -990,7 +1151,7 @@ class InteractionResponse(Generic[ClientT]):
)
http = parent._state.http
await adapter.create_interaction_response(
response = await adapter.create_interaction_response(
parent.id,
parent.token,
session=parent._session,
@ -1015,11 +1176,21 @@ class InteractionResponse(Generic[ClientT]):
asyncio.create_task(inner_call())
async def send_modal(self, modal: Modal, /) -> None:
return InteractionCallbackResponse(
data=response,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
async def send_modal(self, modal: Modal, /) -> InteractionCallbackResponse[ClientT]:
"""|coro|
Responds to this interaction by sending a modal.
.. versionchanged:: 2.5
This now returns a :class:`InteractionCallbackResponse` instance.
Parameters
-----------
modal: :class:`~discord.ui.Modal`
@ -1031,6 +1202,11 @@ class InteractionResponse(Generic[ClientT]):
Sending the modal failed.
InteractionResponded
This interaction has already been responded to before.
Returns
-------
:class:`InteractionCallbackResponse`
The interaction callback data.
"""
if self._response_type:
raise InteractionResponded(self._parent)
@ -1041,7 +1217,7 @@ class InteractionResponse(Generic[ClientT]):
http = parent._state.http
params = interaction_response_params(InteractionResponseType.modal.value, modal.to_dict())
await adapter.create_interaction_response(
response = await adapter.create_interaction_response(
parent.id,
parent.token,
session=parent._session,
@ -1053,6 +1229,13 @@ class InteractionResponse(Generic[ClientT]):
self._parent._state.store_view(modal)
self._response_type = InteractionResponseType.modal
return InteractionCallbackResponse(
data=response,
parent=self._parent,
state=self._parent._state,
type=self._response_type,
)
async def autocomplete(self, choices: Sequence[Choice[ChoiceT]]) -> None:
"""|coro|
@ -1154,6 +1337,7 @@ class InteractionMessage(Message):
view: Optional[View] = MISSING,
allowed_mentions: Optional[AllowedMentions] = None,
delete_after: Optional[float] = None,
poll: Poll = MISSING,
) -> InteractionMessage:
"""|coro|
@ -1188,6 +1372,15 @@ class InteractionMessage(Message):
then it is silently ignored.
.. versionadded:: 2.2
poll: :class:`~discord.Poll`
The poll to create when editing the message.
.. versionadded:: 2.5
.. note::
This is only accepted if the interaction response's :attr:`InteractionResponse.type`
attribute is :attr:`InteractionResponseType.deferred_channel_message`.
Raises
-------
@ -1212,6 +1405,7 @@ class InteractionMessage(Message):
attachments=attachments,
view=view,
allowed_mentions=allowed_mentions,
poll=poll,
)
if delete_after is not None:
await self.delete(delay=delete_after)

2
discord/invite.py

@ -437,7 +437,7 @@ class Invite(Hashable):
def from_incomplete(cls, *, state: ConnectionState, data: InvitePayload) -> Self:
guild: Optional[Union[Guild, PartialInviteGuild]]
try:
guild_data = data['guild']
guild_data = data['guild'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
# If we're here, then this is a group DM
guild = None

10
discord/member.py

@ -326,7 +326,7 @@ class Member(discord.abc.Messageable, _UserTag):
self._flags: int = data['flags']
self._avatar_decoration_data: Optional[AvatarDecorationData] = data.get('avatar_decoration_data')
try:
self._permissions = int(data['permissions'])
self._permissions = int(data['permissions']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self._permissions = None
@ -418,12 +418,12 @@ class Member(discord.abc.Messageable, _UserTag):
# the nickname change is optional,
# if it isn't in the payload then it didn't change
try:
self.nick = data['nick']
self.nick = data['nick'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
try:
self.pending = data['pending']
self.pending = data['pending'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
@ -561,7 +561,9 @@ class Member(discord.abc.Messageable, _UserTag):
role = g.get_role(role_id)
if role:
result.append(role)
result.append(g.default_role)
default_role = g.default_role
if default_role:
result.append(default_role)
result.sort()
return result

23
discord/message.py

@ -479,7 +479,7 @@ class MessageInteraction(Hashable):
self.user: Union[User, Member] = MISSING
try:
payload = data['member']
payload = data['member'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.user = state.create_user(data['user'])
else:
@ -1906,7 +1906,8 @@ class Message(PartialMessage, Hashable):
self.poll: Optional[Poll] = None
try:
self.poll = Poll._from_data(data=data['poll'], message=self, state=state)
poll = data['poll'] # pyright: ignore[reportTypedDictNotRequiredAccess]
self.poll = Poll._from_data(data=poll, message=self, state=state)
except KeyError:
pass
@ -1920,7 +1921,7 @@ class Message(PartialMessage, Hashable):
if self.guild is not None:
try:
thread = data['thread']
thread = data['thread'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
else:
@ -1935,7 +1936,7 @@ class Message(PartialMessage, Hashable):
# deprecated
try:
interaction = data['interaction']
interaction = data['interaction'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
else:
@ -1943,20 +1944,20 @@ class Message(PartialMessage, Hashable):
self.interaction_metadata: Optional[MessageInteractionMetadata] = None
try:
interaction_metadata = data['interaction_metadata']
interaction_metadata = data['interaction_metadata'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
else:
self.interaction_metadata = MessageInteractionMetadata(state=state, guild=self.guild, data=interaction_metadata)
try:
ref = data['message_reference']
ref = data['message_reference'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.reference = None
else:
self.reference = ref = MessageReference.with_state(state, ref)
try:
resolved = data['referenced_message']
resolved = data['referenced_message'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
else:
@ -1983,7 +1984,7 @@ class Message(PartialMessage, Hashable):
self.application: Optional[MessageApplication] = None
try:
application = data['application']
application = data['application'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
else:
@ -1991,7 +1992,7 @@ class Message(PartialMessage, Hashable):
self.role_subscription: Optional[RoleSubscriptionInfo] = None
try:
role_subscription = data['role_subscription_data']
role_subscription = data['role_subscription_data'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
else:
@ -1999,7 +2000,7 @@ class Message(PartialMessage, Hashable):
self.purchase_notification: Optional[PurchaseNotification] = None
try:
purchase_notification = data['purchase_notification']
purchase_notification = data['purchase_notification'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
pass
else:
@ -2007,7 +2008,7 @@ class Message(PartialMessage, Hashable):
for handler in ('author', 'member', 'mentions', 'mention_roles', 'components', 'call'):
try:
getattr(self, f'_handle_{handler}')(data[handler])
getattr(self, f'_handle_{handler}')(data[handler]) # type: ignore
except KeyError:
continue

9
discord/poll.py

@ -336,6 +336,15 @@ class Poll:
Defaults to ``False``.
layout_type: :class:`PollLayoutType`
The layout type of the poll. Defaults to :attr:`PollLayoutType.default`.
Attributes
-----------
duration: :class:`datetime.timedelta`
The duration of the poll.
multiple: :class:`bool`
Whether users are allowed to select more than one answer.
layout_type: :class:`PollLayoutType`
The layout type of the poll.
"""
__slots__ = (

14
discord/raw_models.py

@ -104,7 +104,7 @@ class RawMessageDeleteEvent(_RawReprMixin):
self.channel_id: int = int(data['channel_id'])
self.cached_message: Optional[Message] = None
try:
self.guild_id: Optional[int] = int(data['guild_id'])
self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.guild_id: Optional[int] = None
@ -132,7 +132,7 @@ class RawBulkMessageDeleteEvent(_RawReprMixin):
self.cached_messages: List[Message] = []
try:
self.guild_id: Optional[int] = int(data['guild_id'])
self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.guild_id: Optional[int] = None
@ -248,7 +248,7 @@ class RawReactionActionEvent(_RawReprMixin):
self.type: ReactionType = try_enum(ReactionType, data['type'])
try:
self.guild_id: Optional[int] = int(data['guild_id'])
self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.guild_id: Optional[int] = None
@ -281,7 +281,7 @@ class RawReactionClearEvent(_RawReprMixin):
self.channel_id: int = int(data['channel_id'])
try:
self.guild_id: Optional[int] = int(data['guild_id'])
self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.guild_id: Optional[int] = None
@ -311,7 +311,7 @@ class RawReactionClearEmojiEvent(_RawReprMixin):
self.channel_id: int = int(data['channel_id'])
try:
self.guild_id: Optional[int] = int(data['guild_id'])
self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.guild_id: Optional[int] = None
@ -338,7 +338,9 @@ class RawIntegrationDeleteEvent(_RawReprMixin):
self.guild_id: int = int(data['guild_id'])
try:
self.application_id: Optional[int] = int(data['application_id'])
self.application_id: Optional[int] = int(
data['application_id'] # pyright: ignore[reportTypedDictNotRequiredAccess]
)
except KeyError:
self.application_id: Optional[int] = None

110
discord/role.py

@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING
from typing import Any, Dict, List, Optional, Union, overload, TYPE_CHECKING
from .asset import Asset
from .permissions import Permissions
@ -286,7 +286,7 @@ class Role(Hashable):
self._flags: int = data.get('flags', 0)
try:
self.tags = RoleTags(data['tags'])
self.tags = RoleTags(data['tags']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.tags = None
@ -522,6 +522,112 @@ class Role(Hashable):
data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
return Role(guild=self.guild, data=data, state=self._state)
@overload
async def move(self, *, beginning: bool, offset: int = ..., reason: Optional[str] = ...):
...
@overload
async def move(self, *, end: bool, offset: int = ..., reason: Optional[str] = ...):
...
@overload
async def move(self, *, above: Role, offset: int = ..., reason: Optional[str] = ...):
...
@overload
async def move(self, *, below: Role, offset: int = ..., reason: Optional[str] = ...):
...
async def move(
self,
*,
beginning: bool = MISSING,
end: bool = MISSING,
above: Role = MISSING,
below: Role = MISSING,
offset: int = 0,
reason: Optional[str] = None,
):
"""|coro|
A rich interface to help move a role relative to other roles.
You must have :attr:`~discord.Permissions.manage_roles` to do this,
and you cannot move roles above the client's top role in the guild.
.. versionadded:: 2.5
Parameters
-----------
beginning: :class:`bool`
Whether to move this at the beginning of the role list, above the default role.
This is mutually exclusive with `end`, `above`, and `below`.
end: :class:`bool`
Whether to move this at the end of the role list.
This is mutually exclusive with `beginning`, `above`, and `below`.
above: :class:`Role`
The role that should be above our current role.
This mutually exclusive with `beginning`, `end`, and `below`.
below: :class:`Role`
The role that should be below our current role.
This mutually exclusive with `beginning`, `end`, and `above`.
offset: :class:`int`
The number of roles to offset the move by. For example,
an offset of ``2`` with ``beginning=True`` would move
it 2 above the beginning. A positive number moves it above
while a negative number moves it below. Note that this
number is relative and computed after the ``beginning``,
``end``, ``before``, and ``after`` parameters.
reason: Optional[:class:`str`]
The reason for editing this role. Shows up on the audit log.
Raises
-------
Forbidden
You cannot move the role there, or lack permissions to do so.
HTTPException
Moving the role failed.
TypeError
A bad mix of arguments were passed.
ValueError
An invalid role was passed.
Returns
--------
List[:class:`Role`]
A list of all the roles in the guild.
"""
if sum(bool(a) for a in (beginning, end, above, below)) > 1:
raise TypeError('Only one of [beginning, end, above, below] can be used.')
target = above or below
guild = self.guild
guild_roles = guild.roles
if target:
if target not in guild_roles:
raise ValueError('Target role is from a different guild')
if above == guild.default_role:
raise ValueError('Role cannot be moved below the default role')
if self == target:
raise ValueError('Target role cannot be itself')
roles = [r for r in guild_roles if r != self]
if beginning:
index = 1
elif end:
index = len(roles)
elif above in roles:
index = roles.index(above)
elif below in roles:
index = roles.index(below) + 1
else:
index = guild_roles.index(self)
roles.insert(max((index + offset), 1), self)
payload: List[RolePositionUpdate] = [{'id': role.id, 'position': idx} for idx, role in enumerate(roles)]
await self._state.http.move_role_position(guild.id, payload, reason=reason)
async def delete(self, *, reason: Optional[str] = None) -> None:
"""|coro|

8
discord/state.py

@ -540,7 +540,7 @@ class ConnectionState(Generic[ClientT]):
) -> Tuple[Union[Channel, Thread], Optional[Guild]]:
channel_id = int(data['channel_id'])
try:
guild_id = guild_id or int(data['guild_id'])
guild_id = guild_id or int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
guild = self._get_guild(guild_id)
except KeyError:
channel = DMChannel._from_message(self, channel_id)
@ -736,7 +736,7 @@ class ConnectionState(Generic[ClientT]):
if 'components' in data:
try:
entity_id = int(data['interaction']['id'])
entity_id = int(data['interaction']['id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except (KeyError, ValueError):
entity_id = raw.message_id
@ -935,7 +935,7 @@ class ConnectionState(Generic[ClientT]):
def parse_channel_pins_update(self, data: gw.ChannelPinsUpdateEvent) -> None:
channel_id = int(data['channel_id'])
try:
guild = self._get_guild(int(data['guild_id']))
guild = self._get_guild(int(data['guild_id'])) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
guild = None
channel = self._get_private_channel(channel_id)
@ -1017,7 +1017,7 @@ class ConnectionState(Generic[ClientT]):
return
try:
channel_ids = {int(i) for i in data['channel_ids']}
channel_ids = {int(i) for i in data['channel_ids']} # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
# If not provided, then the entire guild is being synced
# So all previous thread data should be overwritten

2
discord/threads.py

@ -192,7 +192,7 @@ class Thread(Messageable, Hashable):
self.me: Optional[ThreadMember]
try:
member = data['member']
member = data['member'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
self.me = None
else:

7
discord/types/appinfo.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations
from typing import TypedDict, List, Optional
from typing import Literal, Dict, TypedDict, List, Optional
from typing_extensions import NotRequired
from .user import User
@ -38,6 +38,10 @@ class InstallParams(TypedDict):
permissions: str
class AppIntegrationTypeConfig(TypedDict):
oauth2_install_params: NotRequired[InstallParams]
class BaseAppInfo(TypedDict):
id: Snowflake
name: str
@ -69,6 +73,7 @@ class AppInfo(BaseAppInfo):
tags: NotRequired[List[str]]
install_params: NotRequired[InstallParams]
custom_install_url: NotRequired[str]
integration_types_config: NotRequired[Dict[Literal['0', '1'], AppIntegrationTypeConfig]]
class PartialAppInfo(BaseAppInfo, total=False):

2
discord/types/embed.py

@ -50,6 +50,7 @@ class EmbedVideo(TypedDict, total=False):
proxy_url: str
height: int
width: int
flags: int
class EmbedImage(TypedDict, total=False):
@ -88,3 +89,4 @@ class Embed(TypedDict, total=False):
provider: EmbedProvider
author: EmbedAuthor
fields: List[EmbedField]
flags: int

4
discord/types/guild.py

@ -179,8 +179,8 @@ class GuildMFALevel(TypedDict):
class ChannelPositionUpdate(TypedDict):
id: Snowflake
position: Optional[int]
lock_permissions: Optional[bool]
parent_id: Optional[Snowflake]
lock_permissions: NotRequired[Optional[bool]]
parent_id: NotRequired[Optional[Snowflake]]
class _RolePositionRequired(TypedDict):

34
discord/types/interactions.py

@ -42,6 +42,16 @@ if TYPE_CHECKING:
InteractionType = Literal[1, 2, 3, 4, 5]
InteractionResponseType = Literal[
1,
4,
5,
6,
7,
8,
9,
10,
]
InteractionContextType = Literal[0, 1, 2]
InteractionInstallationType = Literal[0, 1]
@ -301,3 +311,27 @@ MessageInteractionMetadata = Union[
MessageComponentMessageInteractionMetadata,
ModalSubmitMessageInteractionMetadata,
]
class InteractionCallbackResponse(TypedDict):
id: Snowflake
type: InteractionType
activity_instance_id: NotRequired[str]
response_message_id: NotRequired[Snowflake]
response_message_loading: NotRequired[bool]
response_message_ephemeral: NotRequired[bool]
class InteractionCallbackActivity(TypedDict):
id: str
class InteractionCallbackResource(TypedDict):
type: InteractionResponseType
activity_instance: NotRequired[InteractionCallbackActivity]
message: NotRequired[Message]
class InteractionCallback(TypedDict):
interaction: InteractionCallbackResponse
resource: NotRequired[InteractionCallbackResource]

5
discord/ui/select.py

@ -21,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
from typing import (
Any,
@ -330,7 +331,9 @@ class BaseSelect(Item[V]):
values = selected_values.get({})
payload: List[PossibleValue]
try:
resolved = Namespace._get_resolved_items(interaction, data['resolved'])
resolved = Namespace._get_resolved_items(
interaction, data['resolved'] # pyright: ignore[reportTypedDictNotRequiredAccess]
)
payload = list(resolved.values())
except KeyError:
payload = data.get("values", []) # type: ignore

7
discord/ui/view.py

@ -177,7 +177,7 @@ class View:
children = []
for func in self.__view_children_items__:
item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__)
item.callback = _ViewCallback(func, self, item)
item.callback = _ViewCallback(func, self, item) # type: ignore
item._view = self
setattr(self, func.__name__, item)
children.append(item)
@ -214,6 +214,11 @@ class View:
# Wait N seconds to see if timeout data has been refreshed
await asyncio.sleep(self.__timeout_expiry - now)
def is_dispatchable(self) -> bool:
# this is used by webhooks to check whether a view requires a state attached
# or not, this simply is, whether a view has a component other than a url button
return any(item.is_dispatchable() for item in self.children)
def to_components(self) -> List[Dict[str, Any]]:
def key(item: Item) -> int:
return item._rendered_row or 0

6
discord/utils.py

@ -714,13 +714,13 @@ async def maybe_coroutine(f: MaybeAwaitableFunc[P, T], *args: P.args, **kwargs:
if _isawaitable(value):
return await value
else:
return value # type: ignore
return value
async def async_all(
gen: Iterable[Union[T, Awaitable[T]]],
*,
check: Callable[[Union[T, Awaitable[T]]], TypeGuard[Awaitable[T]]] = _isawaitable,
check: Callable[[Union[T, Awaitable[T]]], TypeGuard[Awaitable[T]]] = _isawaitable, # type: ignore
) -> bool:
for elem in gen:
if check(elem):
@ -1121,7 +1121,7 @@ def flatten_literal_params(parameters: Iterable[Any]) -> Tuple[Any, ...]:
literal_cls = type(Literal[0])
for p in parameters:
if isinstance(p, literal_cls):
params.extend(p.__args__)
params.extend(p.__args__) # type: ignore
else:
params.append(p)
return tuple(params)

2
discord/voice_state.py

@ -344,7 +344,7 @@ class VoiceConnectionState:
elif self.state is not ConnectionFlowState.disconnected:
# eventual consistency
if previous_token == self.token and previous_server_id == self.server_id and previous_token == self.token:
if previous_token == self.token and previous_server_id == self.server_id and previous_endpoint == self.endpoint:
return
_log.debug('Unexpected server update event, attempting to handle')

45
discord/webhook/async_.py

@ -90,6 +90,9 @@ if TYPE_CHECKING:
)
from ..types.emoji import PartialEmoji as PartialEmojiPayload
from ..types.snowflake import SnowflakeList
from ..types.interactions import (
InteractionCallback as InteractionCallbackResponsePayload,
)
BE = TypeVar('BE', bound=BaseException)
_State = Union[ConnectionState, '_WebhookState']
@ -310,8 +313,9 @@ class AsyncWebhookAdapter:
files: Optional[Sequence[File]] = None,
thread_id: Optional[int] = None,
wait: bool = False,
with_components: bool = False,
) -> Response[Optional[MessagePayload]]:
params = {'wait': int(wait)}
params = {'wait': int(wait), 'with_components': int(with_components)}
if thread_id:
params['thread_id'] = thread_id
route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
@ -434,13 +438,14 @@ class AsyncWebhookAdapter:
proxy: Optional[str] = None,
proxy_auth: Optional[aiohttp.BasicAuth] = None,
params: MultipartParameters,
) -> Response[None]:
) -> Response[InteractionCallbackResponsePayload]:
route = Route(
'POST',
'/interactions/{webhook_id}/{webhook_token}/callback',
webhook_id=interaction_id,
webhook_token=token,
)
request_params = {'with_response': '1'}
if params.files:
return self.request(
@ -450,9 +455,17 @@ class AsyncWebhookAdapter:
proxy_auth=proxy_auth,
files=params.files,
multipart=params.multipart,
params=request_params,
)
else:
return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, payload=params.payload)
return self.request(
route,
session=session,
proxy=proxy,
proxy_auth=proxy_auth,
payload=params.payload,
params=request_params,
)
def get_original_interaction_response(
self,
@ -660,6 +673,11 @@ class PartialWebhookChannel(Hashable):
def __repr__(self) -> str:
return f'<PartialWebhookChannel name={self.name!r} id={self.id}>'
@property
def mention(self) -> str:
""":class:`str`: The string that allows you to mention the channel that the webhook is following."""
return f'<#{self.id}>'
class PartialWebhookGuild(Hashable):
"""Represents a partial guild for webhooks.
@ -1710,10 +1728,9 @@ class Webhook(BaseWebhook):
.. versionadded:: 1.4
view: :class:`discord.ui.View`
The view to send with the message. You can only send a view
if this webhook is not partial and has state attached. A
webhook has state attached if the webhook is managed by the
library.
The view to send with the message. If the webhook is partial or
is not managed by the library, then you can only send URL buttons.
Otherwise, you can send views with any type of components.
.. versionadded:: 2.0
thread: :class:`~discord.abc.Snowflake`
@ -1765,7 +1782,8 @@ class Webhook(BaseWebhook):
The length of ``embeds`` was invalid, there was no token
associated with this webhook or ``ephemeral`` was passed
with the improper webhook type or there was no state
attached with this webhook when giving it a view.
attached with this webhook when giving it a view that had
components other than URL buttons.
Returns
---------
@ -1795,13 +1813,15 @@ class Webhook(BaseWebhook):
wait = True
if view is not MISSING:
if isinstance(self._state, _WebhookState):
raise ValueError('Webhook views require an associated state with the webhook')
if not hasattr(view, '__discord_ui_view__'):
raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}')
if ephemeral is True and view.timeout is None:
if isinstance(self._state, _WebhookState) and view.is_dispatchable():
raise ValueError(
'Webhook views with any component other than URL buttons require an associated state with the webhook'
)
if ephemeral is True and view.timeout is None and view.is_dispatchable():
view.timeout = 15 * 60.0
if thread_name is not MISSING and thread is not MISSING:
@ -1845,6 +1865,7 @@ class Webhook(BaseWebhook):
files=params.files,
thread_id=thread_id,
wait=wait,
with_components=view is not MISSING,
)
msg = None

26
discord/webhook/sync.py

@ -66,6 +66,7 @@ if TYPE_CHECKING:
from ..message import Attachment
from ..abc import Snowflake
from ..state import ConnectionState
from ..ui import View
from ..types.webhook import (
Webhook as WebhookPayload,
)
@ -290,8 +291,9 @@ class WebhookAdapter:
files: Optional[Sequence[File]] = None,
thread_id: Optional[int] = None,
wait: bool = False,
with_components: bool = False,
) -> MessagePayload:
params = {'wait': int(wait)}
params = {'wait': int(wait), 'with_components': int(with_components)}
if thread_id:
params['thread_id'] = thread_id
route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
@ -919,6 +921,7 @@ class SyncWebhook(BaseWebhook):
silent: bool = False,
applied_tags: List[ForumTag] = MISSING,
poll: Poll = MISSING,
view: View = MISSING,
) -> Optional[SyncWebhookMessage]:
"""Sends a message using the webhook.
@ -991,6 +994,13 @@ class SyncWebhook(BaseWebhook):
When sending a Poll via webhook, you cannot manually end it.
.. versionadded:: 2.4
view: :class:`~discord.ui.View`
The view to send with the message. This can only have URL buttons, which donnot
require a state to be attached to it.
If you want to send a view with any component attached to it, check :meth:`Webhook.send`.
.. versionadded:: 2.5
Raises
--------
@ -1004,8 +1014,9 @@ class SyncWebhook(BaseWebhook):
You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
or ``thread`` and ``thread_name``.
ValueError
The length of ``embeds`` was invalid or
there was no token associated with this webhook.
The length of ``embeds`` was invalid, there was no token
associated with this webhook or you tried to send a view
with components other than URL buttons.
Returns
---------
@ -1027,6 +1038,13 @@ class SyncWebhook(BaseWebhook):
else:
flags = MISSING
if view is not MISSING:
if not hasattr(view, '__discord_ui_view__'):
raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}')
if view.is_dispatchable():
raise ValueError('SyncWebhook views can only contain URL buttons')
if thread_name is not MISSING and thread is not MISSING:
raise TypeError('Cannot mix thread_name and thread keyword arguments.')
@ -1050,6 +1068,7 @@ class SyncWebhook(BaseWebhook):
flags=flags,
applied_tags=applied_tag_ids,
poll=poll,
view=view,
) as params:
adapter: WebhookAdapter = _get_webhook_adapter()
thread_id: Optional[int] = None
@ -1065,6 +1084,7 @@ class SyncWebhook(BaseWebhook):
files=params.files,
thread_id=thread_id,
wait=wait,
with_components=view is not MISSING,
)
msg = None

2
discord/widget.py

@ -184,7 +184,7 @@ class WidgetMember(BaseUser):
self.suppress: Optional[bool] = data.get('suppress', False)
try:
game = data['game']
game = data['game'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
activity = None
else:

16
docs/api.rst

@ -80,6 +80,14 @@ AppInstallParams
.. autoclass:: AppInstallParams()
:members:
IntegrationTypeConfig
~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: IntegrationTypeConfig
.. autoclass:: IntegrationTypeConfig()
:members:
Team
~~~~~
@ -5708,6 +5716,14 @@ SKUFlags
.. autoclass:: SKUFlags()
:members:
EmbedFlags
~~~~~~~~~~
.. attributetable:: EmbedFlags
.. autoclass:: EmbedFlags()
:members:
ForumTag
~~~~~~~~~

5
docs/ext/commands/api.rst

@ -531,6 +531,11 @@ Converters
.. autoclass:: discord.ext.commands.ScheduledEventConverter
:members:
.. attributetable:: discord.ext.commands.SoundboardSoundConverter
.. autoclass:: discord.ext.commands.SoundboardSoundConverter
:members:
.. attributetable:: discord.ext.commands.clean_content
.. autoclass:: discord.ext.commands.clean_content

16
docs/interactions/api.rst

@ -28,6 +28,22 @@ InteractionResponse
.. autoclass:: InteractionResponse()
:members:
InteractionCallbackResponse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: InteractionCallbackResponse
.. autoclass:: InteractionCallbackResponse()
:members:
InteractionCallbackActivityInstance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: InteractionCallbackActivityInstance
.. autoclass:: InteractionCallbackActivityInstance()
:members:
InteractionMessage
~~~~~~~~~~~~~~~~~~~

154
docs/whats_new.rst

@ -11,6 +11,160 @@ Changelog
This page keeps a detailed human friendly rendering of what's new and changed
in specific versions.
.. _vp2p5p0:
v2.5.0
-------
New Features
~~~~~~~~~~~~~
- Add support for message forwarding (:issue:`9950`)
- Adds :class:`MessageReferenceType`
- Adds :class:`MessageSnapshot`
- Adds ``type`` parameter to :class:`MessageReference`, :meth:`MessageReference.from_message`, and :meth:`PartialMessage.to_reference`
- Add :meth:`PartialMessage.forward`
- Add SKU subscriptions support (:issue:`9930`)
- Adds new events :func:`on_subscription_create`, :func:`on_subscription_update`, and :func:`on_subscription_delete`
- Add :class:`SubscriptionStatus` enum
- Add :class:`Subscription` model
- Add :meth:`SKU.fetch_subscription` and :meth:`SKU.subscriptions`
- Add support for application emojis (:issue:`9891`)
- Add :meth:`Client.create_application_emoji`
- Add :meth:`Client.fetch_application_emoji`
- Add :meth:`Client.fetch_application_emojis`
- Add :meth:`Emoji.is_application_owned`
- Support for Soundboard and VC effects (:issue:`9349`)
- Add :class:`BaseSoundboardSound`, :class:`SoundboardDefaultSound`, and :class:`SoundboardSound`
- Add :class:`VoiceChannelEffect`
- Add :class:`VoiceChannelEffectAnimation`
- Add :class:`VoiceChannelEffectAnimationType`
- Add :class:`VoiceChannelSoundEffect`
- Add :meth:`VoiceChannel.send_sound`
- Add new audit log actions: :attr:`AuditLogAction.soundboard_sound_create`, :attr:`AuditLogAction.soundboard_sound_update`, and :attr:`AuditLogAction.soundboard_sound_delete`.
- Add :attr:`Intents.expressions` and make :attr:`Intents.emojis` and :attr:`Intents.emojis_and_stickers` aliases of that intent.
- Add new events: :func:`on_soundboard_sound_create`, :func:`on_soundboard_sound_update`, :func:`on_soundboard_sound_delete`, and :func:`on_voice_channel_effect`.
- Add methods and properties dealing with soundboards:
- :attr:`Client.soundboard_sounds`
- :attr:`Guild.soundboard_sounds`
- :meth:`Client.get_soundboard_sound`
- :meth:`Guild.get_soundboard_sound`
- :meth:`Client.fetch_soundboard_default_sounds`
- :meth:`Guild.fetch_soundboard_sound`
- :meth:`Guild.fetch_soundboard_sounds`
- :meth:`Guild.create_soundboard_sound`
- Add support for retrieving interaction responses when sending a response (:issue:`9957`)
- Methods from :class:`InteractionResponse` now return :class:`InteractionCallbackResponse`
- Depending on the interaction response type, :attr:`InteractionCallbackResponse.resource` will be different
- Add :attr:`PartialWebhookChannel.mention` attribute (:issue:`10101`)
- Add support for sending stateless views for :class:`SyncWebhook` or webhooks with no state (:issue:`10089`)
- Add
- Add richer :meth:`Role.move` interface (:issue:`10100`)
- Add support for :class:`EmbedFlags` via :attr:`Embed.flags` (:issue:`10085`)
- Add new flags for :class:`AttachmentFlags` (:issue:`10085`)
- Add :func:`on_raw_presence_update` event that does not depend on cache state (:issue:`10048`)
- This requires setting the ``enable_raw_presences`` keyword argument within :class:`Client`.
- Add :attr:`ForumChannel.members` property. (:issue:`10034`)
- Add ``exclude_deleted`` parameter to :meth:`Client.entitlements` (:issue:`10027`)
- Add :meth:`Client.fetch_guild_preview` (:issue:`9986`)
- Add :meth:`AutoShardedClient.fetch_session_start_limits` (:issue:`10007`)
- Add :attr:`PartialMessageable.mention` (:issue:`9988`)
- Add command target to :class:`MessageInteractionMetadata` (:issue:`10004`)
- :attr:`MessageInteractionMetadata.target_user`
- :attr:`MessageInteractionMetadata.target_message_id`
- :attr:`MessageInteractionMetadata.target_message`
- Add :attr:`Message.forward` flag (:issue:`9978`)
- Add support for purchase notification messages (:issue:`9906`)
- Add new type :attr:`MessageType.purchase_notification`
- Add new models :class:`GuildProductPurchase` and :class:`PurchaseNotification`
- Add :attr:`Message.purchase_notification`
- Add ``category`` parameter to :meth:`.abc.GuildChannel.clone` (:issue:`9941`)
- Add support for message call (:issue:`9911`)
- Add new models :class:`CallMessage`
- Add :attr:`Message.call` attribute
- Parse full message for message edit event (:issue:`10035`)
- Adds :attr:`RawMessageUpdateEvent.message` attribute
- Potentially speeds up :func:`on_message_edit` by no longer copying data
- Add support for retrieving and editing integration type configuration (:issue:`9818`)
- This adds :class:`IntegrationTypeConfig`
- Retrievable via :attr:`AppInfo.guild_integration_config` and :attr:`AppInfo.user_integration_config`.
- Editable via :meth:`AppInfo.edit`
- Allow passing ``None`` for ``scopes`` parameter in :func:`utils.oauth_url` (:issue:`10078`)
- Add support for :attr:`MessageType.poll_result` messages (:issue:`9905`)
- Add various new :class:`MessageFlags`
- Add :meth:`Member.fetch_voice` (:issue:`9908`)
- Add :attr:`Guild.dm_spam_detected_at` and :meth:`Guild.is_dm_spam_detected` (:issue:`9808`)
- Add :attr:`Guild.raid_detected_at` and :meth:`Guild.is_raid_detected` (:issue:`9808`)
- Add :meth:`Client.fetch_premium_sticker_pack` (:issue:`9909`)
- Add :attr:`AppInfo.approximate_user_install_count` (:issue:`9915`)
- Add :meth:`Guild.fetch_role` (:issue:`9921`)
- Add :attr:`Attachment.title` (:issue:`9904`)
- Add :attr:`Member.guild_banner` and :attr:`Member.display_banner`
- Re-add ``connector`` parameter that was removed during v2.0 (:issue:`9900`)
- |commands| Add :class:`~discord.ext.commands.SoundboardSoundConverter` (:issue:`9973`)
Bug Fixes
~~~~~~~~~~
- Change the default file size limit for :attr:`Guild.filesize_limit` to match new Discord limit of 10 MiB (:issue:`10084`)
- Handle improper 1000 close code closures by Discord
- This fixes an issue causing excessive IDENTIFY in large bots
- Fix potential performance regression when dealing with cookies in the library owned session (:issue:`9916`)
- Add support for AEAD XChaCha20 Poly1305 encryption mode (:issue:`9953`)
- This allows voice to continue working when the older encryption modes eventually get removed.
- Support for DAVE is still tentative.
- Fix large performance regression due to polls when creating messages
- Fix cases where :attr:`Member.roles` contains a ``None`` role (:issue:`10093`)
- Update all channel clone implementations to work as expected (:issue:`9935`)
- Fix bug in :meth:`Client.entitlements` only returning 100 entries (:issue:`10051`)
- Fix :meth:`TextChannel.clone` always sending slowmode when not applicable to news channels (:issue:`9967`)
- Fix :attr:`Message.system_content` for :attr:`MessageType.role_subscription_purchase` renewals (:issue:`9955`)
- Fix :attr:`Sticker.url` for GIF stickers (:issue:`9913`)
- Fix :attr:`User.default_avatar` for team users and webhooks (:issue:`9907`)
- Fix potential rounding error in :attr:`Poll.duration` (:issue:`9903`)
- Fix introduced potential TypeError when raising :exc:`app_commands.CommandSyncFailure`
- Fix :attr:`AuditLogEntry.target` causing errors for :attr:`AuditLogAction.message_pin` and :attr:`AuditLogAction.message_unpin` actions (:issue:`10061`).
- Fix incorrect :class:`ui.Select` maximum option check (:issue:`9878`, :issue:`9879`)
- Fix path sanitisation for absolute Windows paths when using ``__main__`` (:issue:`10096`, :issue:`10097`)
- |tasks| Fix race condition when setting timer handle when using uvloop (:issue:`10020`)
- |commands| Fix issue with category cooldowns outside of guild channels (:issue:`9959`)
- |commands| Fix :meth:`Context.defer <ext.commands.Context.defer>` unconditionally deferring
- |commands| Fix callable FlagConverter defaults on hybrid commands not being called (:issue:`10037`)
- |commands| Unwrap :class:`~discord.ext.commands.Parameter` if given as default to :func:`~ext.commands.parameter` (:issue:`9977`)
- |commands| Fix fallback behaviour not being respected when calling replace for :class:`~.ext.commands.Parameter` (:issue:`10076`, :issue:`10077`)
- |commands| Respect ``enabled`` keyword argument for hybrid app commands (:issue:`10001`)
Miscellaneous
~~~~~~~~~~~~~~
- Use a fallback package for ``audioop`` to allow the library to work in Python 3.13 or newer.
- Remove ``aiodns`` from being used on Windows (:issue:`9898`)
- Add zstd gateway compression to ``speed`` extras (:issue:`9947`)
- This can be installed using ``discord.py[speed]``
- Add proxy support fetching from the CDN (:issue:`9966`)
- Remove ``/`` from being safe from URI encoding when constructing paths internally
- Sanitize invite argument before calling the invite info endpoint
- Avoid returning in finally in specific places to prevent exception swallowing (:issue:`9981`, :issue:`9984`)
- Enforce and create random nonces when creating messages throughout the library
- Revert IPv6 block in the library (:issue:`9870`)
- Allow passing :class:`Permissions` object to :func:`app_commands.default_permissions` decorator (:issue:`9951`, :issue:`9971`)
.. _vp2p4p0:
v2.4.0

Loading…
Cancel
Save