Browse Source

Merge branch 'master' into soundboard-mention-property

pull/10081/head
owocado 1 month ago
committed by GitHub
parent
commit
21b0e5d96f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/lint.yml
  2. 5
      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. 263
      discord/client.py
  12. 6
      discord/components.py
  13. 21
      discord/embeds.py
  14. 13
      discord/enums.py
  15. 4
      discord/ext/commands/bot.py
  16. 11
      discord/ext/commands/context.py
  17. 6
      discord/ext/commands/converter.py
  18. 2
      discord/ext/commands/core.py
  19. 9
      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. 14
      discord/guild.py
  25. 1
      discord/http.py
  26. 214
      discord/interactions.py
  27. 2
      discord/invite.py
  28. 98
      discord/member.py
  29. 57
      discord/message.py
  30. 105
      discord/poll.py
  31. 150
      discord/presences.py
  32. 26
      discord/raw_models.py
  33. 110
      discord/role.py
  34. 68
      discord/state.py
  35. 4
      discord/subscription.py
  36. 2
      discord/threads.py
  37. 7
      discord/types/appinfo.py
  38. 4
      discord/types/embed.py
  39. 4
      discord/types/guild.py
  40. 34
      discord/types/interactions.py
  41. 1
      discord/types/message.py
  42. 1
      discord/types/subscription.py
  43. 5
      discord/ui/select.py
  44. 7
      discord/ui/view.py
  45. 16
      discord/utils.py
  46. 2
      discord/voice_state.py
  47. 45
      discord/webhook/async_.py
  48. 26
      discord/webhook/sync.py
  49. 2
      discord/widget.py
  50. 75
      docs/api.rst
  51. 5
      docs/ext/commands/api.rst
  52. 11
      docs/faq.rst
  53. 16
      docs/interactions/api.rst
  54. 154
      docs/whats_new.rst
  55. 1
      setup.py

2
.github/workflows/lint.yml

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

5
discord/__init__.py

@ -13,7 +13,7 @@ __title__ = 'discord'
__author__ = 'Rapptz' __author__ = 'Rapptz'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright 2015-present Rapptz' __copyright__ = 'Copyright 2015-present Rapptz'
__version__ = '2.5.0a' __version__ = '2.6.0a'
__path__ = __import__('pkgutil').extend_path(__path__, __name__) __path__ = __import__('pkgutil').extend_path(__path__, __name__)
@ -72,6 +72,7 @@ from .automod import *
from .poll import * from .poll import *
from .soundboard import * from .soundboard import *
from .subscription import * from .subscription import *
from .presences import *
class VersionInfo(NamedTuple): class VersionInfo(NamedTuple):
@ -82,7 +83,7 @@ class VersionInfo(NamedTuple):
serial: int 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()) logging.getLogger(__name__).addHandler(logging.NullHandler())

10
discord/__main__.py

@ -28,7 +28,7 @@ from typing import Optional, Tuple, Dict
import argparse import argparse
import sys import sys
from pathlib import Path from pathlib import Path, PurePath, PureWindowsPath
import discord import discord
import importlib.metadata 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: if len(name) <= 4 and name.upper() in forbidden:
parser.error('invalid directory name given, use a different one') 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: if replace_spaces:
name = name.replace(' ', '-') name = name.replace(' ', '-')
return Path(name) return Path(name)

7
discord/abc.py

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

10
discord/activity.py

