Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -13,7 +13,7 @@ __title__ = 'discord'
__author__ = 'Rapptz'
__license__ = 'MIT'
__copyright__ = 'Copyright 2015-present Rapptz'
__version__ = '2.6.4'
__version__ = '2.7.1'
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
@ -75,6 +75,7 @@ from .subscription import *
from .presences import *
from .primary_guild import *
from .onboarding import *
from .collectible import *
class VersionInfo(NamedTuple):
@ -85,7 +86,7 @@ class VersionInfo(NamedTuple):
serial: int
version_info: VersionInfo = VersionInfo(major=2, minor=6, micro=4, releaselevel='final', serial=0)
version_info: VersionInfo = VersionInfo(major=2, minor=7, micro=1, releaselevel='final', serial=0)
logging.getLogger(__name__).addHandler(logging.NullHandler())

View file

@ -48,6 +48,14 @@ def show_version() -> None:
entries.append(f' - discord.py metadata: v{version}')
entries.append(f'- aiohttp v{aiohttp.__version__}')
try:
import davey # type: ignore
except ImportError:
entries.append('- davey not found')
else:
entries.append(f'- davey v{davey.__version__}')
uname = platform.uname()
entries.append('- system info: {0.system} {0.release} {0.version}'.format(uname))
print('\n'.join(entries))

View file

@ -195,6 +195,9 @@ async def _purge_helper(
count = 0
await asyncio.sleep(1)
if not message.type.is_deletable():
continue
if not check(message):
continue
@ -818,7 +821,7 @@ class GuildChannel:
if obj.is_default():
return base
overwrite = utils.get(self._overwrites, type=_Overwrites.ROLE, id=obj.id)
overwrite = utils.find(lambda ow: ow.type == _Overwrites.ROLE and ow.id == obj.id, self._overwrites)
if overwrite is not None:
base.handle_overwrite(overwrite.allow, overwrite.deny)

View file

@ -1802,7 +1802,7 @@ class Group:
yield from command.walk_commands()
@mark_overrideable
async def on_error(self, interaction: Interaction, error: AppCommandError, /) -> None:
async def on_error(self, interaction: Interaction[ClientT], error: AppCommandError, /) -> None:
"""|coro|
A callback that is called when a child's command raises an :exc:`AppCommandError`.
@ -1850,7 +1850,7 @@ class Group:
self.on_error = coro # type: ignore
return coro
async def interaction_check(self, interaction: Interaction, /) -> bool:
async def interaction_check(self, interaction: Interaction[ClientT], /) -> bool:
"""|coro|
A callback that is called when an interaction happens within the group
@ -2180,8 +2180,9 @@ def describe(**parameters: Union[str, locale_str]) -> Callable[[T], T]:
'''
def decorator(inner: T) -> T:
if isinstance(inner, Command):
_populate_descriptions(inner._params, parameters)
unwrapped = getattr(inner, '__discord_app_commands_unwrap__', inner) or inner
if isinstance(unwrapped, Command):
_populate_descriptions(unwrapped._params, parameters)
else:
try:
inner.__discord_app_commands_param_description__.update(parameters) # type: ignore # Runtime attribute access
@ -2223,8 +2224,9 @@ def rename(**parameters: Union[str, locale_str]) -> Callable[[T], T]:
"""
def decorator(inner: T) -> T:
if isinstance(inner, Command):
_populate_renames(inner._params, parameters)
unwrapped = getattr(inner, '__discord_app_commands_unwrap__', inner) or inner
if isinstance(unwrapped, Command):
_populate_renames(unwrapped._params, parameters)
else:
try:
inner.__discord_app_commands_param_rename__.update(parameters) # type: ignore # Runtime attribute access
@ -2292,8 +2294,9 @@ def choices(**parameters: List[Choice[ChoiceT]]) -> Callable[[T], T]:
"""
def decorator(inner: T) -> T:
if isinstance(inner, Command):
_populate_choices(inner._params, parameters)
unwrapped = getattr(inner, '__discord_app_commands_unwrap__', inner) or inner
if isinstance(unwrapped, Command):
_populate_choices(unwrapped._params, parameters)
else:
try:
inner.__discord_app_commands_param_choices__.update(parameters) # type: ignore # Runtime attribute access
@ -2351,8 +2354,9 @@ def autocomplete(**parameters: AutocompleteCallback[GroupT, ChoiceT]) -> Callabl
"""
def decorator(inner: T) -> T:
if isinstance(inner, Command):
_populate_autocomplete(inner._params, parameters)
unwrapped = getattr(inner, '__discord_app_commands_unwrap__', inner) or inner
if isinstance(unwrapped, Command):
_populate_autocomplete(unwrapped._params, parameters)
else:
try:
inner.__discord_app_commands_param_autocomplete__.update(parameters) # type: ignore # Runtime attribute access
@ -2408,13 +2412,14 @@ def guilds(*guild_ids: Union[Snowflake, int]) -> Callable[[T], T]:
defaults: List[int] = [g if isinstance(g, int) else g.id for g in guild_ids]
def decorator(inner: T) -> T:
if isinstance(inner, (Group, ContextMenu)):
inner._guild_ids = defaults
elif isinstance(inner, Command):
if inner.parent is not None:
unwrapped = getattr(inner, '__discord_app_commands_unwrap__', inner) or inner
if isinstance(unwrapped, (Group, ContextMenu)):
unwrapped._guild_ids = defaults
elif isinstance(unwrapped, Command):
if unwrapped.parent is not None:
raise ValueError('child commands of a group cannot have default guilds set')
inner._guild_ids = defaults
unwrapped._guild_ids = defaults
else:
# Runtime attribute assignment
inner.__discord_app_commands_default_guilds__ = defaults # type: ignore
@ -2470,13 +2475,14 @@ def check(predicate: Check) -> Callable[[T], T]:
"""
def decorator(func: CheckInputParameter) -> CheckInputParameter:
if isinstance(func, (Command, ContextMenu)):
func.checks.append(predicate)
unwrapped = getattr(func, '__discord_app_commands_unwrap__', func) or func
if isinstance(unwrapped, (Command, ContextMenu)):
unwrapped.checks.append(predicate)
else:
if not hasattr(func, '__discord_app_commands_checks__'):
func.__discord_app_commands_checks__ = []
func.__discord_app_commands_checks__ = [] # type: ignore # Runtime attribute assignment
func.__discord_app_commands_checks__.append(predicate)
func.__discord_app_commands_checks__.append(predicate) # type: ignore # Runtime attribute access
return func
@ -2513,10 +2519,11 @@ def guild_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]:
"""
def inner(f: T) -> T:
if isinstance(f, (Command, Group, ContextMenu)):
f.guild_only = True
allowed_contexts = f.allowed_contexts or AppCommandContext()
f.allowed_contexts = allowed_contexts
unwrapped = getattr(f, '__discord_app_commands_unwrap__', f) or f
if isinstance(unwrapped, (Command, Group, ContextMenu)):
unwrapped.guild_only = True
allowed_contexts = unwrapped.allowed_contexts or AppCommandContext()
unwrapped.allowed_contexts = allowed_contexts
else:
f.__discord_app_commands_guild_only__ = True # type: ignore # Runtime attribute assignment
@ -2567,10 +2574,11 @@ def private_channel_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]
"""
def inner(f: T) -> T:
if isinstance(f, (Command, Group, ContextMenu)):
f.guild_only = False
allowed_contexts = f.allowed_contexts or AppCommandContext()
f.allowed_contexts = allowed_contexts
unwrapped = getattr(f, '__discord_app_commands_unwrap__', f) or f
if isinstance(unwrapped, (Command, Group, ContextMenu)):
unwrapped.guild_only = False
allowed_contexts = unwrapped.allowed_contexts or AppCommandContext()
unwrapped.allowed_contexts = allowed_contexts
else:
allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext()
f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment
@ -2617,10 +2625,11 @@ def dm_only(func: Optional[T] = None) -> Union[T, Callable[[T], T]]:
"""
def inner(f: T) -> T:
if isinstance(f, (Command, Group, ContextMenu)):
f.guild_only = False
allowed_contexts = f.allowed_contexts or AppCommandContext()
f.allowed_contexts = allowed_contexts
unwrapped = getattr(f, '__discord_app_commands_unwrap__', f) or f
if isinstance(unwrapped, (Command, Group, ContextMenu)):
unwrapped.guild_only = False
allowed_contexts = unwrapped.allowed_contexts or AppCommandContext()
unwrapped.allowed_contexts = allowed_contexts
else:
allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext()
f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment
@ -2658,10 +2667,11 @@ def allowed_contexts(guilds: bool = MISSING, dms: bool = MISSING, private_channe
"""
def inner(f: T) -> T:
if isinstance(f, (Command, Group, ContextMenu)):
f.guild_only = False
allowed_contexts = f.allowed_contexts or AppCommandContext()
f.allowed_contexts = allowed_contexts
unwrapped = getattr(f, '__discord_app_commands_unwrap__', f) or f
if isinstance(unwrapped, (Command, Group, ContextMenu)):
unwrapped.guild_only = False
allowed_contexts = unwrapped.allowed_contexts or AppCommandContext()
unwrapped.allowed_contexts = allowed_contexts
else:
allowed_contexts = getattr(f, '__discord_app_commands_contexts__', None) or AppCommandContext()
f.__discord_app_commands_contexts__ = allowed_contexts # type: ignore # Runtime attribute assignment
@ -2709,9 +2719,10 @@ def guild_install(func: Optional[T] = None) -> Union[T, Callable[[T], T]]:
"""
def inner(f: T) -> T:
if isinstance(f, (Command, Group, ContextMenu)):
allowed_installs = f.allowed_installs or AppInstallationType()
f.allowed_installs = allowed_installs
unwrapped = getattr(f, '__discord_app_commands_unwrap__', f) or f
if isinstance(unwrapped, (Command, Group, ContextMenu)):
allowed_installs = unwrapped.allowed_installs or AppInstallationType()
unwrapped.allowed_installs = allowed_installs
else:
allowed_installs = getattr(f, '__discord_app_commands_installation_types__', None) or AppInstallationType()
f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment
@ -2757,9 +2768,10 @@ def user_install(func: Optional[T] = None) -> Union[T, Callable[[T], T]]:
"""
def inner(f: T) -> T:
if isinstance(f, (Command, Group, ContextMenu)):
allowed_installs = f.allowed_installs or AppInstallationType()
f.allowed_installs = allowed_installs
unwrapped = getattr(f, '__discord_app_commands_unwrap__', f) or f
if isinstance(unwrapped, (Command, Group, ContextMenu)):
allowed_installs = unwrapped.allowed_installs or AppInstallationType()
unwrapped.allowed_installs = allowed_installs
else:
allowed_installs = getattr(f, '__discord_app_commands_installation_types__', None) or AppInstallationType()
f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment
@ -2801,9 +2813,10 @@ def allowed_installs(
"""
def inner(f: T) -> T:
if isinstance(f, (Command, Group, ContextMenu)):
allowed_installs = f.allowed_installs or AppInstallationType()
f.allowed_installs = allowed_installs
unwrapped = getattr(f, '__discord_app_commands_unwrap__', f) or f
if isinstance(unwrapped, (Command, Group, ContextMenu)):
allowed_installs = unwrapped.allowed_installs or AppInstallationType()
unwrapped.allowed_installs = allowed_installs
else:
allowed_installs = getattr(f, '__discord_app_commands_installation_types__', None) or AppInstallationType()
f.__discord_app_commands_installation_types__ = allowed_installs # type: ignore # Runtime attribute assignment
@ -2874,8 +2887,9 @@ def default_permissions(perms_obj: Optional[Permissions] = None, /, **perms: Unp
permissions = Permissions(**perms)
def decorator(func: T) -> T:
if isinstance(func, (Command, Group, ContextMenu)):
func.default_permissions = permissions
unwrapped = getattr(func, '__discord_app_commands_unwrap__', func) or func
if isinstance(unwrapped, (Command, Group, ContextMenu)):
unwrapped.default_permissions = permissions
else:
func.__discord_app_commands_default_permissions__ = permissions # type: ignore # Runtime attribute assignment

View file

@ -597,8 +597,7 @@ class AppCommandChannel(Hashable):
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~discord.Permissions.manage_channels` or
:attr:`~discord.Permissions.manage_messages` bypass slowmode.
Bots and users with :attr:`~discord.Permissions.bypass_slowmode` bypass slowmode.
.. versionadded:: 2.6
nsfw: :class:`bool`
@ -779,8 +778,7 @@ class AppCommandThread(Hashable):
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this thread. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~discord.Permissions.manage_channels` or
:attr:`~discord.Permissions.manage_messages` bypass slowmode.
Bots and users with :attr:`~discord.Permissions.bypass_slowmode` bypass slowmode.
.. versionadded:: 2.6
message_count: :class:`int`

View file

@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
import datetime
import inspect
from dataclasses import dataclass
@ -52,7 +53,7 @@ from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel,
from ..abc import GuildChannel
from ..threads import Thread
from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType, Locale
from ..utils import MISSING, maybe_coroutine, _human_join
from ..utils import MISSING, maybe_coroutine, _human_join, TIMESTAMP_PATTERN
from ..user import User
from ..role import Role
from ..member import Member
@ -62,6 +63,7 @@ from .._types import ClientT
__all__ = (
'Transformer',
'Transform',
'Timestamp',
'Range',
)
@ -681,6 +683,41 @@ class UnionChannelTransformer(BaseChannelTransformer[ClientT]):
return resolved
if TYPE_CHECKING:
Timestamp = datetime.datetime
else:
class Timestamp(Transformer[ClientT]):
"""A type annotation that can be applied to a parameter for transforming a :ddocs:`Discord style timestamp <reference#message-formatting>` input to a
:class:`datetime.datetime`.
.. versionadded:: 2.7
.. warning::
Due to a Discord limitation, no timezone is provided with the input. The UTC timezone has been supplanted instead.
Examples
---------
.. code-block:: python3
@app_commands.command()
async def datetime(interaction: discord.Interaction, value: app_commands.Timestamp):
await interaction.response.send_message(value.isoformat())
"""
@property
def type(self) -> AppCommandOptionType:
return AppCommandOptionType.string
async def transform(self, interaction: Interaction[ClientT], value: Any, /):
match = TIMESTAMP_PATTERN.match(value)
if not match:
raise TransformerError(value, AppCommandOptionType.string, self)
return datetime.datetime.fromtimestamp(int(match[1]), tz=datetime.timezone.utc)
CHANNEL_TO_TYPES: Dict[Any, List[ChannelType]] = {
AppCommandChannel: [
ChannelType.stage_voice,

View file

@ -1289,7 +1289,12 @@ class CommandTree(Generic[ClientT]):
await command._invoke_autocomplete(interaction, focused, namespace)
except Exception:
# Suppress exception since it can't be handled anyway.
_log.exception('Ignoring exception in autocomplete for %r', command.qualified_name)
_log.exception(
'Ignoring exception in autocomplete for %r (Guild: %s, User: %s)',
command.qualified_name,
interaction.guild_id,
interaction.user.id,
)
return

View file

@ -355,6 +355,16 @@ class Asset(AssetMixin):
animated=False,
)
@classmethod
def _from_user_collectible(cls, state: _State, asset: str, animated: bool = False) -> Self:
name = 'static.png' if not animated else 'asset.webm'
return cls(
state,
url=f'{cls.BASE}/assets/collectibles/{asset}{name}',
key=asset,
animated=animated,
)
def __str__(self) -> str:
return self._url

View file

@ -630,11 +630,6 @@ class _AuditLogProxyAutoModAction(_AuditLogProxy):
channel: Optional[Union[abc.GuildChannel, Thread]]
class _AuditLogProxyAutoModActionQuarantineUser(_AuditLogProxy):
automod_rule_name: str
automod_rule_trigger_type: str
class _AuditLogProxyMemberKickOrMemberRoleUpdate(_AuditLogProxy):
integration_type: Optional[str]
@ -725,7 +720,6 @@ class AuditLogEntry(Hashable):
_AuditLogProxyStageInstanceAction,
_AuditLogProxyMessageBulkDelete,
_AuditLogProxyAutoModAction,
_AuditLogProxyAutoModActionQuarantineUser,
_AuditLogProxyMemberKickOrMemberRoleUpdate,
Member, User, None, PartialIntegration,
Role, Object
@ -766,6 +760,7 @@ class AuditLogEntry(Hashable):
self.action is enums.AuditLogAction.automod_block_message
or self.action is enums.AuditLogAction.automod_flag_message
or self.action is enums.AuditLogAction.automod_timeout_member
or self.action is enums.AuditLogAction.automod_quarantine_user
):
channel_id = utils._get_as_snowflake(extra, 'channel_id')
channel = None
@ -781,13 +776,6 @@ class AuditLogEntry(Hashable):
),
channel=channel,
)
elif self.action is enums.AuditLogAction.automod_quarantine_user:
self.extra = _AuditLogProxyAutoModActionQuarantineUser(
automod_rule_name=extra['auto_moderation_rule_name'],
automod_rule_trigger_type=enums.try_enum(
enums.AutoModRuleTriggerType, int(extra['auto_moderation_rule_trigger_type'])
),
)
elif self.action.name.startswith('overwrite_'):
# the overwrite_ actions have a dict with some information

View file

@ -322,8 +322,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
Bots and users with :attr:`~Permissions.bypass_slowmode` bypass slowmode.
nsfw: :class:`bool`
If the channel is marked as "not safe for work" or "age restricted".
default_auto_archive_duration: :class:`int`
@ -1516,8 +1515,7 @@ class VoiceChannel(VocalGuildChannel):
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
Bots and users with :attr:`~Permissions.bypass_slowmode` bypass slowmode.
.. versionadded:: 2.2
"""
@ -1744,8 +1742,7 @@ class StageChannel(VocalGuildChannel):
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
Bots and users with :attr:`~Permissions.bypass_slowmode` bypass slowmode.
.. versionadded:: 2.2
"""
@ -2409,8 +2406,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
slowmode_delay: :class:`int`
The number of seconds a member must wait between creating threads
in this forum. A value of ``0`` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
Bots and users with :attr:`~Permissions.bypass_slowmode` bypass slowmode.
nsfw: :class:`bool`
If the forum is marked as "not safe for work" or "age restricted".
default_auto_archive_duration: :class:`int`
@ -2879,6 +2875,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
applied_tags: Sequence[ForumTag] = ...,
view: LayoutView,
suppress_embeds: bool = ...,
silent: bool = ...,
reason: Optional[str] = ...,
) -> ThreadWithMessage: ...
@ -2901,6 +2898,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
applied_tags: Sequence[ForumTag] = ...,
view: View = ...,
suppress_embeds: bool = ...,
silent: bool = ...,
reason: Optional[str] = ...,
) -> ThreadWithMessage: ...
@ -2922,6 +2920,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
applied_tags: Sequence[ForumTag] = MISSING,
view: BaseView = MISSING,
suppress_embeds: bool = False,
silent: bool = False,
reason: Optional[str] = None,
) -> ThreadWithMessage:
"""|coro|
@ -2976,6 +2975,11 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
A list of stickers to upload. Must be a maximum of 3.
suppress_embeds: :class:`bool`
Whether to suppress embeds for the message. This sends the message without any embeds if set to ``True``.
silent: :class:`bool`
Whether to suppress push and desktop notifications for the message. This will increment the mention counter
in the UI, but will not actually send a notification.
.. versionadded:: 2.7
reason: :class:`str`
The reason for creating a new thread. Shows up on the audit log.
@ -3008,8 +3012,10 @@ class ForumChannel(discord.abc.GuildChannel, Hashable):
if view and not hasattr(view, '__discord_ui_view__'):
raise TypeError(f'view parameter must be View not {view.__class__.__name__}')
if suppress_embeds:
flags = MessageFlags._from_value(4)
if suppress_embeds or silent:
flags = MessageFlags._from_value(0)
flags.suppress_embeds = suppress_embeds
flags.suppress_notifications = silent
else:
flags = MISSING

View file

@ -88,7 +88,7 @@ if TYPE_CHECKING:
from .abc import Messageable, PrivateChannel, Snowflake, SnowflakeTime
from .app_commands import Command, ContextMenu
from .automod import AutoModAction, AutoModRule
from .channel import DMChannel, GroupChannel
from .channel import DMChannel, GroupChannel, VoiceChannelEffect
from .ext.commands import AutoShardedBot, Bot, Context, CommandError
from .guild import GuildChannel
from .integrations import Integration
@ -340,6 +340,10 @@ class Client:
VoiceClient.warn_nacl = False
_log.warning('PyNaCl is not installed, voice will NOT be supported')
if VoiceClient.warn_dave:
VoiceClient.warn_dave = False
_log.warning('davey is not installed, voice will NOT be supported')
async def __aenter__(self) -> Self:
await self._async_setup_hook()
return self
@ -1753,6 +1757,38 @@ class Client:
timeout: Optional[float] = ...,
) -> Tuple[ScheduledEvent, User]: ...
@overload
async def wait_for(
self,
event: Literal['scheduled_event_update'],
/,
*,
check: Optional[Callable[[ScheduledEvent, ScheduledEvent], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[ScheduledEvent, ScheduledEvent]: ...
# Soundboard
@overload
async def wait_for(
self,
event: Literal['soundboard_sound_create', 'soundboard_sound_delete'],
/,
*,
check: Optional[Callable[[SoundboardSound], bool]] = ...,
timeout: Optional[float] = ...,
) -> SoundboardSound: ...
@overload
async def wait_for(
self,
event: Literal['soundboard_sound_update'],
/,
*,
check: Optional[Callable[[SoundboardSound, SoundboardSound], bool]] = ...,
timeout: Optional[float] = ...,
) -> Tuple[SoundboardSound, SoundboardSound]: ...
# Stages
@overload
@ -1859,6 +1895,16 @@ class Client:
timeout: Optional[float] = ...,
) -> Tuple[Member, VoiceState, VoiceState]: ...
@overload
async def wait_for(
self,
event: Literal['voice_channel_effect'],
/,
*,
check: Optional[Callable[[VoiceChannelEffect], bool]] = ...,
timeout: Optional[float] = ...,
) -> VoiceChannelEffect: ...
# Polls
@overload
@ -2511,7 +2557,7 @@ class Client:
)
return Invite.from_incomplete(state=self._connection, data=data)
async def delete_invite(self, invite: Union[Invite, str], /) -> Invite:
async def delete_invite(self, invite: Union[Invite, str], /, *, reason: Optional[str] = None) -> Invite:
"""|coro|
Revokes an :class:`.Invite`, URL, or ID to an invite.
@ -2527,6 +2573,8 @@ class Client:
----------
invite: Union[:class:`.Invite`, :class:`str`]
The invite to revoke.
reason: Optional[:class:`str`]
The reason for deleting the invite. Shows up on the audit log.
Raises
-------
@ -2539,7 +2587,7 @@ class Client:
"""
resolved = utils.resolve_invite(invite)
data = await self.http.delete_invite(resolved.code)
data = await self.http.delete_invite(resolved.code, reason=reason)
return Invite.from_incomplete(state=self._connection, data=data)
# Miscellaneous stuff

View file

@ -0,0 +1,109 @@
"""
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 Optional, TYPE_CHECKING
from .asset import Asset
from .enums import NameplatePalette, CollectibleType, try_enum
from .utils import parse_time
if TYPE_CHECKING:
from datetime import datetime
from .state import ConnectionState
from .types.user import (
Collectible as CollectiblePayload,
)
__all__ = ('Collectible',)
class Collectible:
"""Represents a user's collectible.
.. versionadded:: 2.7
Attributes
----------
label: :class:`str`
The label of the collectible.
palette: Optional[:class:`NameplatePalette`]
The palette of the collectible.
This is only available if ``type`` is
:class:`CollectibleType.nameplate`.
sku_id: :class:`int`
The SKU ID of the collectible.
type: :class:`CollectibleType`
The type of the collectible.
expires_at: Optional[:class:`datetime.datetime`]
The expiration date of the collectible. If applicable.
"""
__slots__ = (
'type',
'sku_id',
'label',
'expires_at',
'palette',
'_state',
'_asset',
)
def __init__(self, *, state: ConnectionState, type: str, data: CollectiblePayload) -> None:
self._state: ConnectionState = state
self.type: CollectibleType = try_enum(CollectibleType, type)
self._asset: str = data['asset']
self.sku_id: int = int(data['sku_id'])
self.label: str = data['label']
self.expires_at: Optional[datetime] = parse_time(data.get('expires_at'))
# nameplate
self.palette: Optional[NameplatePalette]
try:
self.palette = try_enum(NameplatePalette, data['palette']) # type: ignore
except KeyError:
self.palette = None
@property
def static(self) -> Asset:
""":class:`Asset`: The static asset of the collectible."""
return Asset._from_user_collectible(self._state, self._asset)
@property
def animated(self) -> Asset:
""":class:`Asset`: The animated asset of the collectible."""
return Asset._from_user_collectible(self._state, self._asset, animated=True)
def __repr__(self) -> str:
attrs = ['sku_id']
if self.palette:
attrs.append('palette')
joined_attrs = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in attrs)
return f'<{self.type.name.title()} {joined_attrs}>'

View file

@ -72,6 +72,12 @@ if TYPE_CHECKING:
ContainerComponent as ContainerComponentPayload,
UnfurledMediaItem as UnfurledMediaItemPayload,
LabelComponent as LabelComponentPayload,
FileUploadComponent as FileUploadComponentPayload,
RadioGroupComponent as RadioGroupComponentPayload,
RadioGroupOption as RadioGroupOptionPayload,
CheckboxGroupComponent as CheckboxGroupComponentPayload,
CheckboxGroupOption as CheckboxGroupOptionPayload,
CheckboxComponent as CheckboxComponentPayload,
)
from .emoji import Emoji
@ -91,6 +97,7 @@ if TYPE_CHECKING:
'SectionComponent',
'Component',
]
OptionPayload = Union[SelectOptionPayload, RadioGroupOptionPayload, CheckboxGroupOptionPayload]
__all__ = (
@ -112,6 +119,12 @@ __all__ = (
'TextDisplay',
'SeparatorComponent',
'LabelComponent',
'FileUploadComponent',
'RadioGroupComponent',
'CheckboxGroupComponent',
'CheckboxComponent',
'RadioGroupOption',
'CheckboxGroupOption',
)
@ -132,6 +145,7 @@ class Component:
- :class:`SeparatorComponent`
- :class:`Container`
- :class:`LabelComponent`
- :class:`FileUploadComponent`
This class is abstract and cannot be instantiated.
@ -167,6 +181,71 @@ class Component:
raise NotImplementedError
class BaseOption:
"""Represents a base option for components that have options.
This currently implements:
- :class:`SelectOption`
- :class:`RadioGroupOption`
- :class:`CheckboxGroupOption`
.. versionadded:: 2.7
"""
__slots__: Tuple[str, ...] = ('label', 'value', 'description', 'default')
__repr_info__: ClassVar[Tuple[str, ...]] = ('label', 'value', 'description', 'default')
def __init__(
self,
*,
label: str,
value: str = MISSING,
description: Optional[str] = None,
default: bool = False,
) -> None:
self.label: str = label
self.value: str = label if value is MISSING else value
self.description: Optional[str] = description
self.default: bool = default
def __repr__(self) -> str:
attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__repr_info__)
return f'<{self.__class__.__name__} {attrs}>'
def __str__(self) -> str:
base = self.label
if self.description:
return f'{base}\n{self.description}'
return base
@classmethod
def from_dict(cls, data: OptionPayload) -> Self:
return cls(
label=data['label'],
value=data['value'],
description=data.get('description'),
default=data.get('default', False),
)
def to_dict(self) -> OptionPayload:
payload: OptionPayload = {
'label': self.label,
'value': self.value,
'default': self.default,
}
if self.description:
payload['description'] = self.description
return payload
def copy(self) -> Self:
return self.__class__.from_dict(self.to_dict())
class ActionRow(Component):
"""Represents a Discord Bot UI Kit Action Row.
@ -413,7 +492,7 @@ class SelectMenu(Component):
return payload
class SelectOption:
class SelectOption(BaseOption):
"""Represents a select menu's option.
These can be created by users.
@ -451,13 +530,8 @@ class SelectOption:
Whether this option is selected by default.
"""
__slots__: Tuple[str, ...] = (
'label',
'value',
'description',
'_emoji',
'default',
)
__slots__: Tuple[str, ...] = BaseOption.__slots__ + ('_emoji',)
__repr_info__ = BaseOption.__repr_info__ + ('emoji',)
def __init__(
self,
@ -468,18 +542,9 @@ class SelectOption:
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
default: bool = False,
) -> None:
self.label: str = label
self.value: str = label if value is MISSING else value
self.description: Optional[str] = description
super().__init__(label=label, value=value, description=description, default=default)
self.emoji = emoji
self.default: bool = default
def __repr__(self) -> str:
return (
f'<SelectOption label={self.label!r} value={self.value!r} description={self.description!r} '
f'emoji={self.emoji!r} default={self.default!r}>'
)
def __str__(self) -> str:
if self.emoji:
@ -509,7 +574,7 @@ class SelectOption:
self._emoji = None
@classmethod
def from_dict(cls, data: SelectOptionPayload) -> SelectOption:
def from_dict(cls, data: SelectOptionPayload) -> Self:
try:
emoji = PartialEmoji.from_dict(data['emoji']) # pyright: ignore[reportTypedDictNotRequiredAccess]
except KeyError:
@ -519,28 +584,18 @@ class SelectOption:
label=data['label'],
value=data['value'],
description=data.get('description'),
emoji=emoji,
default=data.get('default', False),
emoji=emoji,
)
def to_dict(self) -> SelectOptionPayload:
payload: SelectOptionPayload = {
'label': self.label,
'value': self.value,
'default': self.default,
}
payload: SelectOptionPayload = super().to_dict() # type: ignore
if self.emoji:
payload['emoji'] = self.emoji.to_dict()
if self.description:
payload['description'] = self.description
return payload
def copy(self) -> SelectOption:
return self.__class__.from_dict(self.to_dict())
class TextInput(Component):
"""Represents a text input from the Discord Bot UI Kit.
@ -1385,6 +1440,313 @@ class LabelComponent(Component):
return payload
class FileUploadComponent(Component):
"""Represents a file upload component from the Discord Bot UI Kit.
This inherits from :class:`Component`.
.. note::
The user constructible and usable type for creating a file upload is
:class:`discord.ui.FileUpload` not this one.
.. versionadded:: 2.7
Attributes
------------
custom_id: Optional[:class:`str`]
The ID of the component that gets received during an interaction.
min_values: :class:`int`
The minimum number of files that must be uploaded for this component.
Defaults to 1 and must be between 0 and 10.
max_values: :class:`int`
The maximum number of files that must be uploaded for this component.
Defaults to 1 and must be between 1 and 10.
id: Optional[:class:`int`]
The ID of this component.
required: :class:`bool`
Whether the component is required.
Defaults to ``True``.
"""
__slots__: Tuple[str, ...] = (
'custom_id',
'min_values',
'max_values',
'required',
'id',
)
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: FileUploadComponentPayload, /) -> None:
self.custom_id: str = data['custom_id']
self.min_values: int = data.get('min_values', 1)
self.max_values: int = data.get('max_values', 1)
self.required: bool = data.get('required', True)
self.id: Optional[int] = data.get('id')
@property
def type(self) -> Literal[ComponentType.file_upload]:
""":class:`ComponentType`: The type of component."""
return ComponentType.file_upload
def to_dict(self) -> FileUploadComponentPayload:
payload: FileUploadComponentPayload = {
'type': self.type.value,
'custom_id': self.custom_id,
'min_values': self.min_values,
'max_values': self.max_values,
'required': self.required,
}
if self.id is not None:
payload['id'] = self.id
return payload
class RadioGroupComponent(Component):
"""Represents a radio group component from the Discord Bot UI Kit.
This inherits from :class:`Component`.
.. note::
The user constructible and usable type for creating a radio group is
:class:`discord.ui.RadioGroup` not this one.
.. versionadded:: 2.7
Attributes
------------
custom_id: Optional[:class:`str`]
The ID of the component that gets received during an interaction.
id: Optional[:class:`int`]
The ID of this component.
required: :class:`bool`
Whether the component is required.
Defaults to ``True``.
options: List[:class:`RadioGroupOption`]
A list of options that can be selected in this group.
"""
__slots__: Tuple[str, ...] = ('custom_id', 'required', 'id', 'options')
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: RadioGroupComponentPayload, /) -> None:
self.custom_id: str = data['custom_id']
self.required: bool = data.get('required', True)
self.id: Optional[int] = data.get('id')
self.options: List[RadioGroupOption] = [RadioGroupOption.from_dict(option) for option in data.get('options', [])]
@property
def type(self) -> Literal[ComponentType.radio_group]:
""":class:`ComponentType`: The type of component."""
return ComponentType.radio_group
def to_dict(self) -> RadioGroupComponentPayload:
payload: RadioGroupComponentPayload = {
'type': self.type.value,
'custom_id': self.custom_id,
'required': self.required,
}
if self.id is not None:
payload['id'] = self.id
if self.options:
payload['options'] = [option.to_dict() for option in self.options]
return payload
class RadioGroupOption(BaseOption):
"""Represents a radio group's option
These can be created by users.
.. versionadded:: 2.7
Parameters
-----------
label: :class:`str`
The label of the option. This is displayed to users.
Can only be up to 100 characters.
value: :class:`str`
The value of the option. This is not displayed to users.
If not provided when constructed then it defaults to the label.
Can only be up to 100 characters.
description: Optional[:class:`str`]
An additional description of the option, if any.
Can only be up to 100 characters.
default: :class:`bool`
Whether this option is selected by default.
Attributes
-----------
label: :class:`str`
The label of the option. This is displayed to users.
value: :class:`str`
The value of the option. This is not displayed to users.
If not provided when constructed then it defaults to the
label.
description: Optional[:class:`str`]
An additional description of the option, if any.
default: :class:`bool`
Whether this option is selected by default.
"""
class CheckboxGroupComponent(Component):
"""Represents a checkbox group component from the Discord Bot UI Kit.
This inherits from :class:`Component`.
.. note::
The user constructible and usable type for creating a checkbox group is
:class:`discord.ui.CheckboxGroup` not this one.
.. versionadded:: 2.7
Attributes
------------
custom_id: Optional[:class:`str`]
The ID of the component that gets received during an interaction.
id: Optional[:class:`int`]
The ID of this component.
required: :class:`bool`
Whether the component is required.
Defaults to ``True``.
min_values: :class:`int`
The minimum number of options that must be selected in this component.
Must be between 0 and 10. Defaults to 0.
max_values: :class:`int`
The maximum number of options that can be selected in this component.
Must be between 1 and 10. Defaults to 1.
options: List[:class:`CheckboxGroupOption`]
A list of options that can be selected in this group.
"""
__slots__: Tuple[str, ...] = ('custom_id', 'required', 'id', 'min_values', 'max_values', 'options')
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: CheckboxGroupComponentPayload, /) -> None:
self.custom_id: str = data['custom_id']
self.required: bool = data.get('required', True)
self.id: Optional[int] = data.get('id')
self.min_values: int = data.get('min_values', 0)
self.max_values: int = data.get('max_values', 1)
self.options: List[CheckboxGroupOption] = [
CheckboxGroupOption.from_dict(option) for option in data.get('options', [])
]
@property
def type(self) -> Literal[ComponentType.checkbox_group]:
""":class:`ComponentType`: The type of component."""
return ComponentType.checkbox_group
def to_dict(self) -> CheckboxGroupComponentPayload:
payload: CheckboxGroupComponentPayload = {
'type': self.type.value,
'custom_id': self.custom_id,
'min_values': self.min_values,
'max_values': self.max_values,
'required': self.required,
}
if self.id is not None:
payload['id'] = self.id
if self.options:
payload['options'] = [option.to_dict() for option in self.options]
return payload
class CheckboxGroupOption(BaseOption):
"""Represents a checkbox group's option
These can be created by users.
.. versionadded:: 2.7
Parameters
-----------
label: :class:`str`
The label of the option. This is displayed to users.
Can only be up to 100 characters.
value: :class:`str`
The value of the option. This is not displayed to users.
If not provided when constructed then it defaults to the label.
Can only be up to 100 characters.
description: Optional[:class:`str`]
An additional description of the option, if any.
Can only be up to 100 characters.
default: :class:`bool`
Whether this option is selected by default.
Attributes
-----------
label: :class:`str`
The label of the option. This is displayed to users.
value: :class:`str`
The value of the option. This is not displayed to users.
If not provided when constructed then it defaults to the
label.
description: Optional[:class:`str`]
An additional description of the option, if any.
default: :class:`bool`
Whether this option is selected by default.
"""
class CheckboxComponent(Component):
"""Represents a checkbox component from the Discord Bot UI Kit.
This inherits from :class:`Component`.
.. note::
The user constructible and usable type for creating a checkbox is
:class:`discord.ui.Checkbox` not this one.
.. versionadded:: 2.7
Attributes
------------
custom_id: Optional[:class:`str`]
The ID of the component that gets received during an interaction.
id: Optional[:class:`int`]
The ID of this component.
default: :class:`bool`
Whether this checkbox is selected by default.
"""
__slots__: Tuple[str, ...] = ('custom_id', 'default', 'id')
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: CheckboxComponentPayload, /) -> None:
self.custom_id: str = data['custom_id']
self.id: Optional[int] = data.get('id')
self.default: bool = data.get('default', False)
@property
def type(self) -> Literal[ComponentType.checkbox]:
""":class:`ComponentType`: The type of component."""
return ComponentType.checkbox
def to_dict(self) -> CheckboxComponentPayload:
payload: CheckboxComponentPayload = {
'type': self.type.value,
'custom_id': self.custom_id,
'default': self.default,
}
if self.id is not None:
payload['id'] = self.id
return payload
def _component_factory(data: ComponentPayload, state: Optional[ConnectionState] = None) -> Optional[Component]:
if data['type'] == 1:
return ActionRow(data)
@ -1410,3 +1772,11 @@ def _component_factory(data: ComponentPayload, state: Optional[ConnectionState]
return Container(data, state)
elif data['type'] == 18:
return LabelComponent(data, state)
elif data['type'] == 19:
return FileUploadComponent(data)
elif data['type'] == 21:
return RadioGroupComponent(data)
elif data['type'] == 22:
return CheckboxGroupComponent(data)
elif data['type'] == 23:
return CheckboxComponent(data)

View file

@ -165,8 +165,8 @@ class Emoji(_EmojiTag, AssetMixin):
@property
def url(self) -> str:
""":class:`str`: Returns the URL of the emoji."""
fmt = 'gif' if self.animated else 'png'
return f'{Asset.BASE}/emojis/{self.id}.{fmt}'
end = 'webp?animated=true' if self.animated else 'png'
return f'{Asset.BASE}/emojis/{self.id}.{end}'
@property
def roles(self) -> List[Role]:

View file

@ -85,6 +85,8 @@ __all__ = (
'OnboardingMode',
'SeparatorSpacing',
'MediaItemLoadingState',
'CollectibleType',
'NameplatePalette',
)
@ -275,6 +277,17 @@ class MessageType(Enum):
guild_incident_report_false_alarm = 39
purchase_notification = 44
poll_result = 46
emoji_added = 63
def is_deletable(self) -> bool:
return self not in {
MessageType.recipient_add,
MessageType.recipient_remove,
MessageType.call,
MessageType.channel_name_change,
MessageType.channel_icon_change,
MessageType.thread_starter_message,
}
class SpeakingState(Enum):
@ -680,6 +693,11 @@ class ComponentType(Enum):
separator = 14
container = 17
label = 18
file_upload = 19
# checkpoint = 20
radio_group = 21
checkbox_group = 22
checkbox = 23
def __int__(self) -> int:
return self.value
@ -970,6 +988,24 @@ class MediaItemLoadingState(Enum):
not_found = 3
class CollectibleType(Enum):
nameplate = 'nameplate'
class NameplatePalette(Enum):
crimson = 'crimson'
berry = 'berry'
sky = 'sky'
teal = 'teal'
forest = 'forest'
bubble_gum = 'bubble_gum'
violet = 'violet'
cobalt = 'cobalt'
clover = 'clover'
lemon = 'lemon'
white = 'white'
def create_unknown_value(cls: Type[E], val: Any) -> E:
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
name = f'unknown_{val}'

View file

@ -48,6 +48,7 @@ __all__ = (
'PrivilegedIntentsRequired',
'InteractionResponded',
'MissingApplicationID',
'FFmpegProcessError',
)
APP_ID_NOT_FOUND = (
@ -74,6 +75,15 @@ class ClientException(DiscordException):
pass
class FFmpegProcessError(ClientException):
"""Exception that's raised when an FFmpeg process fails.
.. versionadded:: 2.7
"""
pass
class GatewayNotFound(DiscordException):
"""An exception that is raised when the gateway for Discord could not be found"""

Some files were not shown because too many files have changed in this diff Show more