@ -273,7 +273,7 @@ class Activity(BaseActivity):
def start(self) -> Optional[datetime.datetime]: def start(self) -> Optional[datetime.datetime]:
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable.""" """Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable."""
try: try:
timestamp = self.timestamps['start'] / 1000 timestamp = self.timestamps['start'] / 1000 # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
return None return None
else: else:
@ -283,7 +283,7 @@ class Activity(BaseActivity):
def end(self) -> Optional[datetime.datetime]: def end(self) -> Optional[datetime.datetime]:
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable.""" """Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable."""
try: try:
timestamp = self.timestamps['end'] / 1000 timestamp = self.timestamps['end'] / 1000 # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
return None return None
else: else:
@ -293,7 +293,7 @@ class Activity(BaseActivity):
def large_image_url(self) -> Optional[str]: def large_image_url(self) -> Optional[str]:
"""Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity, if applicable.""" """Optional[:class:`str`]: Returns a URL pointing to the large image asset of this activity, if applicable."""
try: try:
large_image = self.assets['large_image'] large_image = self.assets['large_image'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
return None return None
else: else:
@ -303,7 +303,7 @@ class Activity(BaseActivity):
def small_image_url(self) -> Optional[str]: def small_image_url(self) -> Optional[str]:
"""Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity, if applicable.""" """Optional[:class:`str`]: Returns a URL pointing to the small image asset of this activity, if applicable."""
try: try:
small_image = self.assets['small_image'] small_image = self.assets['small_image'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
return None return None
else: else:
@ -525,7 +525,7 @@ class Streaming(BaseActivity):
""" """
try: try:
name = self.assets['large_image'] name = self.assets['large_image'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
return None return None
else: 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__', []) predicates = getattr(param.autocomplete, '__discord_app_commands_checks__', [])
if predicates: if predicates:
try: 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: except Exception:
passed = False passed = False
@ -1014,7 +1014,7 @@ class Command(Generic[GroupT, P, T]):
if not predicates: if not predicates:
return True 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]: def error(self, coro: Error[GroupT]) -> Error[GroupT]:
"""A decorator that registers a coroutine as a local error handler. """A decorator that registers a coroutine as a local error handler.
@ -1308,7 +1308,7 @@ class ContextMenu:
if not predicates: if not predicates:
return True 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: def _has_any_error_handlers(self) -> bool:
return self.on_error is not None return self.on_error is not None
@ -1842,7 +1842,7 @@ class Group:
if len(params) != 2: if len(params) != 2:
raise TypeError('The error handler must have 2 parameters.') raise TypeError('The error handler must have 2 parameters.')
self.on_error = coro self.on_error = coro # type: ignore
return coro return coro
async def interaction_check(self, interaction: Interaction, /) -> bool: async def interaction_check(self, interaction: Interaction, /) -> bool:

2
discord/app_commands/transformers.py

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

2
discord/app_commands/tree.py

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

129
discord/appinfo.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import List, TYPE_CHECKING, Optional from typing import List, TYPE_CHECKING, Literal, Optional
from . import utils from . import utils
from .asset import Asset from .asset import Asset
@ -41,6 +41,7 @@ if TYPE_CHECKING:
PartialAppInfo as PartialAppInfoPayload, PartialAppInfo as PartialAppInfoPayload,
Team as TeamPayload, Team as TeamPayload,
InstallParams as InstallParamsPayload, InstallParams as InstallParamsPayload,
AppIntegrationTypeConfig as AppIntegrationTypeConfigPayload,
) )
from .user import User from .user import User
from .state import ConnectionState from .state import ConnectionState
@ -49,6 +50,7 @@ __all__ = (
'AppInfo', 'AppInfo',
'PartialAppInfo', 'PartialAppInfo',
'AppInstallParams', 'AppInstallParams',
'IntegrationTypeConfig',
) )
@ -180,6 +182,7 @@ class AppInfo:
'redirect_uris', 'redirect_uris',
'approximate_guild_count', 'approximate_guild_count',
'approximate_user_install_count', 'approximate_user_install_count',
'_integration_types_config',
) )
def __init__(self, state: ConnectionState, data: AppInfoPayload): def __init__(self, state: ConnectionState, data: AppInfoPayload):
@ -218,6 +221,9 @@ class AppInfo:
self.redirect_uris: List[str] = data.get('redirect_uris', []) self.redirect_uris: List[str] = data.get('redirect_uris', [])
self.approximate_guild_count: int = data.get('approximate_guild_count', 0) 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.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: def __repr__(self) -> str:
return ( return (
@ -260,6 +266,36 @@ class AppInfo:
""" """
return ApplicationFlags._from_value(self._flags) 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( async def edit(
self, self,
*, *,
@ -274,6 +310,10 @@ class AppInfo:
cover_image: Optional[bytes] = MISSING, cover_image: Optional[bytes] = MISSING,
interactions_endpoint_url: Optional[str] = MISSING, interactions_endpoint_url: Optional[str] = MISSING,
tags: Optional[List[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: ) -> AppInfo:
r"""|coro| r"""|coro|
@ -315,6 +355,24 @@ class AppInfo:
over the gateway. Can be ``None`` to remove the URL. over the gateway. Can be ``None`` to remove the URL.
tags: Optional[List[:class:`str`]] tags: Optional[List[:class:`str`]]
The new list of tags describing the functionality of the application. Can be ``None`` to remove the tags. 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`] reason: Optional[:class:`str`]
The reason for editing the application. Shows up on the audit log. The reason for editing the application. Shows up on the audit log.
@ -324,7 +382,8 @@ class AppInfo:
Editing the application failed Editing the application failed
ValueError ValueError
The image format passed in to ``icon`` or ``cover_image`` is invalid. This is also raised 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 Returns
------- -------
@ -364,7 +423,7 @@ class AppInfo:
else: else:
if install_params_permissions is not MISSING: 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 not MISSING:
if flags is None: if flags is None:
@ -389,6 +448,51 @@ class AppInfo:
if tags is not MISSING: if tags is not MISSING:
payload['tags'] = tags 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) data = await self._state.http.edit_application_info(reason=reason, payload=payload)
return AppInfo(data=data, state=self._state) return AppInfo(data=data, state=self._state)
@ -520,3 +624,22 @@ class AppInstallParams:
def __init__(self, data: InstallParamsPayload) -> None: def __init__(self, data: InstallParamsPayload) -> None:
self.scopes: List[str] = data.get('scopes', []) self.scopes: List[str] = data.get('scopes', [])
self.permissions: Permissions = Permissions(int(data['permissions'])) 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]: 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) 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) 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]: def _convert_target_stage_instance(self, target_id: int) -> Union[StageInstance, Object]:

263
discord/client.py

@ -237,6 +237,15 @@ class Client:
To enable these events, this must be set to ``True``. Defaults to ``False``. To enable these events, this must be set to ``True``. Defaults to ``False``.
.. versionadded:: 2.0 .. versionadded:: 2.0
enable_raw_presences: :class:`bool`
Whether to manually enable or disable the :func:`on_raw_presence_update` event.
Setting this flag to ``True`` requires :attr:`Intents.presences` to be enabled.
By default, this flag is set to ``True`` only when :attr:`Intents.presences` is enabled and :attr:`Intents.members`
is disabled, otherwise it's set to ``False``.
.. versionadded:: 2.5
http_trace: :class:`aiohttp.TraceConfig` http_trace: :class:`aiohttp.TraceConfig`
The trace configuration to use for tracking HTTP requests the library does using ``aiohttp``. The trace configuration to use for tracking HTTP requests the library does using ``aiohttp``.
This allows you to check requests the library is using. For more information, check the This allows you to check requests the library is using. For more information, check the
@ -1204,8 +1213,8 @@ class Client:
event: Literal['raw_app_command_permissions_update'], event: Literal['raw_app_command_permissions_update'],
/, /,
*, *,
check: Optional[Callable[[RawAppCommandPermissionsUpdateEvent], bool]], check: Optional[Callable[[RawAppCommandPermissionsUpdateEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawAppCommandPermissionsUpdateEvent: ) -> RawAppCommandPermissionsUpdateEvent:
... ...
@ -1215,8 +1224,8 @@ class Client:
event: Literal['app_command_completion'], event: Literal['app_command_completion'],
/, /,
*, *,
check: Optional[Callable[[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]], bool]], check: Optional[Callable[[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]]: ) -> Tuple[Interaction[Self], Union[Command[Any, ..., Any], ContextMenu]]:
... ...
@ -1228,8 +1237,8 @@ class Client:
event: Literal['automod_rule_create', 'automod_rule_update', 'automod_rule_delete'], event: Literal['automod_rule_create', 'automod_rule_update', 'automod_rule_delete'],
/, /,
*, *,
check: Optional[Callable[[AutoModRule], bool]], check: Optional[Callable[[AutoModRule], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> AutoModRule: ) -> AutoModRule:
... ...
@ -1239,8 +1248,8 @@ class Client:
event: Literal['automod_action'], event: Literal['automod_action'],
/, /,
*, *,
check: Optional[Callable[[AutoModAction], bool]], check: Optional[Callable[[AutoModAction], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> AutoModAction: ) -> AutoModAction:
... ...
@ -1252,8 +1261,8 @@ class Client:
event: Literal['private_channel_update'], event: Literal['private_channel_update'],
/, /,
*, *,
check: Optional[Callable[[GroupChannel, GroupChannel], bool]], check: Optional[Callable[[GroupChannel, GroupChannel], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[GroupChannel, GroupChannel]: ) -> Tuple[GroupChannel, GroupChannel]:
... ...
@ -1263,8 +1272,8 @@ class Client:
event: Literal['private_channel_pins_update'], event: Literal['private_channel_pins_update'],
/, /,
*, *,
check: Optional[Callable[[PrivateChannel, datetime.datetime], bool]], check: Optional[Callable[[PrivateChannel, datetime.datetime], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[PrivateChannel, datetime.datetime]: ) -> Tuple[PrivateChannel, datetime.datetime]:
... ...
@ -1274,8 +1283,8 @@ class Client:
event: Literal['guild_channel_delete', 'guild_channel_create'], event: Literal['guild_channel_delete', 'guild_channel_create'],
/, /,
*, *,
check: Optional[Callable[[GuildChannel], bool]], check: Optional[Callable[[GuildChannel], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> GuildChannel: ) -> GuildChannel:
... ...
@ -1285,8 +1294,8 @@ class Client:
event: Literal['guild_channel_update'], event: Literal['guild_channel_update'],
/, /,
*, *,
check: Optional[Callable[[GuildChannel, GuildChannel], bool]], check: Optional[Callable[[GuildChannel, GuildChannel], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[GuildChannel, GuildChannel]: ) -> Tuple[GuildChannel, GuildChannel]:
... ...
@ -1302,7 +1311,7 @@ class Client:
bool, bool,
] ]
], ],
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Union[GuildChannel, Thread], Optional[datetime.datetime]]: ) -> Tuple[Union[GuildChannel, Thread], Optional[datetime.datetime]]:
... ...
@ -1312,8 +1321,8 @@ class Client:
event: Literal['typing'], event: Literal['typing'],
/, /,
*, *,
check: Optional[Callable[[Messageable, Union[User, Member], datetime.datetime], bool]], check: Optional[Callable[[Messageable, Union[User, Member], datetime.datetime], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Messageable, Union[User, Member], datetime.datetime]: ) -> Tuple[Messageable, Union[User, Member], datetime.datetime]:
... ...
@ -1323,8 +1332,8 @@ class Client:
event: Literal['raw_typing'], event: Literal['raw_typing'],
/, /,
*, *,
check: Optional[Callable[[RawTypingEvent], bool]], check: Optional[Callable[[RawTypingEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawTypingEvent: ) -> RawTypingEvent:
... ...
@ -1336,8 +1345,8 @@ class Client:
event: Literal['connect', 'disconnect', 'ready', 'resumed'], event: Literal['connect', 'disconnect', 'ready', 'resumed'],
/, /,
*, *,
check: Optional[Callable[[], bool]], check: Optional[Callable[[], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> None: ) -> None:
... ...
@ -1347,8 +1356,8 @@ class Client:
event: Literal['shard_connect', 'shard_disconnect', 'shard_ready', 'shard_resumed'], event: Literal['shard_connect', 'shard_disconnect', 'shard_ready', 'shard_resumed'],
/, /,
*, *,
check: Optional[Callable[[int], bool]], check: Optional[Callable[[int], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> int: ) -> int:
... ...
@ -1358,8 +1367,8 @@ class Client:
event: Literal['socket_event_type', 'socket_raw_receive'], event: Literal['socket_event_type', 'socket_raw_receive'],
/, /,
*, *,
check: Optional[Callable[[str], bool]], check: Optional[Callable[[str], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> str: ) -> str:
... ...
@ -1369,8 +1378,8 @@ class Client:
event: Literal['socket_raw_send'], event: Literal['socket_raw_send'],
/, /,
*, *,
check: Optional[Callable[[Union[str, bytes]], bool]], check: Optional[Callable[[Union[str, bytes]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Union[str, bytes]: ) -> Union[str, bytes]:
... ...
@ -1381,8 +1390,8 @@ class Client:
event: Literal['entitlement_create', 'entitlement_update', 'entitlement_delete'], event: Literal['entitlement_create', 'entitlement_update', 'entitlement_delete'],
/, /,
*, *,
check: Optional[Callable[[Entitlement], bool]], check: Optional[Callable[[Entitlement], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Entitlement: ) -> Entitlement:
... ...
@ -1399,8 +1408,8 @@ class Client:
], ],
/, /,
*, *,
check: Optional[Callable[[Guild], bool]], check: Optional[Callable[[Guild], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Guild: ) -> Guild:
... ...
@ -1410,8 +1419,8 @@ class Client:
event: Literal['guild_update'], event: Literal['guild_update'],
/, /,
*, *,
check: Optional[Callable[[Guild, Guild], bool]], check: Optional[Callable[[Guild, Guild], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Guild, Guild]: ) -> Tuple[Guild, Guild]:
... ...
@ -1421,8 +1430,8 @@ class Client:
event: Literal['guild_emojis_update'], event: Literal['guild_emojis_update'],
/, /,
*, *,
check: Optional[Callable[[Guild, Sequence[Emoji], Sequence[Emoji]], bool]], check: Optional[Callable[[Guild, Sequence[Emoji], Sequence[Emoji]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Guild, Sequence[Emoji], Sequence[Emoji]]: ) -> Tuple[Guild, Sequence[Emoji], Sequence[Emoji]]:
... ...
@ -1432,8 +1441,8 @@ class Client:
event: Literal['guild_stickers_update'], event: Literal['guild_stickers_update'],
/, /,
*, *,
check: Optional[Callable[[Guild, Sequence[GuildSticker], Sequence[GuildSticker]], bool]], check: Optional[Callable[[Guild, Sequence[GuildSticker], Sequence[GuildSticker]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Guild, Sequence[GuildSticker], Sequence[GuildSticker]]: ) -> Tuple[Guild, Sequence[GuildSticker], Sequence[GuildSticker]]:
... ...
@ -1443,8 +1452,8 @@ class Client:
event: Literal['invite_create', 'invite_delete'], event: Literal['invite_create', 'invite_delete'],
/, /,
*, *,
check: Optional[Callable[[Invite], bool]], check: Optional[Callable[[Invite], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Invite: ) -> Invite:
... ...
@ -1454,8 +1463,8 @@ class Client:
event: Literal['audit_log_entry_create'], event: Literal['audit_log_entry_create'],
/, /,
*, *,
check: Optional[Callable[[AuditLogEntry], bool]], check: Optional[Callable[[AuditLogEntry], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> AuditLogEntry: ) -> AuditLogEntry:
... ...
@ -1467,8 +1476,8 @@ class Client:
event: Literal['integration_create', 'integration_update'], event: Literal['integration_create', 'integration_update'],
/, /,
*, *,
check: Optional[Callable[[Integration], bool]], check: Optional[Callable[[Integration], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Integration: ) -> Integration:
... ...
@ -1478,8 +1487,8 @@ class Client:
event: Literal['guild_integrations_update'], event: Literal['guild_integrations_update'],
/, /,
*, *,
check: Optional[Callable[[Guild], bool]], check: Optional[Callable[[Guild], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Guild: ) -> Guild:
... ...
@ -1489,8 +1498,8 @@ class Client:
event: Literal['webhooks_update'], event: Literal['webhooks_update'],
/, /,
*, *,
check: Optional[Callable[[GuildChannel], bool]], check: Optional[Callable[[GuildChannel], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> GuildChannel: ) -> GuildChannel:
... ...
@ -1500,8 +1509,8 @@ class Client:
event: Literal['raw_integration_delete'], event: Literal['raw_integration_delete'],
/, /,
*, *,
check: Optional[Callable[[RawIntegrationDeleteEvent], bool]], check: Optional[Callable[[RawIntegrationDeleteEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawIntegrationDeleteEvent: ) -> RawIntegrationDeleteEvent:
... ...
@ -1513,8 +1522,8 @@ class Client:
event: Literal['interaction'], event: Literal['interaction'],
/, /,
*, *,
check: Optional[Callable[[Interaction[Self]], bool]], check: Optional[Callable[[Interaction[Self]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Interaction[Self]: ) -> Interaction[Self]:
... ...
@ -1526,8 +1535,8 @@ class Client:
event: Literal['member_join', 'member_remove'], event: Literal['member_join', 'member_remove'],
/, /,
*, *,
check: Optional[Callable[[Member], bool]], check: Optional[Callable[[Member], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Member: ) -> Member:
... ...
@ -1537,8 +1546,8 @@ class Client:
event: Literal['raw_member_remove'], event: Literal['raw_member_remove'],
/, /,
*, *,
check: Optional[Callable[[RawMemberRemoveEvent], bool]], check: Optional[Callable[[RawMemberRemoveEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawMemberRemoveEvent: ) -> RawMemberRemoveEvent:
... ...
@ -1548,8 +1557,8 @@ class Client:
event: Literal['member_update', 'presence_update'], event: Literal['member_update', 'presence_update'],
/, /,
*, *,
check: Optional[Callable[[Member, Member], bool]], check: Optional[Callable[[Member, Member], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Member, Member]: ) -> Tuple[Member, Member]:
... ...
@ -1559,8 +1568,8 @@ class Client:
event: Literal['user_update'], event: Literal['user_update'],
/, /,
*, *,
check: Optional[Callable[[User, User], bool]], check: Optional[Callable[[User, User], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[User, User]: ) -> Tuple[User, User]:
... ...
@ -1570,8 +1579,8 @@ class Client:
event: Literal['member_ban'], event: Literal['member_ban'],
/, /,
*, *,
check: Optional[Callable[[Guild, Union[User, Member]], bool]], check: Optional[Callable[[Guild, Union[User, Member]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Guild, Union[User, Member]]: ) -> Tuple[Guild, Union[User, Member]]:
... ...
@ -1581,8 +1590,8 @@ class Client:
event: Literal['member_unban'], event: Literal['member_unban'],
/, /,
*, *,
check: Optional[Callable[[Guild, User], bool]], check: Optional[Callable[[Guild, User], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Guild, User]: ) -> Tuple[Guild, User]:
... ...
@ -1594,8 +1603,8 @@ class Client:
event: Literal['message', 'message_delete'], event: Literal['message', 'message_delete'],
/, /,
*, *,
check: Optional[Callable[[Message], bool]], check: Optional[Callable[[Message], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Message: ) -> Message:
... ...
@ -1605,8 +1614,8 @@ class Client:
event: Literal['message_edit'], event: Literal['message_edit'],
/, /,
*, *,
check: Optional[Callable[[Message, Message], bool]], check: Optional[Callable[[Message, Message], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Message, Message]: ) -> Tuple[Message, Message]:
... ...
@ -1616,8 +1625,8 @@ class Client:
event: Literal['bulk_message_delete'], event: Literal['bulk_message_delete'],
/, /,
*, *,
check: Optional[Callable[[List[Message]], bool]], check: Optional[Callable[[List[Message]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> List[Message]: ) -> List[Message]:
... ...
@ -1627,8 +1636,8 @@ class Client:
event: Literal['raw_message_edit'], event: Literal['raw_message_edit'],
/, /,
*, *,
check: Optional[Callable[[RawMessageUpdateEvent], bool]], check: Optional[Callable[[RawMessageUpdateEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawMessageUpdateEvent: ) -> RawMessageUpdateEvent:
... ...
@ -1638,8 +1647,8 @@ class Client:
event: Literal['raw_message_delete'], event: Literal['raw_message_delete'],
/, /,
*, *,
check: Optional[Callable[[RawMessageDeleteEvent], bool]], check: Optional[Callable[[RawMessageDeleteEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawMessageDeleteEvent: ) -> RawMessageDeleteEvent:
... ...
@ -1649,8 +1658,8 @@ class Client:
event: Literal['raw_bulk_message_delete'], event: Literal['raw_bulk_message_delete'],
/, /,
*, *,
check: Optional[Callable[[RawBulkMessageDeleteEvent], bool]], check: Optional[Callable[[RawBulkMessageDeleteEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawBulkMessageDeleteEvent: ) -> RawBulkMessageDeleteEvent:
... ...
@ -1662,8 +1671,8 @@ class Client:
event: Literal['reaction_add', 'reaction_remove'], event: Literal['reaction_add', 'reaction_remove'],
/, /,
*, *,
check: Optional[Callable[[Reaction, Union[Member, User]], bool]], check: Optional[Callable[[Reaction, Union[Member, User]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Reaction, Union[Member, User]]: ) -> Tuple[Reaction, Union[Member, User]]:
... ...
@ -1673,8 +1682,8 @@ class Client:
event: Literal['reaction_clear'], event: Literal['reaction_clear'],
/, /,
*, *,
check: Optional[Callable[[Message, List[Reaction]], bool]], check: Optional[Callable[[Message, List[Reaction]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Message, List[Reaction]]: ) -> Tuple[Message, List[Reaction]]:
... ...
@ -1684,8 +1693,8 @@ class Client:
event: Literal['reaction_clear_emoji'], event: Literal['reaction_clear_emoji'],
/, /,
*, *,
check: Optional[Callable[[Reaction], bool]], check: Optional[Callable[[Reaction], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Reaction: ) -> Reaction:
... ...
@ -1695,8 +1704,8 @@ class Client:
event: Literal['raw_reaction_add', 'raw_reaction_remove'], event: Literal['raw_reaction_add', 'raw_reaction_remove'],
/, /,
*, *,
check: Optional[Callable[[RawReactionActionEvent], bool]], check: Optional[Callable[[RawReactionActionEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawReactionActionEvent: ) -> RawReactionActionEvent:
... ...
@ -1706,8 +1715,8 @@ class Client:
event: Literal['raw_reaction_clear'], event: Literal['raw_reaction_clear'],
/, /,
*, *,
check: Optional[Callable[[RawReactionClearEvent], bool]], check: Optional[Callable[[RawReactionClearEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawReactionClearEvent: ) -> RawReactionClearEvent:
... ...
@ -1717,8 +1726,8 @@ class Client:
event: Literal['raw_reaction_clear_emoji'], event: Literal['raw_reaction_clear_emoji'],
/, /,
*, *,
check: Optional[Callable[[RawReactionClearEmojiEvent], bool]], check: Optional[Callable[[RawReactionClearEmojiEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawReactionClearEmojiEvent: ) -> RawReactionClearEmojiEvent:
... ...
@ -1730,8 +1739,8 @@ class Client:
event: Literal['guild_role_create', 'guild_role_delete'], event: Literal['guild_role_create', 'guild_role_delete'],
/, /,
*, *,
check: Optional[Callable[[Role], bool]], check: Optional[Callable[[Role], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Role: ) -> Role:
... ...
@ -1741,8 +1750,8 @@ class Client:
event: Literal['guild_role_update'], event: Literal['guild_role_update'],
/, /,
*, *,
check: Optional[Callable[[Role, Role], bool]], check: Optional[Callable[[Role, Role], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Role, Role]: ) -> Tuple[Role, Role]:
... ...
@ -1754,8 +1763,8 @@ class Client:
event: Literal['scheduled_event_create', 'scheduled_event_delete'], event: Literal['scheduled_event_create', 'scheduled_event_delete'],
/, /,
*, *,
check: Optional[Callable[[ScheduledEvent], bool]], check: Optional[Callable[[ScheduledEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> ScheduledEvent: ) -> ScheduledEvent:
... ...
@ -1765,8 +1774,8 @@ class Client:
event: Literal['scheduled_event_user_add', 'scheduled_event_user_remove'], event: Literal['scheduled_event_user_add', 'scheduled_event_user_remove'],
/, /,
*, *,
check: Optional[Callable[[ScheduledEvent, User], bool]], check: Optional[Callable[[ScheduledEvent, User], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[ScheduledEvent, User]: ) -> Tuple[ScheduledEvent, User]:
... ...
@ -1778,8 +1787,8 @@ class Client:
event: Literal['stage_instance_create', 'stage_instance_delete'], event: Literal['stage_instance_create', 'stage_instance_delete'],
/, /,
*, *,
check: Optional[Callable[[StageInstance], bool]], check: Optional[Callable[[StageInstance], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> StageInstance: ) -> StageInstance:
... ...
@ -1789,8 +1798,8 @@ class Client:
event: Literal['stage_instance_update'], event: Literal['stage_instance_update'],
/, /,
*, *,
check: Optional[Callable[[StageInstance, StageInstance], bool]], check: Optional[Callable[[StageInstance, StageInstance], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Coroutine[Any, Any, Tuple[StageInstance, StageInstance]]: ) -> Coroutine[Any, Any, Tuple[StageInstance, StageInstance]]:
... ...
@ -1801,8 +1810,8 @@ class Client:
event: Literal['subscription_create', 'subscription_update', 'subscription_delete'], event: Literal['subscription_create', 'subscription_update', 'subscription_delete'],
/, /,
*, *,
check: Optional[Callable[[Subscription], bool]], check: Optional[Callable[[Subscription], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Subscription: ) -> Subscription:
... ...
@ -1813,8 +1822,8 @@ class Client:
event: Literal['thread_create', 'thread_join', 'thread_remove', 'thread_delete'], event: Literal['thread_create', 'thread_join', 'thread_remove', 'thread_delete'],
/, /,
*, *,
check: Optional[Callable[[Thread], bool]], check: Optional[Callable[[Thread], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Thread: ) -> Thread:
... ...
@ -1824,8 +1833,8 @@ class Client:
event: Literal['thread_update'], event: Literal['thread_update'],
/, /,
*, *,
check: Optional[Callable[[Thread, Thread], bool]], check: Optional[Callable[[Thread, Thread], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Thread, Thread]: ) -> Tuple[Thread, Thread]:
... ...
@ -1835,8 +1844,8 @@ class Client:
event: Literal['raw_thread_update'], event: Literal['raw_thread_update'],
/, /,
*, *,
check: Optional[Callable[[RawThreadUpdateEvent], bool]], check: Optional[Callable[[RawThreadUpdateEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawThreadUpdateEvent: ) -> RawThreadUpdateEvent:
... ...
@ -1846,8 +1855,8 @@ class Client:
event: Literal['raw_thread_delete'], event: Literal['raw_thread_delete'],
/, /,
*, *,
check: Optional[Callable[[RawThreadDeleteEvent], bool]], check: Optional[Callable[[RawThreadDeleteEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawThreadDeleteEvent: ) -> RawThreadDeleteEvent:
... ...
@ -1857,8 +1866,8 @@ class Client:
event: Literal['thread_member_join', 'thread_member_remove'], event: Literal['thread_member_join', 'thread_member_remove'],
/, /,
*, *,
check: Optional[Callable[[ThreadMember], bool]], check: Optional[Callable[[ThreadMember], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> ThreadMember: ) -> ThreadMember:
... ...
@ -1868,8 +1877,8 @@ class Client:
event: Literal['raw_thread_member_remove'], event: Literal['raw_thread_member_remove'],
/, /,
*, *,
check: Optional[Callable[[RawThreadMembersUpdate], bool]], check: Optional[Callable[[RawThreadMembersUpdate], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawThreadMembersUpdate: ) -> RawThreadMembersUpdate:
... ...
@ -1881,8 +1890,8 @@ class Client:
event: Literal['voice_state_update'], event: Literal['voice_state_update'],
/, /,
*, *,
check: Optional[Callable[[Member, VoiceState, VoiceState], bool]], check: Optional[Callable[[Member, VoiceState, VoiceState], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Member, VoiceState, VoiceState]: ) -> Tuple[Member, VoiceState, VoiceState]:
... ...
@ -1894,8 +1903,8 @@ class Client:
event: Literal['poll_vote_add', 'poll_vote_remove'], event: Literal['poll_vote_add', 'poll_vote_remove'],
/, /,
*, *,
check: Optional[Callable[[Union[User, Member], PollAnswer], bool]] = None, check: Optional[Callable[[Union[User, Member], PollAnswer], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Union[User, Member], PollAnswer]: ) -> Tuple[Union[User, Member], PollAnswer]:
... ...
@ -1905,8 +1914,8 @@ class Client:
event: Literal['raw_poll_vote_add', 'raw_poll_vote_remove'], event: Literal['raw_poll_vote_add', 'raw_poll_vote_remove'],
/, /,
*, *,
check: Optional[Callable[[RawPollVoteActionEvent], bool]] = None, check: Optional[Callable[[RawPollVoteActionEvent], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> RawPollVoteActionEvent: ) -> RawPollVoteActionEvent:
... ...
@ -1918,8 +1927,8 @@ class Client:
event: Literal["command", "command_completion"], event: Literal["command", "command_completion"],
/, /,
*, *,
check: Optional[Callable[[Context[Any]], bool]] = None, check: Optional[Callable[[Context[Any]], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Context[Any]: ) -> Context[Any]:
... ...
@ -1929,8 +1938,8 @@ class Client:
event: Literal["command_error"], event: Literal["command_error"],
/, /,
*, *,
check: Optional[Callable[[Context[Any], CommandError], bool]] = None, check: Optional[Callable[[Context[Any], CommandError], bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Tuple[Context[Any], CommandError]: ) -> Tuple[Context[Any], CommandError]:
... ...
@ -1940,8 +1949,8 @@ class Client:
event: str, event: str,
/, /,
*, *,
check: Optional[Callable[..., bool]] = None, check: Optional[Callable[..., bool]] = ...,
timeout: Optional[float] = None, timeout: Optional[float] = ...,
) -> Any: ) -> Any:
... ...

6
discord/components.py

@ -196,12 +196,12 @@ class Button(Component):
self.label: Optional[str] = data.get('label') self.label: Optional[str] = data.get('label')
self.emoji: Optional[PartialEmoji] self.emoji: Optional[PartialEmoji]
try: try:
self.emoji = PartialEmoji.from_dict(data['emoji']) self.emoji = PartialEmoji.from_dict(data['emoji']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.emoji = None self.emoji = None
try: try:
self.sku_id: Optional[int] = int(data['sku_id']) self.sku_id: Optional[int] = int(data['sku_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.sku_id = None self.sku_id = None
@ -415,7 +415,7 @@ class SelectOption:
@classmethod @classmethod
def from_dict(cls, data: SelectOptionPayload) -> SelectOption: def from_dict(cls, data: SelectOptionPayload) -> SelectOption:
try: try:
emoji = PartialEmoji.from_dict(data['emoji']) emoji = PartialEmoji.from_dict(data['emoji']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
emoji = None 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 . import utils
from .colour import Colour from .colour import Colour
from .flags import AttachmentFlags, EmbedFlags
# fmt: off # fmt: off
__all__ = ( __all__ = (
@ -76,6 +77,7 @@ if TYPE_CHECKING:
proxy_url: Optional[str] proxy_url: Optional[str]
height: Optional[int] height: Optional[int]
width: Optional[int] width: Optional[int]
flags: Optional[AttachmentFlags]
class _EmbedVideoProxy(Protocol): class _EmbedVideoProxy(Protocol):
url: Optional[str] url: Optional[str]
@ -131,7 +133,7 @@ class Embed:
The type of embed. Usually "rich". The type of embed. Usually "rich".
This can be set during initialisation. This can be set during initialisation.
Possible strings for embed types can be found on discord's 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`] description: Optional[:class:`str`]
The description of the embed. The description of the embed.
This can be set during initialisation. This can be set during initialisation.
@ -146,6 +148,10 @@ class Embed:
colour: Optional[Union[:class:`Colour`, :class:`int`]] colour: Optional[Union[:class:`Colour`, :class:`int`]]
The colour code of the embed. Aliased to ``color`` as well. The colour code of the embed. Aliased to ``color`` as well.
This can be set during initialisation. This can be set during initialisation.
flags: Optional[:class:`EmbedFlags`]
The flags of this embed.
.. versionadded:: 2.5
""" """
__slots__ = ( __slots__ = (
@ -162,6 +168,7 @@ class Embed:
'_author', '_author',
'_fields', '_fields',
'description', 'description',
'flags',
) )
def __init__( def __init__(
@ -181,6 +188,7 @@ class Embed:
self.type: EmbedType = type self.type: EmbedType = type
self.url: Optional[str] = url self.url: Optional[str] = url
self.description: Optional[str] = description self.description: Optional[str] = description
self.flags: Optional[EmbedFlags] = None
if self.title is not None: if self.title is not None:
self.title = str(self.title) self.title = str(self.title)
@ -245,6 +253,11 @@ class Embed:
else: else:
setattr(self, '_' + attr, value) setattr(self, '_' + attr, value)
try:
self.flags = EmbedFlags._from_value(data['flags'])
except KeyError:
pass
return self return self
def copy(self) -> Self: def copy(self) -> Self:
@ -399,11 +412,15 @@ class Embed:
- ``proxy_url`` - ``proxy_url``
- ``width`` - ``width``
- ``height`` - ``height``
- ``flags``
If the attribute has no value then ``None`` is returned. If the attribute has no value then ``None`` is returned.
""" """
# Lying to the type checker for better developer UX. # 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: def set_image(self, *, url: Optional[Any]) -> Self:
"""Sets the image for the embed content. """Sets the image for the embed content.

13
discord/enums.py

@ -84,13 +84,13 @@ def _create_value_cls(name: str, comparable: bool):
# All the type ignores here are due to the type checker being unable to recognise # All the type ignores here are due to the type checker being unable to recognise
# Runtime type creation without exploding. # Runtime type creation without exploding.
cls = namedtuple('_EnumValue_' + name, 'name value') cls = namedtuple('_EnumValue_' + name, 'name value')
cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>' # type: ignore cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>'
cls.__str__ = lambda self: f'{name}.{self.name}' # type: ignore cls.__str__ = lambda self: f'{name}.{self.name}'
if comparable: if comparable:
cls.__le__ = 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 # type: ignore 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 # type: ignore 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 # type: ignore cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value
return cls return cls
@ -266,6 +266,7 @@ class MessageType(Enum):
guild_incident_report_raid = 38 guild_incident_report_raid = 38
guild_incident_report_false_alarm = 39 guild_incident_report_false_alarm = 39
purchase_notification = 44 purchase_notification = 44
poll_result = 46
class SpeakingState(Enum): class SpeakingState(Enum):

4
discord/ext/commands/bot.py

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

11
discord/ext/commands/context.py

@ -82,7 +82,7 @@ def is_cog(obj: Any) -> TypeGuard[Cog]:
return hasattr(obj, '__cog_commands__') return hasattr(obj, '__cog_commands__')
class DeferTyping: class DeferTyping(Generic[BotT]):
def __init__(self, ctx: Context[BotT], *, ephemeral: bool): def __init__(self, ctx: Context[BotT], *, ephemeral: bool):
self.ctx: Context[BotT] = ctx self.ctx: Context[BotT] = ctx
self.ephemeral: bool = ephemeral self.ephemeral: bool = ephemeral
@ -751,7 +751,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
else: else:
return await self.send(content, **kwargs) return await self.send(content, **kwargs)
def typing(self, *, ephemeral: bool = False) -> Union[Typing, DeferTyping]: def typing(self, *, ephemeral: bool = False) -> Union[Typing, DeferTyping[BotT]]:
"""Returns an asynchronous context manager that allows you to send a typing indicator to """Returns an asynchronous context manager that allows you to send a typing indicator to
the destination for an indefinite period of time, or 10 seconds if the context manager the destination for an indefinite period of time, or 10 seconds if the context manager
is called using ``await``. is called using ``await``.
@ -1078,8 +1078,11 @@ class Context(discord.abc.Messageable, Generic[BotT]):
if self.interaction.response.is_done(): if self.interaction.response.is_done():
msg = await self.interaction.followup.send(**kwargs, wait=True) msg = await self.interaction.followup.send(**kwargs, wait=True)
else: else:
await self.interaction.response.send_message(**kwargs) response = await self.interaction.response.send_message(**kwargs)
msg = await self.interaction.original_response() if not isinstance(response.resource, discord.InteractionMessage):
msg = await self.interaction.original_response()
else:
msg = response.resource
if delete_after is not None: if delete_after is not None:
await msg.delete(delay=delete_after) await msg.delete(delay=delete_after)

6
discord/ext/commands/converter.py

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

9
discord/ext/commands/errors.py

@ -24,18 +24,19 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations 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.errors import ClientException, DiscordException
from discord.utils import _human_join from discord.utils import _human_join
from ._types import BotT
if TYPE_CHECKING: if TYPE_CHECKING:
from discord.abc import GuildChannel from discord.abc import GuildChannel
from discord.threads import Thread from discord.threads import Thread
from discord.types.snowflake import Snowflake, SnowflakeList from discord.types.snowflake import Snowflake, SnowflakeList
from discord.app_commands import AppCommandError from discord.app_commands import AppCommandError
from ._types import BotT
from .context import Context from .context import Context
from .converter import Converter from .converter import Converter
from .cooldowns import BucketType, Cooldown from .cooldowns import BucketType, Cooldown
@ -235,7 +236,7 @@ class CheckFailure(CommandError):
pass pass
class CheckAnyFailure(CheckFailure): class CheckAnyFailure(Generic[BotT], CheckFailure):
"""Exception raised when all predicates in :func:`check_any` fail. """Exception raised when all predicates in :func:`check_any` fail.
This inherits from :exc:`CheckFailure`. This inherits from :exc:`CheckFailure`.
@ -1080,7 +1081,7 @@ class ExtensionNotFound(ExtensionError):
""" """
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
msg = f'Extension {name!r} could not be loaded.' msg = f'Extension {name!r} could not be loaded or found.'
super().__init__(msg, name=name) super().__init__(msg, name=name)

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) return await convert_flag(ctx, argument, flag, annotation)
elif origin is Union and type(None) in annotation.__args__: elif origin is Union and type(None) in annotation.__args__:
# typing.Optional[x] # 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) return await run_converters(ctx, annotation, argument, param)
elif origin is dict: elif origin is dict:
# typing.Dict[K, V] -> typing.Tuple[K, V] # 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 # Fallback to see if the behaviour needs changing
origin = getattr(converter, '__origin__', None) origin = getattr(converter, '__origin__', None)
args = getattr(converter, '__args__', []) args = getattr(converter, '__args__', [])
if isinstance(converter, Range): if isinstance(converter, Range): # type: ignore # Range is not an Annotation at runtime
r = converter 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): elif isinstance(converter, Greedy):
# Greedy is "optional" in ext.commands # Greedy is "optional" in ext.commands
# However, in here, it probably makes sense to make it required. # However, in here, it probably makes sense to make it required.
@ -257,7 +257,7 @@ def replace_parameter(
inner = args[0] inner = args[0]
is_inner_transformer = is_transformer(inner) is_inner_transformer = is_transformer(inner)
if is_converter(inner) and not is_inner_transformer: 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: else:
raise raise
elif origin: elif origin:
@ -424,10 +424,10 @@ class HybridAppCommand(discord.app_commands.Command[CogT, P, T]):
if not ret: if not ret:
return False 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 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 False
return True return True
@ -915,7 +915,8 @@ def hybrid_command(
def decorator(func: CommandCallback[CogT, ContextT, P, T]) -> HybridCommand[CogT, P, T]: def decorator(func: CommandCallback[CogT, ContextT, P, T]) -> HybridCommand[CogT, P, T]:
if isinstance(func, Command): if isinstance(func, Command):
raise TypeError('Callback is already a 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 return decorator

89
discord/flags.py

@ -63,6 +63,7 @@ __all__ = (
'RoleFlags', 'RoleFlags',
'AppInstallationType', 'AppInstallationType',
'SKUFlags', 'SKUFlags',
'EmbedFlags',
) )
BF = TypeVar('BF', bound='BaseFlags') BF = TypeVar('BF', bound='BaseFlags')
@ -2173,6 +2174,30 @@ class AttachmentFlags(BaseFlags):
""":class:`bool`: Returns ``True`` if the attachment has been edited using the remix feature.""" """:class:`bool`: Returns ``True`` if the attachment has been edited using the remix feature."""
return 1 << 2 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() @fill_with_flags()
class RoleFlags(BaseFlags): class RoleFlags(BaseFlags):
@ -2308,3 +2333,67 @@ class SKUFlags(BaseFlags):
def user_subscription(self): def user_subscription(self):
""":class:`bool`: Returns ``True`` if the SKU is a user subscription.""" """:class:`bool`: Returns ``True`` if the SKU is a user subscription."""
return 1 << 8 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._close_code: Optional[int] = None
self.secret_key: Optional[List[int]] = None self.secret_key: Optional[List[int]] = None
if hook: if hook:
self._hook = hook self._hook = hook # type: ignore
async def _hook(self, *args: Any) -> None: async def _hook(self, *args: Any) -> None:
pass pass
@ -893,7 +893,7 @@ class DiscordVoiceWebSocket:
return ws 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 = { payload = {
'op': self.SELECT_PROTOCOL, 'op': self.SELECT_PROTOCOL,
'd': { 'd': {

14
discord/guild.py

@ -95,7 +95,7 @@ from .welcome_screen import WelcomeScreen, WelcomeChannel
from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction
from .partial_emoji import _EmojiTag, PartialEmoji from .partial_emoji import _EmojiTag, PartialEmoji
from .soundboard import SoundboardSound from .soundboard import SoundboardSound
from .presences import RawPresenceUpdateEvent
__all__ = ( __all__ = (
'Guild', 'Guild',
@ -551,7 +551,8 @@ class Guild(Hashable):
member = self.get_member(user_id) member = self.get_member(user_id)
if member is None: if member is None:
try: 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: except KeyError:
member = None member = None
@ -573,7 +574,7 @@ class Guild(Hashable):
def _from_data(self, guild: GuildPayload) -> None: def _from_data(self, guild: GuildPayload) -> None:
try: try:
self._member_count = guild['member_count'] self._member_count = guild['member_count'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
@ -653,10 +654,11 @@ class Guild(Hashable):
empty_tuple = () empty_tuple = ()
for presence in guild.get('presences', []): for presence in guild.get('presences', []):
user_id = int(presence['user']['id']) raw_presence = RawPresenceUpdateEvent(data=presence, state=self._state)
member = self.get_member(user_id) member = self.get_member(raw_presence.user_id)
if member is not None: if member is not None:
member._presence_update(presence, empty_tuple) # type: ignore member._presence_update(raw_presence, empty_tuple) # type: ignore
if 'threads' in guild: if 'threads' in guild:
threads = guild['threads'] threads = guild['threads']

1
discord/http.py

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

214
discord/interactions.py

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

98
discord/member.py

@ -36,13 +36,13 @@ from . import utils
from .asset import Asset from .asset import Asset
from .utils import MISSING from .utils import MISSING
from .user import BaseUser, ClientUser, User, _UserTag from .user import BaseUser, ClientUser, User, _UserTag
from .activity import create_activity, ActivityTypes
from .permissions import Permissions from .permissions import Permissions
from .enums import Status, try_enum from .enums import Status
from .errors import ClientException from .errors import ClientException
from .colour import Colour from .colour import Colour
from .object import Object from .object import Object
from .flags import MemberFlags from .flags import MemberFlags
from .presences import ClientStatus
__all__ = ( __all__ = (
'VoiceState', 'VoiceState',
@ -57,10 +57,8 @@ if TYPE_CHECKING:
from .channel import DMChannel, VoiceChannel, StageChannel from .channel import DMChannel, VoiceChannel, StageChannel
from .flags import PublicUserFlags from .flags import PublicUserFlags
from .guild import Guild from .guild import Guild
from .types.activity import ( from .activity import ActivityTypes
ClientStatus as ClientStatusPayload, from .presences import RawPresenceUpdateEvent
PartialPresenceUpdate,
)
from .types.member import ( from .types.member import (
MemberWithUser as MemberWithUserPayload, MemberWithUser as MemberWithUserPayload,
Member as MemberPayload, Member as MemberPayload,
@ -168,46 +166,6 @@ class VoiceState:
return f'<{self.__class__.__name__} {inner}>' return f'<{self.__class__.__name__} {inner}>'
class _ClientStatus:
__slots__ = ('_status', 'desktop', 'mobile', 'web')
def __init__(self):
self._status: str = 'offline'
self.desktop: Optional[str] = None
self.mobile: Optional[str] = None
self.web: Optional[str] = None
def __repr__(self) -> str:
attrs = [
('_status', self._status),
('desktop', self.desktop),
('mobile', self.mobile),
('web', self.web),
]
inner = ' '.join('%s=%r' % t for t in attrs)
return f'<{self.__class__.__name__} {inner}>'
def _update(self, status: str, data: ClientStatusPayload, /) -> None:
self._status = status
self.desktop = data.get('desktop')
self.mobile = data.get('mobile')
self.web = data.get('web')
@classmethod
def _copy(cls, client_status: Self, /) -> Self:
self = cls.__new__(cls) # bypass __init__
self._status = client_status._status
self.desktop = client_status.desktop
self.mobile = client_status.mobile
self.web = client_status.web
return self
def flatten_user(cls: T) -> T: def flatten_user(cls: T) -> T:
for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()): for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
# ignore private/special methods # ignore private/special methods
@ -306,6 +264,10 @@ class Member(discord.abc.Messageable, _UserTag):
This will be set to ``None`` or a time in the past if the user is not timed out. This will be set to ``None`` or a time in the past if the user is not timed out.
.. versionadded:: 2.0 .. versionadded:: 2.0
client_status: :class:`ClientStatus`
Model which holds information about the status of the member on various clients/platforms via presence updates.
.. versionadded:: 2.5
""" """
__slots__ = ( __slots__ = (
@ -318,7 +280,7 @@ class Member(discord.abc.Messageable, _UserTag):
'nick', 'nick',
'timed_out_until', 'timed_out_until',
'_permissions', '_permissions',
'_client_status', 'client_status',
'_user', '_user',
'_state', '_state',
'_avatar', '_avatar',
@ -354,7 +316,7 @@ class Member(discord.abc.Messageable, _UserTag):
self.joined_at: Optional[datetime.datetime] = utils.parse_time(data.get('joined_at')) self.joined_at: Optional[datetime.datetime] = utils.parse_time(data.get('joined_at'))
self.premium_since: Optional[datetime.datetime] = utils.parse_time(data.get('premium_since')) self.premium_since: Optional[datetime.datetime] = utils.parse_time(data.get('premium_since'))
self._roles: utils.SnowflakeList = utils.SnowflakeList(map(int, data['roles'])) self._roles: utils.SnowflakeList = utils.SnowflakeList(map(int, data['roles']))
self._client_status: _ClientStatus = _ClientStatus() self.client_status: ClientStatus = ClientStatus()
self.activities: Tuple[ActivityTypes, ...] = () self.activities: Tuple[ActivityTypes, ...] = ()
self.nick: Optional[str] = data.get('nick', None) self.nick: Optional[str] = data.get('nick', None)
self.pending: bool = data.get('pending', False) self.pending: bool = data.get('pending', False)
@ -364,7 +326,7 @@ class Member(discord.abc.Messageable, _UserTag):
self._flags: int = data['flags'] self._flags: int = data['flags']
self._avatar_decoration_data: Optional[AvatarDecorationData] = data.get('avatar_decoration_data') self._avatar_decoration_data: Optional[AvatarDecorationData] = data.get('avatar_decoration_data')
try: try:
self._permissions = int(data['permissions']) self._permissions = int(data['permissions']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self._permissions = None self._permissions = None
@ -430,7 +392,7 @@ class Member(discord.abc.Messageable, _UserTag):
self._roles = utils.SnowflakeList(member._roles, is_sorted=True) self._roles = utils.SnowflakeList(member._roles, is_sorted=True)
self.joined_at = member.joined_at self.joined_at = member.joined_at
self.premium_since = member.premium_since self.premium_since = member.premium_since
self._client_status = _ClientStatus._copy(member._client_status) self.client_status = member.client_status
self.guild = member.guild self.guild = member.guild
self.nick = member.nick self.nick = member.nick
self.pending = member.pending self.pending = member.pending
@ -456,12 +418,12 @@ class Member(discord.abc.Messageable, _UserTag):
# the nickname change is optional, # the nickname change is optional,
# if it isn't in the payload then it didn't change # if it isn't in the payload then it didn't change
try: try:
self.nick = data['nick'] self.nick = data['nick'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
try: try:
self.pending = data['pending'] self.pending = data['pending'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
@ -473,13 +435,12 @@ class Member(discord.abc.Messageable, _UserTag):
self._flags = data.get('flags', 0) self._flags = data.get('flags', 0)
self._avatar_decoration_data = data.get('avatar_decoration_data') self._avatar_decoration_data = data.get('avatar_decoration_data')
def _presence_update(self, data: PartialPresenceUpdate, user: UserPayload) -> Optional[Tuple[User, User]]: def _presence_update(self, raw: RawPresenceUpdateEvent, user: UserPayload) -> Optional[Tuple[User, User]]:
self.activities = tuple(create_activity(d, self._state) for d in data['activities']) self.activities = raw.activities
self._client_status._update(data['status'], data['client_status']) self.client_status = raw.client_status
if len(user) > 1: if len(user) > 1:
return self._update_inner_user(user) return self._update_inner_user(user)
return None
def _update_inner_user(self, user: UserPayload) -> Optional[Tuple[User, User]]: def _update_inner_user(self, user: UserPayload) -> Optional[Tuple[User, User]]:
u = self._user u = self._user
@ -518,7 +479,7 @@ class Member(discord.abc.Messageable, _UserTag):
@property @property
def status(self) -> Status: def status(self) -> Status:
""":class:`Status`: The member's overall status. If the value is unknown, then it will be a :class:`str` instead.""" """:class:`Status`: The member's overall status. If the value is unknown, then it will be a :class:`str` instead."""
return try_enum(Status, self._client_status._status) return self.client_status.status
@property @property
def raw_status(self) -> str: def raw_status(self) -> str:
@ -526,31 +487,36 @@ class Member(discord.abc.Messageable, _UserTag):
.. versionadded:: 1.5 .. versionadded:: 1.5
""" """
return self._client_status._status return self.client_status._status
@status.setter @status.setter
def status(self, value: Status) -> None: def status(self, value: Status) -> None:
# internal use only # internal use only
self._client_status._status = str(value) self.client_status._status = str(value)
@property @property
def mobile_status(self) -> Status: def mobile_status(self) -> Status:
""":class:`Status`: The member's status on a mobile device, if applicable.""" """:class:`Status`: The member's status on a mobile device, if applicable."""
return try_enum(Status, self._client_status.mobile or 'offline') return self.client_status.mobile_status
@property @property
def desktop_status(self) -> Status: def desktop_status(self) -> Status:
""":class:`Status`: The member's status on the desktop client, if applicable.""" """:class:`Status`: The member's status on the desktop client, if applicable."""
return try_enum(Status, self._client_status.desktop or 'offline') return self.client_status.desktop_status
@property @property
def web_status(self) -> Status: def web_status(self) -> Status:
""":class:`Status`: The member's status on the web client, if applicable.""" """:class:`Status`: The member's status on the web client, if applicable."""
return try_enum(Status, self._client_status.web or 'offline') return self.client_status.web_status
def is_on_mobile(self) -> bool: def is_on_mobile(self) -> bool:
""":class:`bool`: A helper function that determines if a member is active on a mobile device.""" """A helper function that determines if a member is active on a mobile device.
return self._client_status.mobile is not None
Returns
-------
:class:`bool`
"""
return self.client_status.is_on_mobile()
@property @property
def colour(self) -> Colour: def colour(self) -> Colour:
@ -595,7 +561,9 @@ class Member(discord.abc.Messageable, _UserTag):
role = g.get_role(role_id) role = g.get_role(role_id)
if role: if role:
result.append(role) result.append(role)
result.append(g.default_role) default_role = g.default_role
if default_role:
result.append(default_role)
result.sort() result.sort()
return result return result

57
discord/message.py

@ -610,6 +610,11 @@ class MessageReference:
.. versionadded:: 2.5 .. versionadded:: 2.5
message_id: Optional[:class:`int`] message_id: Optional[:class:`int`]
The id of the message referenced. The id of the message referenced.
This can be ``None`` when this message reference was retrieved from
a system message of one of the following types:
- :attr:`MessageType.channel_follow_add`
- :attr:`MessageType.thread_created`
channel_id: :class:`int` channel_id: :class:`int`
The channel id of the message referenced. The channel id of the message referenced.
guild_id: Optional[:class:`int`] guild_id: Optional[:class:`int`]
@ -773,7 +778,7 @@ class MessageInteraction(Hashable):
self.user: Union[User, Member] = MISSING self.user: Union[User, Member] = MISSING
try: try:
payload = data['member'] payload = data['member'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.user = state.create_user(data['user']) self.user = state.create_user(data['user'])
else: else:
@ -2010,9 +2015,16 @@ class Message(PartialMessage, Hashable):
The :class:`TextChannel` or :class:`Thread` that the message was sent from. The :class:`TextChannel` or :class:`Thread` that the message was sent from.
Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message. Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message.
reference: Optional[:class:`~discord.MessageReference`] reference: Optional[:class:`~discord.MessageReference`]
The message that this message references. This is only applicable to messages of The message that this message references. This is only applicable to
type :attr:`MessageType.pins_add`, crossposted messages created by a message replies (:attr:`MessageType.reply`), crossposted messages created by
followed channel integration, or message replies. a followed channel integration, forwarded messages, and messages of type:
- :attr:`MessageType.pins_add`
- :attr:`MessageType.channel_follow_add`
- :attr:`MessageType.thread_created`
- :attr:`MessageType.thread_starter_message`
- :attr:`MessageType.poll_result`
- :attr:`MessageType.context_menu_command`
.. versionadded:: 1.5 .. versionadded:: 1.5
@ -2200,7 +2212,8 @@ class Message(PartialMessage, Hashable):
self.poll: Optional[Poll] = None self.poll: Optional[Poll] = None
try: 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: except KeyError:
pass pass
@ -2214,7 +2227,7 @@ class Message(PartialMessage, Hashable):
if self.guild is not None: if self.guild is not None:
try: try:
thread = data['thread'] thread = data['thread'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
else: else:
@ -2229,7 +2242,7 @@ class Message(PartialMessage, Hashable):
# deprecated # deprecated
try: try:
interaction = data['interaction'] interaction = data['interaction'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
else: else:
@ -2237,20 +2250,20 @@ class Message(PartialMessage, Hashable):
self.interaction_metadata: Optional[MessageInteractionMetadata] = None self.interaction_metadata: Optional[MessageInteractionMetadata] = None
try: try:
interaction_metadata = data['interaction_metadata'] interaction_metadata = data['interaction_metadata'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
else: else:
self.interaction_metadata = MessageInteractionMetadata(state=state, guild=self.guild, data=interaction_metadata) self.interaction_metadata = MessageInteractionMetadata(state=state, guild=self.guild, data=interaction_metadata)
try: try:
ref = data['message_reference'] ref = data['message_reference'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.reference = None self.reference = None
else: else:
self.reference = ref = MessageReference.with_state(state, ref) self.reference = ref = MessageReference.with_state(state, ref)
try: try:
resolved = data['referenced_message'] resolved = data['referenced_message'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
else: else:
@ -2268,9 +2281,16 @@ class Message(PartialMessage, Hashable):
# the channel will be the correct type here # the channel will be the correct type here
ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore ref.resolved = self.__class__(channel=chan, data=resolved, state=state) # type: ignore
if self.type is MessageType.poll_result:
if isinstance(self.reference.resolved, self.__class__):
self._state._update_poll_results(self, self.reference.resolved)
else:
if self.reference.message_id:
self._state._update_poll_results(self, self.reference.message_id)
self.application: Optional[MessageApplication] = None self.application: Optional[MessageApplication] = None
try: try:
application = data['application'] application = data['application'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
else: else:
@ -2278,7 +2298,7 @@ class Message(PartialMessage, Hashable):
self.role_subscription: Optional[RoleSubscriptionInfo] = None self.role_subscription: Optional[RoleSubscriptionInfo] = None
try: try:
role_subscription = data['role_subscription_data'] role_subscription = data['role_subscription_data'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
else: else:
@ -2286,7 +2306,7 @@ class Message(PartialMessage, Hashable):
self.purchase_notification: Optional[PurchaseNotification] = None self.purchase_notification: Optional[PurchaseNotification] = None
try: try:
purchase_notification = data['purchase_notification'] purchase_notification = data['purchase_notification'] # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
pass pass
else: else:
@ -2294,7 +2314,7 @@ class Message(PartialMessage, Hashable):
for handler in ('author', 'member', 'mentions', 'mention_roles', 'components', 'call'): for handler in ('author', 'member', 'mentions', 'mention_roles', 'components', 'call'):
try: try:
getattr(self, f'_handle_{handler}')(data[handler]) getattr(self, f'_handle_{handler}')(data[handler]) # type: ignore
except KeyError: except KeyError:
continue continue
@ -2634,6 +2654,7 @@ class Message(PartialMessage, Hashable):
MessageType.chat_input_command, MessageType.chat_input_command,
MessageType.context_menu_command, MessageType.context_menu_command,
MessageType.thread_starter_message, MessageType.thread_starter_message,
MessageType.poll_result,
) )
@utils.cached_slot_property('_cs_system_content') @utils.cached_slot_property('_cs_system_content')
@ -2810,6 +2831,14 @@ class Message(PartialMessage, Hashable):
if guild_product_purchase is not None: if guild_product_purchase is not None:
return f'{self.author.name} has purchased {guild_product_purchase.product_name}!' return f'{self.author.name} has purchased {guild_product_purchase.product_name}!'
if self.type is MessageType.poll_result:
embed = self.embeds[0] # Will always have 1 embed
poll_title = utils.get(
embed.fields,
name='poll_question_text',
)
return f'{self.author.display_name}\'s poll {poll_title.value} has closed.' # type: ignore
# Fallback for unknown message types # Fallback for unknown message types
return '' return ''

105
discord/poll.py

@ -29,7 +29,7 @@ from typing import Optional, List, TYPE_CHECKING, Union, AsyncIterator, Dict
import datetime import datetime
from .enums import PollLayoutType, try_enum from .enums import PollLayoutType, try_enum, MessageType
from . import utils from . import utils
from .emoji import PartialEmoji, Emoji from .emoji import PartialEmoji, Emoji
from .user import User from .user import User
@ -125,7 +125,16 @@ class PollAnswer:
Whether the current user has voted to this answer or not. Whether the current user has voted to this answer or not.
""" """
__slots__ = ('media', 'id', '_state', '_message', '_vote_count', 'self_voted', '_poll') __slots__ = (
'media',
'id',
'_state',
'_message',
'_vote_count',
'self_voted',
'_poll',
'_victor',
)
def __init__( def __init__(
self, self,
@ -141,6 +150,7 @@ class PollAnswer:
self._vote_count: int = 0 self._vote_count: int = 0
self.self_voted: bool = False self.self_voted: bool = False
self._poll: Poll = poll self._poll: Poll = poll
self._victor: bool = False
def _handle_vote_event(self, added: bool, self_voted: bool) -> None: def _handle_vote_event(self, added: bool, self_voted: bool) -> None:
if added: if added:
@ -210,6 +220,19 @@ class PollAnswer:
'poll_media': self.media.to_dict(), 'poll_media': self.media.to_dict(),
} }
@property
def victor(self) -> bool:
""":class:`bool`: Whether the answer is the one that had the most
votes when the poll ended.
.. versionadded:: 2.5
.. note::
If the poll has not ended, this will always return ``False``.
"""
return self._victor
async def voters( async def voters(
self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None self, *, limit: Optional[int] = None, after: Optional[Snowflake] = None
) -> AsyncIterator[Union[User, Member]]: ) -> AsyncIterator[Union[User, Member]]:
@ -313,6 +336,15 @@ class Poll:
Defaults to ``False``. Defaults to ``False``.
layout_type: :class:`PollLayoutType` layout_type: :class:`PollLayoutType`
The layout type of the poll. Defaults to :attr:`PollLayoutType.default`. 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__ = ( __slots__ = (
@ -325,6 +357,8 @@ class Poll:
'_expiry', '_expiry',
'_finalized', '_finalized',
'_state', '_state',
'_total_votes',
'_victor_answer_id',
) )
def __init__( def __init__(
@ -348,6 +382,8 @@ class Poll:
self._state: Optional[ConnectionState] = None self._state: Optional[ConnectionState] = None
self._finalized: bool = False self._finalized: bool = False
self._expiry: Optional[datetime.datetime] = None self._expiry: Optional[datetime.datetime] = None
self._total_votes: Optional[int] = None
self._victor_answer_id: Optional[int] = None
def _update(self, message: Message) -> None: def _update(self, message: Message) -> None:
self._state = message._state self._state = message._state
@ -360,6 +396,33 @@ class Poll:
self._expiry = message.poll.expires_at self._expiry = message.poll.expires_at
self._finalized = message.poll._finalized self._finalized = message.poll._finalized
self._answers = message.poll._answers self._answers = message.poll._answers
self._update_results_from_message(message)
def _update_results_from_message(self, message: Message) -> None:
if message.type != MessageType.poll_result or not message.embeds:
return
result_embed = message.embeds[0] # Will always have 1 embed
fields: Dict[str, str] = {field.name: field.value for field in result_embed.fields} # type: ignore
total_votes = fields.get('total_votes')
if total_votes is not None:
self._total_votes = int(total_votes)
victor_answer = fields.get('victor_answer_id')
if victor_answer is None:
return # Can't do anything else without the victor answer
self._victor_answer_id = int(victor_answer)
victor_answer_votes = fields['victor_answer_votes']
answer = self._answers[self._victor_answer_id]
answer._victor = True
answer._vote_count = int(victor_answer_votes)
self._answers[answer.id] = answer # Ensure update
def _update_results(self, data: PollResultPayload) -> None: def _update_results(self, data: PollResultPayload) -> None:
self._finalized = data['is_finalized'] self._finalized = data['is_finalized']
@ -432,6 +495,32 @@ class Poll:
"""List[:class:`PollAnswer`]: Returns a read-only copy of the answers.""" """List[:class:`PollAnswer`]: Returns a read-only copy of the answers."""
return list(self._answers.values()) return list(self._answers.values())
@property
def victor_answer_id(self) -> Optional[int]:
"""Optional[:class:`int`]: The victor answer ID.
.. versionadded:: 2.5
.. note::
This will **always** be ``None`` for polls that have not yet finished.
"""
return self._victor_answer_id
@property
def victor_answer(self) -> Optional[PollAnswer]:
"""Optional[:class:`PollAnswer`]: The victor answer.
.. versionadded:: 2.5
.. note::
This will **always** be ``None`` for polls that have not yet finished.
"""
if self.victor_answer_id is None:
return None
return self.get_answer(self.victor_answer_id)
@property @property
def expires_at(self) -> Optional[datetime.datetime]: def expires_at(self) -> Optional[datetime.datetime]:
"""Optional[:class:`datetime.datetime`]: A datetime object representing the poll expiry. """Optional[:class:`datetime.datetime`]: A datetime object representing the poll expiry.
@ -457,12 +546,20 @@ class Poll:
@property @property
def message(self) -> Optional[Message]: def message(self) -> Optional[Message]:
""":class:`Message`: The message this poll is from.""" """Optional[:class:`Message`]: The message this poll is from."""
return self._message return self._message
@property @property
def total_votes(self) -> int: def total_votes(self) -> int:
""":class:`int`: Returns the sum of all the answer votes.""" """:class:`int`: Returns the sum of all the answer votes.
If the poll has not yet finished, this is an approximate vote count.
.. versionchanged:: 2.5
This now returns an exact vote count when updated from its poll results message.
"""
if self._total_votes is not None:
return self._total_votes
return sum([answer.vote_count for answer in self.answers]) return sum([answer.vote_count for answer in self.answers])
def is_finalised(self) -> bool: def is_finalised(self) -> bool:

150
discord/presences.py

@ -0,0 +1,150 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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 TYPE_CHECKING, Optional, Tuple
from .activity import create_activity
from .enums import Status, try_enum
from .utils import MISSING, _get_as_snowflake, _RawReprMixin
if TYPE_CHECKING:
from typing_extensions import Self
from .activity import ActivityTypes
from .guild import Guild
from .state import ConnectionState
from .types.activity import ClientStatus as ClientStatusPayload, PartialPresenceUpdate
__all__ = (
'RawPresenceUpdateEvent',
'ClientStatus',
)
class ClientStatus:
"""Represents the :ddocs:`Client Status Object <events/gateway-events#client-status-object>` from Discord,
which holds information about the status of the user on various clients/platforms, with additional helpers.
.. versionadded:: 2.5
"""
__slots__ = ('_status', 'desktop', 'mobile', 'web')
def __init__(self, *, status: str = MISSING, data: ClientStatusPayload = MISSING) -> None:
self._status: str = status or 'offline'
data = data or {}
self.desktop: Optional[str] = data.get('desktop')
self.mobile: Optional[str] = data.get('mobile')
self.web: Optional[str] = data.get('web')
def __repr__(self) -> str:
attrs = [
('_status', self._status),
('desktop', self.desktop),
('mobile', self.mobile),
('web', self.web),
]
inner = ' '.join('%s=%r' % t for t in attrs)
return f'<{self.__class__.__name__} {inner}>'
def _update(self, status: str, data: ClientStatusPayload, /) -> None:
self._status = status
self.desktop = data.get('desktop')
self.mobile = data.get('mobile')
self.web = data.get('web')
@classmethod
def _copy(cls, client_status: Self, /) -> Self:
self = cls.__new__(cls) # bypass __init__
self._status = client_status._status
self.desktop = client_status.desktop
self.mobile = client_status.mobile
self.web = client_status.web
return self
@property
def status(self) -> Status:
""":class:`Status`: The user's overall status. If the value is unknown, then it will be a :class:`str` instead."""
return try_enum(Status, self._status)
@property
def raw_status(self) -> str:
""":class:`str`: The user's overall status as a string value."""
return self._status
@property
def mobile_status(self) -> Status:
""":class:`Status`: The user's status on a mobile device, if applicable."""
return try_enum(Status, self.mobile or 'offline')
@property
def desktop_status(self) -> Status:
""":class:`Status`: The user's status on the desktop client, if applicable."""
return try_enum(Status, self.desktop or 'offline')
@property
def web_status(self) -> Status:
""":class:`Status`: The user's status on the web client, if applicable."""
return try_enum(Status, self.web or 'offline')
def is_on_mobile(self) -> bool:
""":class:`bool`: A helper function that determines if a user is active on a mobile device."""
return self.mobile is not None
class RawPresenceUpdateEvent(_RawReprMixin):
"""Represents the payload for a :func:`on_raw_presence_update` event.
.. versionadded:: 2.5
Attributes
----------
user_id: :class:`int`
The ID of the user that triggered the presence update.
guild_id: Optional[:class:`int`]
The guild ID for the users presence update. Could be ``None``.
guild: Optional[:class:`Guild`]
The guild associated with the presence update and user. Could be ``None``.
client_status: :class:`ClientStatus`
The :class:`~.ClientStatus` model which holds information about the status of the user on various clients.
activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]]
The activities the user is currently doing. Due to a Discord API limitation, a user's Spotify activity may not appear
if they are listening to a song with a title longer than ``128`` characters. See :issue:`1738` for more information.
"""
__slots__ = ('user_id', 'guild_id', 'guild', 'client_status', 'activities')
def __init__(self, *, data: PartialPresenceUpdate, state: ConnectionState) -> None:
self.user_id: int = int(data['user']['id'])
self.client_status: ClientStatus = ClientStatus(status=data['status'], data=data['client_status'])
self.activities: Tuple[ActivityTypes, ...] = tuple(create_activity(d, state) for d in data['activities'])
self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id')
self.guild: Optional[Guild] = state._get_guild(self.guild_id)

26
discord/raw_models.py

@ -25,10 +25,10 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from typing import TYPE_CHECKING, Literal, Optional, Set, List, Tuple, Union from typing import TYPE_CHECKING, Literal, Optional, Set, List, Union
from .enums import ChannelType, try_enum, ReactionType from .enums import ChannelType, try_enum, ReactionType
from .utils import _get_as_snowflake from .utils import _get_as_snowflake, _RawReprMixin
from .app_commands import AppCommandPermissions from .app_commands import AppCommandPermissions
from .colour import Colour from .colour import Colour
@ -82,14 +82,6 @@ __all__ = (
) )
class _RawReprMixin:
__slots__: Tuple[str, ...] = ()
def __repr__(self) -> str:
value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__)
return f'<{self.__class__.__name__} {value}>'
class RawMessageDeleteEvent(_RawReprMixin): class RawMessageDeleteEvent(_RawReprMixin):
"""Represents the event payload for a :func:`on_raw_message_delete` event. """Represents the event payload for a :func:`on_raw_message_delete` event.
@ -112,7 +104,7 @@ class RawMessageDeleteEvent(_RawReprMixin):
self.channel_id: int = int(data['channel_id']) self.channel_id: int = int(data['channel_id'])
self.cached_message: Optional[Message] = None self.cached_message: Optional[Message] = None
try: try:
self.guild_id: Optional[int] = int(data['guild_id']) self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.guild_id: Optional[int] = None self.guild_id: Optional[int] = None
@ -140,7 +132,7 @@ class RawBulkMessageDeleteEvent(_RawReprMixin):
self.cached_messages: List[Message] = [] self.cached_messages: List[Message] = []
try: try:
self.guild_id: Optional[int] = int(data['guild_id']) self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.guild_id: Optional[int] = None self.guild_id: Optional[int] = None
@ -256,7 +248,7 @@ class RawReactionActionEvent(_RawReprMixin):
self.type: ReactionType = try_enum(ReactionType, data['type']) self.type: ReactionType = try_enum(ReactionType, data['type'])
try: try:
self.guild_id: Optional[int] = int(data['guild_id']) self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.guild_id: Optional[int] = None self.guild_id: Optional[int] = None
@ -289,7 +281,7 @@ class RawReactionClearEvent(_RawReprMixin):
self.channel_id: int = int(data['channel_id']) self.channel_id: int = int(data['channel_id'])
try: try:
self.guild_id: Optional[int] = int(data['guild_id']) self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.guild_id: Optional[int] = None self.guild_id: Optional[int] = None
@ -319,7 +311,7 @@ class RawReactionClearEmojiEvent(_RawReprMixin):
self.channel_id: int = int(data['channel_id']) self.channel_id: int = int(data['channel_id'])
try: try:
self.guild_id: Optional[int] = int(data['guild_id']) self.guild_id: Optional[int] = int(data['guild_id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.guild_id: Optional[int] = None self.guild_id: Optional[int] = None
@ -346,7 +338,9 @@ class RawIntegrationDeleteEvent(_RawReprMixin):
self.guild_id: int = int(data['guild_id']) self.guild_id: int = int(data['guild_id'])
try: try:
self.application_id: Optional[int] = int(data['application_id']) self.application_id: Optional[int] = int(
data['application_id'] # pyright: ignore[reportTypedDictNotRequiredAccess]
)
except KeyError: except KeyError:
self.application_id: Optional[int] = None self.application_id: Optional[int] = None

110
discord/role.py

@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations 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 .asset import Asset
from .permissions import Permissions from .permissions import Permissions
@ -286,7 +286,7 @@ class Role(Hashable):
self._flags: int = data.get('flags', 0) self._flags: int = data.get('flags', 0)
try: try:
self.tags = RoleTags(data['tags']) self.tags = RoleTags(data['tags']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
self.tags = None 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) 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) 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: async def delete(self, *, reason: Optional[str] = None) -> None:
"""|coro| """|coro|

68
discord/state.py

@ -62,6 +62,7 @@ from .message import Message
from .channel import * from .channel import *
from .channel import _channel_factory from .channel import _channel_factory
from .raw_models import * from .raw_models import *
from .presences import RawPresenceUpdateEvent
from .member import Member from .member import Member
from .role import Role from .role import Role
from .enums import ChannelType, try_enum, Status from .enums import ChannelType, try_enum, Status
@ -261,6 +262,10 @@ class ConnectionState(Generic[ClientT]):
if not intents.members or cache_flags._empty: if not intents.members or cache_flags._empty:
self.store_user = self.store_user_no_intents self.store_user = self.store_user_no_intents
self.raw_presence_flag: bool = options.get('enable_raw_presences', utils.MISSING)
if self.raw_presence_flag is utils.MISSING:
self.raw_presence_flag = not intents.members and intents.presences
self.parsers: Dict[str, Callable[[Any], None]] self.parsers: Dict[str, Callable[[Any], None]]
self.parsers = parsers = {} self.parsers = parsers = {}
for attr, func in inspect.getmembers(self): for attr, func in inspect.getmembers(self):
@ -535,7 +540,7 @@ class ConnectionState(Generic[ClientT]):
) -> Tuple[Union[Channel, Thread], Optional[Guild]]: ) -> Tuple[Union[Channel, Thread], Optional[Guild]]:
channel_id = int(data['channel_id']) channel_id = int(data['channel_id'])
try: 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) guild = self._get_guild(guild_id)
except KeyError: except KeyError:
channel = DMChannel._from_message(self, channel_id) channel = DMChannel._from_message(self, channel_id)
@ -552,6 +557,27 @@ class ConnectionState(Generic[ClientT]):
poll._handle_vote(answer_id, added, self_voted) poll._handle_vote(answer_id, added, self_voted)
return poll return poll
def _update_poll_results(self, from_: Message, to: Union[Message, int]) -> None:
if isinstance(to, Message):
cached = self._get_message(to.id)
elif isinstance(to, int):
cached = self._get_message(to)
if cached is None:
return
to = cached
else:
return
if to.poll is None:
return
to.poll._update_results_from_message(from_)
if cached is not None and cached.poll:
cached.poll._update_results_from_message(from_)
async def chunker( async def chunker(
self, guild_id: int, query: str = '', limit: int = 0, presences: bool = False, *, nonce: Optional[str] = None self, guild_id: int, query: str = '', limit: int = 0, presences: bool = False, *, nonce: Optional[str] = None
) -> None: ) -> None:
@ -710,7 +736,7 @@ class ConnectionState(Generic[ClientT]):
if 'components' in data: if 'components' in data:
try: try:
entity_id = int(data['interaction']['id']) entity_id = int(data['interaction']['id']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except (KeyError, ValueError): except (KeyError, ValueError):
entity_id = raw.message_id entity_id = raw.message_id
@ -806,22 +832,24 @@ class ConnectionState(Generic[ClientT]):
self.dispatch('interaction', interaction) self.dispatch('interaction', interaction)
def parse_presence_update(self, data: gw.PresenceUpdateEvent) -> None: def parse_presence_update(self, data: gw.PresenceUpdateEvent) -> None:
guild_id = utils._get_as_snowflake(data, 'guild_id') raw = RawPresenceUpdateEvent(data=data, state=self)
# guild_id won't be None here
guild = self._get_guild(guild_id) if self.raw_presence_flag:
if guild is None: self.dispatch('raw_presence_update', raw)
_log.debug('PRESENCE_UPDATE referencing an unknown guild ID: %s. Discarding.', guild_id)
if raw.guild is None:
_log.debug('PRESENCE_UPDATE referencing an unknown guild ID: %s. Discarding.', raw.guild_id)
return return
user = data['user'] member = raw.guild.get_member(raw.user_id)
member_id = int(user['id'])
member = guild.get_member(member_id)
if member is None: if member is None:
_log.debug('PRESENCE_UPDATE referencing an unknown member ID: %s. Discarding', member_id) _log.debug('PRESENCE_UPDATE referencing an unknown member ID: %s. Discarding', raw.user_id)
return return
old_member = Member._copy(member) old_member = Member._copy(member)
user_update = member._presence_update(data=data, user=user) user_update = member._presence_update(raw=raw, user=data['user'])
if user_update: if user_update:
self.dispatch('user_update', user_update[0], user_update[1]) self.dispatch('user_update', user_update[0], user_update[1])
@ -907,7 +935,7 @@ class ConnectionState(Generic[ClientT]):
def parse_channel_pins_update(self, data: gw.ChannelPinsUpdateEvent) -> None: def parse_channel_pins_update(self, data: gw.ChannelPinsUpdateEvent) -> None:
channel_id = int(data['channel_id']) channel_id = int(data['channel_id'])
try: try:
guild = self._get_guild(int(data['guild_id'])) guild = self._get_guild(int(data['guild_id'])) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError: except KeyError:
guild = None guild = None
channel = self._get_private_channel(channel_id) channel = self._get_private_channel(channel_id)
@ -989,7 +1017,7 @@ class ConnectionState(Generic[ClientT]):
return return
try: 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: except KeyError:
# If not provided, then the entire guild is being synced # If not provided, then the entire guild is being synced
# So all previous thread data should be overwritten # So all previous thread data should be overwritten
@ -1409,8 +1437,10 @@ class ConnectionState(Generic[ClientT]):
user = presence['user'] user = presence['user']
member_id = user['id'] member_id = user['id']
member = member_dict.get(member_id) member = member_dict.get(member_id)
if member is not None: if member is not None:
member._presence_update(presence, user) raw_presence = RawPresenceUpdateEvent(data=presence, state=self)
member._presence_update(raw_presence, user)
complete = data.get('chunk_index', 0) + 1 == data.get('chunk_count') complete = data.get('chunk_index', 0) + 1 == data.get('chunk_count')
self.process_chunk_requests(guild_id, data.get('nonce'), members, complete) self.process_chunk_requests(guild_id, data.get('nonce'), members, complete)
@ -1523,12 +1553,8 @@ class ConnectionState(Generic[ClientT]):
def parse_guild_scheduled_event_delete(self, data: gw.GuildScheduledEventDeleteEvent) -> None: def parse_guild_scheduled_event_delete(self, data: gw.GuildScheduledEventDeleteEvent) -> None:
guild = self._get_guild(int(data['guild_id'])) guild = self._get_guild(int(data['guild_id']))
if guild is not None: if guild is not None:
try: scheduled_event = guild._scheduled_events.pop(int(data['id']), ScheduledEvent(state=self, data=data))
scheduled_event = guild._scheduled_events.pop(int(data['id'])) self.dispatch('scheduled_event_delete', scheduled_event)
except KeyError:
pass
else:
self.dispatch('scheduled_event_delete', scheduled_event)
else: else:
_log.debug('SCHEDULED_EVENT_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id']) _log.debug('SCHEDULED_EVENT_DELETE referencing unknown guild ID: %s. Discarding.', data['guild_id'])

4
discord/subscription.py

@ -63,6 +63,8 @@ class Subscription(Hashable):
canceled_at: Optional[:class:`datetime.datetime`] canceled_at: Optional[:class:`datetime.datetime`]
When the subscription was canceled. When the subscription was canceled.
This is only available for subscriptions with a :attr:`status` of :attr:`SubscriptionStatus.inactive`. This is only available for subscriptions with a :attr:`status` of :attr:`SubscriptionStatus.inactive`.
renewal_sku_ids: List[:class:`int`]
The IDs of the SKUs that the user is going to be subscribed to when renewing.
""" """
__slots__ = ( __slots__ = (
@ -75,6 +77,7 @@ class Subscription(Hashable):
'current_period_end', 'current_period_end',
'status', 'status',
'canceled_at', 'canceled_at',
'renewal_sku_ids',
) )
def __init__(self, *, state: ConnectionState, data: SubscriptionPayload): def __init__(self, *, state: ConnectionState, data: SubscriptionPayload):
@ -88,6 +91,7 @@ class Subscription(Hashable):
self.current_period_end: datetime.datetime = utils.parse_time(data['current_period_end']) self.current_period_end: datetime.datetime = utils.parse_time(data['current_period_end'])
self.status: SubscriptionStatus = try_enum(SubscriptionStatus, data['status']) self.status: SubscriptionStatus = try_enum(SubscriptionStatus, data['status'])
self.canceled_at: Optional[datetime.datetime] = utils.parse_time(data['canceled_at']) self.canceled_at: Optional[datetime.datetime] = utils.parse_time(data['canceled_at'])
self.renewal_sku_ids: List[int] = list(map(int, data['renewal_sku_ids'] or []))
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<Subscription id={self.id} user_id={self.user_id} status={self.status!r}>' return f'<Subscription id={self.id} user_id={self.user_id} status={self.status!r}>'

2
discord/threads.py

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

7
discord/types/appinfo.py

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

4
discord/types/embed.py

@ -50,6 +50,7 @@ class EmbedVideo(TypedDict, total=False):
proxy_url: str proxy_url: str
height: int height: int
width: int width: int
flags: int
class EmbedImage(TypedDict, total=False): class EmbedImage(TypedDict, total=False):
@ -71,7 +72,7 @@ class EmbedAuthor(TypedDict, total=False):
proxy_icon_url: str proxy_icon_url: str
EmbedType = Literal['rich', 'image', 'video', 'gifv', 'article', 'link'] EmbedType = Literal['rich', 'image', 'video', 'gifv', 'article', 'link', 'poll_result']
class Embed(TypedDict, total=False): class Embed(TypedDict, total=False):
@ -88,3 +89,4 @@ class Embed(TypedDict, total=False):
provider: EmbedProvider provider: EmbedProvider
author: EmbedAuthor author: EmbedAuthor
fields: List[EmbedField] fields: List[EmbedField]
flags: int

4
discord/types/guild.py

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

34
discord/types/interactions.py

@ -42,6 +42,16 @@ if TYPE_CHECKING:
InteractionType = Literal[1, 2, 3, 4, 5] InteractionType = Literal[1, 2, 3, 4, 5]
InteractionResponseType = Literal[
1,
4,
5,
6,
7,
8,
9,
10,
]
InteractionContextType = Literal[0, 1, 2] InteractionContextType = Literal[0, 1, 2]
InteractionInstallationType = Literal[0, 1] InteractionInstallationType = Literal[0, 1]
@ -301,3 +311,27 @@ MessageInteractionMetadata = Union[
MessageComponentMessageInteractionMetadata, MessageComponentMessageInteractionMetadata,
ModalSubmitMessageInteractionMetadata, 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]

1
discord/types/message.py

@ -174,6 +174,7 @@ MessageType = Literal[
38, 38,
39, 39,
44, 44,
46,
] ]

1
discord/types/subscription.py

@ -40,3 +40,4 @@ class Subscription(TypedDict):
current_period_end: str current_period_end: str
status: SubscriptionStatus status: SubscriptionStatus
canceled_at: Optional[str] canceled_at: Optional[str]
renewal_sku_ids: Optional[List[Snowflake]]

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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations from __future__ import annotations
from typing import ( from typing import (
Any, Any,
@ -330,7 +331,9 @@ class BaseSelect(Item[V]):
values = selected_values.get({}) values = selected_values.get({})
payload: List[PossibleValue] payload: List[PossibleValue]
try: try:
resolved = Namespace._get_resolved_items(interaction, data['resolved']) resolved = Namespace._get_resolved_items(
interaction, data['resolved'] # pyright: ignore[reportTypedDictNotRequiredAccess]
)
payload = list(resolved.values()) payload = list(resolved.values())
except KeyError: except KeyError:
payload = data.get("values", []) # type: ignore payload = data.get("values", []) # type: ignore

7
discord/ui/view.py

@ -177,7 +177,7 @@ class View:
children = [] children = []
for func in self.__view_children_items__: for func in self.__view_children_items__:
item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) 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 item._view = self
setattr(self, func.__name__, item) setattr(self, func.__name__, item)
children.append(item) children.append(item)
@ -214,6 +214,11 @@ class View:
# Wait N seconds to see if timeout data has been refreshed # Wait N seconds to see if timeout data has been refreshed
await asyncio.sleep(self.__timeout_expiry - now) 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 to_components(self) -> List[Dict[str, Any]]:
def key(item: Item) -> int: def key(item: Item) -> int:
return item._rendered_row or 0 return item._rendered_row or 0

16
discord/utils.py

@ -108,7 +108,7 @@ __all__ = (
) )
DISCORD_EPOCH = 1420070400000 DISCORD_EPOCH = 1420070400000
DEFAULT_FILE_SIZE_LIMIT_BYTES = 26214400 DEFAULT_FILE_SIZE_LIMIT_BYTES = 10485760
class _MissingSentinel: class _MissingSentinel:
@ -714,13 +714,13 @@ async def maybe_coroutine(f: MaybeAwaitableFunc[P, T], *args: P.args, **kwargs:
if _isawaitable(value): if _isawaitable(value):
return await value return await value
else: else:
return value # type: ignore return value
async def async_all( async def async_all(
gen: Iterable[Union[T, Awaitable[T]]], 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: ) -> bool:
for elem in gen: for elem in gen:
if check(elem): if check(elem):
@ -1121,7 +1121,7 @@ def flatten_literal_params(parameters: Iterable[Any]) -> Tuple[Any, ...]:
literal_cls = type(Literal[0]) literal_cls = type(Literal[0])
for p in parameters: for p in parameters:
if isinstance(p, literal_cls): if isinstance(p, literal_cls):
params.extend(p.__args__) params.extend(p.__args__) # type: ignore
else: else:
params.append(p) params.append(p)
return tuple(params) return tuple(params)
@ -1532,3 +1532,11 @@ def _format_call_duration(duration: datetime.timedelta) -> str:
formatted = f"{years} years" formatted = f"{years} years"
return formatted return formatted
class _RawReprMixin:
__slots__: Tuple[str, ...] = ()
def __repr__(self) -> str:
value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__)
return f'<{self.__class__.__name__} {value}>'

2
discord/voice_state.py

@ -344,7 +344,7 @@ class VoiceConnectionState:
elif self.state is not ConnectionFlowState.disconnected: elif self.state is not ConnectionFlowState.disconnected:
# eventual consistency # 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 return
_log.debug('Unexpected server update event, attempting to handle') _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.emoji import PartialEmoji as PartialEmojiPayload
from ..types.snowflake import SnowflakeList from ..types.snowflake import SnowflakeList
from ..types.interactions import (
InteractionCallback as InteractionCallbackResponsePayload,
)
BE = TypeVar('BE', bound=BaseException) BE = TypeVar('BE', bound=BaseException)
_State = Union[ConnectionState, '_WebhookState'] _State = Union[ConnectionState, '_WebhookState']
@ -310,8 +313,9 @@ class AsyncWebhookAdapter:
files: Optional[Sequence[File]] = None, files: Optional[Sequence[File]] = None,
thread_id: Optional[int] = None, thread_id: Optional[int] = None,
wait: bool = False, wait: bool = False,
with_components: bool = False,
) -> Response[Optional[MessagePayload]]: ) -> Response[Optional[MessagePayload]]:
params = {'wait': int(wait)} params = {'wait': int(wait), 'with_components': int(with_components)}
if thread_id: if thread_id:
params['thread_id'] = thread_id params['thread_id'] = thread_id
route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token) 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: Optional[str] = None,
proxy_auth: Optional[aiohttp.BasicAuth] = None, proxy_auth: Optional[aiohttp.BasicAuth] = None,
params: MultipartParameters, params: MultipartParameters,
) -> Response[None]: ) -> Response[InteractionCallbackResponsePayload]:
route = Route( route = Route(
'POST', 'POST',
'/interactions/{webhook_id}/{webhook_token}/callback', '/interactions/{webhook_id}/{webhook_token}/callback',
webhook_id=interaction_id, webhook_id=interaction_id,
webhook_token=token, webhook_token=token,
) )
request_params = {'with_response': '1'}
if params.files: if params.files:
return self.request( return self.request(
@ -450,9 +455,17 @@ class AsyncWebhookAdapter:
proxy_auth=proxy_auth, proxy_auth=proxy_auth,
files=params.files, files=params.files,
multipart=params.multipart, multipart=params.multipart,
params=request_params,
) )
else: 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( def get_original_interaction_response(
self, self,
@ -660,6 +673,11 @@ class PartialWebhookChannel(Hashable):
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<PartialWebhookChannel name={self.name!r} id={self.id}>' 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): class PartialWebhookGuild(Hashable):
"""Represents a partial guild for webhooks. """Represents a partial guild for webhooks.
@ -1710,10 +1728,9 @@ class Webhook(BaseWebhook):
.. versionadded:: 1.4 .. versionadded:: 1.4
view: :class:`discord.ui.View` view: :class:`discord.ui.View`
The view to send with the message. You can only send a view The view to send with the message. If the webhook is partial or
if this webhook is not partial and has state attached. A is not managed by the library, then you can only send URL buttons.
webhook has state attached if the webhook is managed by the Otherwise, you can send views with any type of components.
library.
.. versionadded:: 2.0 .. versionadded:: 2.0
thread: :class:`~discord.abc.Snowflake` thread: :class:`~discord.abc.Snowflake`
@ -1765,7 +1782,8 @@ class Webhook(BaseWebhook):
The length of ``embeds`` was invalid, there was no token The length of ``embeds`` was invalid, there was no token
associated with this webhook or ``ephemeral`` was passed associated with this webhook or ``ephemeral`` was passed
with the improper webhook type or there was no state 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 Returns
--------- ---------
@ -1795,13 +1813,15 @@ class Webhook(BaseWebhook):
wait = True wait = True
if view is not MISSING: 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__'): if not hasattr(view, '__discord_ui_view__'):
raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}') 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 view.timeout = 15 * 60.0
if thread_name is not MISSING and thread is not MISSING: if thread_name is not MISSING and thread is not MISSING:
@ -1845,6 +1865,7 @@ class Webhook(BaseWebhook):
files=params.files, files=params.files,
thread_id=thread_id, thread_id=thread_id,
wait=wait, wait=wait,
with_components=view is not MISSING,
) )
msg = None msg = None

26
discord/webhook/sync.py

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

2
discord/widget.py

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

75
docs/api.rst

@ -80,6 +80,14 @@ AppInstallParams
.. autoclass:: AppInstallParams() .. autoclass:: AppInstallParams()
:members: :members:
IntegrationTypeConfig
~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: IntegrationTypeConfig
.. autoclass:: IntegrationTypeConfig()
:members:
Team Team
~~~~~ ~~~~~
@ -916,6 +924,29 @@ Members
:param after: The updated member's updated info. :param after: The updated member's updated info.
:type after: :class:`Member` :type after: :class:`Member`
.. function:: on_raw_presence_update(payload)
Called when a :class:`Member` updates their presence.
This requires :attr:`Intents.presences` to be enabled.
Unlike :func:`on_presence_update`, when enabled, this is called regardless of the state of internal guild
and member caches, and **does not** provide a comparison between the previous and updated states of the :class:`Member`.
.. important::
By default, this event is only dispatched when :attr:`Intents.presences` is enabled **and** :attr:`Intents.members`
is disabled.
You can manually override this behaviour by setting the **enable_raw_presences** flag in the :class:`Client`,
however :attr:`Intents.presences` is always required for this event to work.
.. versionadded:: 2.5
:param payload: The raw presence update event model.
:type payload: :class:`RawPresenceUpdateEvent`
Messages Messages
~~~~~~~~~ ~~~~~~~~~
@ -1887,6 +1918,10 @@ of :class:`enum.Enum`.
.. versionadded:: 2.5 .. versionadded:: 2.5
.. attribute:: poll_result
The system message sent when a poll has closed.
.. class:: UserFlags .. class:: UserFlags
Represents Discord User flags. Represents Discord User flags.
@ -3816,17 +3851,25 @@ of :class:`enum.Enum`.
.. versionadded:: 2.5 .. versionadded:: 2.5
.. attribute:: reply .. attribute:: default
A message reply. A standard reference used by message replies (:attr:`MessageType.reply`),
crossposted messaged created by a followed channel integration, and messages of type:
- :attr:`MessageType.pins_add`
- :attr:`MessageType.channel_follow_add`
- :attr:`MessageType.thread_created`
- :attr:`MessageType.thread_starter_message`
- :attr:`MessageType.poll_result`
- :attr:`MessageType.context_menu_command`
.. attribute:: forward .. attribute:: forward
A forwarded message. A forwarded message.
.. attribute:: default .. attribute:: reply
An alias for :attr:`.reply`. An alias for :attr:`.default`.
.. _discord-api-audit-logs: .. _discord-api-audit-logs:
@ -5360,6 +5403,14 @@ RawPollVoteActionEvent
.. autoclass:: RawPollVoteActionEvent() .. autoclass:: RawPollVoteActionEvent()
:members: :members:
RawPresenceUpdateEvent
~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: RawPresenceUpdateEvent
.. autoclass:: RawPresenceUpdateEvent()
:members:
PartialWebhookGuild PartialWebhookGuild
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
@ -5394,6 +5445,14 @@ MessageSnapshot
.. autoclass:: MessageSnapshot .. autoclass:: MessageSnapshot
:members: :members:
ClientStatus
~~~~~~~~~~~~
.. attributetable:: ClientStatus
.. autoclass:: ClientStatus()
:members:
Data Classes Data Classes
-------------- --------------
@ -5665,6 +5724,14 @@ SKUFlags
.. autoclass:: SKUFlags() .. autoclass:: SKUFlags()
:members: :members:
EmbedFlags
~~~~~~~~~~
.. attributetable:: EmbedFlags
.. autoclass:: EmbedFlags()
:members:
ForumTag ForumTag
~~~~~~~~~ ~~~~~~~~~

5
docs/ext/commands/api.rst

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

11
docs/faq.rst

@ -439,7 +439,7 @@ How can I disable all items on timeout?
This requires three steps. This requires three steps.
1. Attach a message to the :class:`~discord.ui.View` using either the return type of :meth:`~abc.Messageable.send` or retrieving it via :meth:`Interaction.original_response`. 1. Attach a message to the :class:`~discord.ui.View` using either the return type of :meth:`~abc.Messageable.send` or retrieving it via :attr:`InteractionCallbackResponse.resource`.
2. Inside :meth:`~ui.View.on_timeout`, loop over all items inside the view and mark them disabled. 2. Inside :meth:`~ui.View.on_timeout`, loop over all items inside the view and mark them disabled.
3. Edit the message we retrieved in step 1 with the newly modified view. 3. Edit the message we retrieved in step 1 with the newly modified view.
@ -467,7 +467,7 @@ Putting it all together, we can do this in a text command:
# Step 1 # Step 1
view.message = await ctx.send('Press me!', view=view) view.message = await ctx.send('Press me!', view=view)
Application commands do not return a message when you respond with :meth:`InteractionResponse.send_message`, therefore in order to reliably do this we should retrieve the message using :meth:`Interaction.original_response`. Application commands, when you respond with :meth:`InteractionResponse.send_message`, return an instance of :class:`InteractionCallbackResponse` which contains the message you sent. This is the message you should attach to the view.
Putting it all together, using the previous view definition: Putting it all together, using the previous view definition:
@ -477,10 +477,13 @@ Putting it all together, using the previous view definition:
async def more_timeout_example(interaction): async def more_timeout_example(interaction):
"""Another example to showcase disabling buttons on timing out""" """Another example to showcase disabling buttons on timing out"""
view = MyView() view = MyView()
await interaction.response.send_message('Press me!', view=view) callback = await interaction.response.send_message('Press me!', view=view)
# Step 1 # Step 1
view.message = await interaction.original_response() resource = callback.resource
# making sure it's an interaction response message
if isinstance(resource, discord.InteractionMessage):
view.message = resource
Application Commands Application Commands

16
docs/interactions/api.rst

@ -28,6 +28,22 @@ InteractionResponse
.. autoclass:: InteractionResponse() .. autoclass:: InteractionResponse()
:members: :members:
InteractionCallbackResponse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: InteractionCallbackResponse
.. autoclass:: InteractionCallbackResponse()
:members:
InteractionCallbackActivityInstance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: InteractionCallbackActivityInstance
.. autoclass:: InteractionCallbackActivityInstance()
:members:
InteractionMessage 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 This page keeps a detailed human friendly rendering of what's new and changed
in specific versions. 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: .. _vp2p4p0:
v2.4.0 v2.4.0

1
setup.py

@ -1,6 +1,7 @@
from setuptools import setup from setuptools import setup
import re import re
def derive_version() -> str: def derive_version() -> str:
version = '' version = ''
with open('discord/__init__.py') as f: with open('discord/__init__.py') as f:

Loading…
Cancel
Save