mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-05-14 17:59:48 +00:00
Add initial support for app command localisation
This commit is contained in:
parent
eb3bc7102b
commit
2d586ae805
@ -15,5 +15,6 @@ from .models import *
|
|||||||
from .tree import *
|
from .tree import *
|
||||||
from .namespace import *
|
from .namespace import *
|
||||||
from .transformers import *
|
from .transformers import *
|
||||||
|
from .translator import *
|
||||||
from . import checks as checks
|
from . import checks as checks
|
||||||
from .checks import Cooldown as Cooldown
|
from .checks import Cooldown as Cooldown
|
||||||
|
@ -48,10 +48,11 @@ from textwrap import TextWrapper
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ..enums import AppCommandOptionType, AppCommandType
|
from ..enums import AppCommandOptionType, AppCommandType, Locale
|
||||||
from .models import Choice
|
from .models import Choice
|
||||||
from .transformers import annotation_to_parameter, CommandParameter, NoneType
|
from .transformers import annotation_to_parameter, CommandParameter, NoneType
|
||||||
from .errors import AppCommandError, CheckFailure, CommandInvokeError, CommandSignatureMismatch, CommandAlreadyRegistered
|
from .errors import AppCommandError, CheckFailure, CommandInvokeError, CommandSignatureMismatch, CommandAlreadyRegistered
|
||||||
|
from .translator import TranslationContext, Translator, locale_str
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
from ..user import User
|
from ..user import User
|
||||||
from ..member import Member
|
from ..member import Member
|
||||||
@ -281,18 +282,21 @@ def _populate_descriptions(params: Dict[str, CommandParameter], descriptions: Di
|
|||||||
param.description = '…'
|
param.description = '…'
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not isinstance(description, str):
|
if not isinstance(description, (str, locale_str)):
|
||||||
raise TypeError('description must be a string')
|
raise TypeError('description must be a string')
|
||||||
|
|
||||||
param.description = _shorten(description)
|
if isinstance(description, str):
|
||||||
|
param.description = _shorten(description)
|
||||||
|
else:
|
||||||
|
param.description = description
|
||||||
|
|
||||||
if descriptions:
|
if descriptions:
|
||||||
first = next(iter(descriptions))
|
first = next(iter(descriptions))
|
||||||
raise TypeError(f'unknown parameter given: {first}')
|
raise TypeError(f'unknown parameter given: {first}')
|
||||||
|
|
||||||
|
|
||||||
def _populate_renames(params: Dict[str, CommandParameter], renames: Dict[str, str]) -> None:
|
def _populate_renames(params: Dict[str, CommandParameter], renames: Dict[str, Union[str, locale_str]]) -> None:
|
||||||
rename_map: Dict[str, str] = {}
|
rename_map: Dict[str, Union[str, locale_str]] = {}
|
||||||
|
|
||||||
# original name to renamed name
|
# original name to renamed name
|
||||||
|
|
||||||
@ -306,7 +310,11 @@ def _populate_renames(params: Dict[str, CommandParameter], renames: Dict[str, st
|
|||||||
if name in rename_map:
|
if name in rename_map:
|
||||||
raise ValueError(f'{new_name} is already used')
|
raise ValueError(f'{new_name} is already used')
|
||||||
|
|
||||||
new_name = validate_name(new_name)
|
if isinstance(new_name, str):
|
||||||
|
new_name = validate_name(new_name)
|
||||||
|
else:
|
||||||
|
validate_name(new_name.message)
|
||||||
|
|
||||||
rename_map[name] = new_name
|
rename_map[name] = new_name
|
||||||
params[name]._rename = new_name
|
params[name]._rename = new_name
|
||||||
|
|
||||||
@ -449,6 +457,12 @@ def _get_context_menu_parameter(func: ContextMenuCallback) -> Tuple[str, Any, Ap
|
|||||||
return (parameter.name, resolved, type)
|
return (parameter.name, resolved, type)
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_translation_payload(
|
||||||
|
command: Union[Command[Any, ..., Any], Group, ContextMenu], translator: Translator
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class Command(Generic[GroupT, P, T]):
|
class Command(Generic[GroupT, P, T]):
|
||||||
"""A class that implements an application command.
|
"""A class that implements an application command.
|
||||||
|
|
||||||
@ -464,10 +478,12 @@ class Command(Generic[GroupT, P, T]):
|
|||||||
Attributes
|
Attributes
|
||||||
------------
|
------------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The name of the application command.
|
The name of the application command. When passed as an argument
|
||||||
|
to ``__init__`` this can also be :class:`locale_str`.
|
||||||
description: :class:`str`
|
description: :class:`str`
|
||||||
The description of the application command. This shows up in the UI to describe
|
The description of the application command. This shows up in the UI to describe
|
||||||
the application command.
|
the application command. When passed as an argument to ``__init__`` this
|
||||||
|
can also be :class:`locale_str`.
|
||||||
checks
|
checks
|
||||||
A list of predicates that take a :class:`~discord.Interaction` parameter
|
A list of predicates that take a :class:`~discord.Interaction` parameter
|
||||||
to indicate whether the command callback should be executed. If an exception
|
to indicate whether the command callback should be executed. If an exception
|
||||||
@ -501,16 +517,22 @@ class Command(Generic[GroupT, P, T]):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: str,
|
name: Union[str, locale_str],
|
||||||
description: str,
|
description: Union[str, locale_str],
|
||||||
callback: CommandCallback[GroupT, P, T],
|
callback: CommandCallback[GroupT, P, T],
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
parent: Optional[Group] = None,
|
parent: Optional[Group] = None,
|
||||||
guild_ids: Optional[List[int]] = None,
|
guild_ids: Optional[List[int]] = None,
|
||||||
extras: Dict[Any, Any] = MISSING,
|
extras: Dict[Any, Any] = MISSING,
|
||||||
):
|
):
|
||||||
|
name, locale = (name.message, name) if isinstance(name, locale_str) else (name, None)
|
||||||
self.name: str = validate_name(name)
|
self.name: str = validate_name(name)
|
||||||
|
self._locale_name: Optional[locale_str] = locale
|
||||||
|
description, locale = (
|
||||||
|
(description.message, description) if isinstance(description, locale_str) else (description, None)
|
||||||
|
)
|
||||||
self.description: str = description
|
self.description: str = description
|
||||||
|
self._locale_description: Optional[locale_str] = locale
|
||||||
self._attr: Optional[str] = None
|
self._attr: Optional[str] = None
|
||||||
self._callback: CommandCallback[GroupT, P, T] = callback
|
self._callback: CommandCallback[GroupT, P, T] = callback
|
||||||
self.parent: Optional[Group] = parent
|
self.parent: Optional[Group] = parent
|
||||||
@ -561,9 +583,11 @@ class Command(Generic[GroupT, P, T]):
|
|||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
copy = cls.__new__(cls)
|
copy = cls.__new__(cls)
|
||||||
copy.name = self.name
|
copy.name = self.name
|
||||||
|
copy._locale_name = self._locale_name
|
||||||
copy._guild_ids = self._guild_ids
|
copy._guild_ids = self._guild_ids
|
||||||
copy.checks = self.checks
|
copy.checks = self.checks
|
||||||
copy.description = self.description
|
copy.description = self.description
|
||||||
|
copy._locale_description = self._locale_description
|
||||||
copy.default_permissions = self.default_permissions
|
copy.default_permissions = self.default_permissions
|
||||||
copy.guild_only = self.guild_only
|
copy.guild_only = self.guild_only
|
||||||
copy.nsfw = self.nsfw
|
copy.nsfw = self.nsfw
|
||||||
@ -581,6 +605,28 @@ class Command(Generic[GroupT, P, T]):
|
|||||||
|
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
|
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||||
|
base = self.to_dict()
|
||||||
|
name_localizations: Dict[str, str] = {}
|
||||||
|
description_localizations: Dict[str, str] = {}
|
||||||
|
for locale in Locale:
|
||||||
|
if self._locale_name:
|
||||||
|
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.command_name)
|
||||||
|
if translation is not None:
|
||||||
|
name_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
if self._locale_description:
|
||||||
|
translation = await translator._checked_translate(
|
||||||
|
self._locale_description, locale, TranslationContext.command_description
|
||||||
|
)
|
||||||
|
if translation is not None:
|
||||||
|
description_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
base['name_localizations'] = name_localizations
|
||||||
|
base['description_localizations'] = description_localizations
|
||||||
|
base['options'] = [await param.get_translated_payload(translator) for param in self._params.values()]
|
||||||
|
return base
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
# If we have a parent then our type is a subcommand
|
# If we have a parent then our type is a subcommand
|
||||||
# Otherwise, the type falls back to the specific command type (e.g. slash command or context menu)
|
# Otherwise, the type falls back to the specific command type (e.g. slash command or context menu)
|
||||||
@ -929,7 +975,8 @@ class ContextMenu:
|
|||||||
Attributes
|
Attributes
|
||||||
------------
|
------------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The name of the context menu.
|
The name of the context menu. When passed as an argument to ``__init__``
|
||||||
|
this can be :class:`locale_str`.
|
||||||
type: :class:`.AppCommandType`
|
type: :class:`.AppCommandType`
|
||||||
The type of context menu application command. By default, this is inferred
|
The type of context menu application command. By default, this is inferred
|
||||||
by the parameter of the callback.
|
by the parameter of the callback.
|
||||||
@ -958,14 +1005,16 @@ class ContextMenu:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: str,
|
name: Union[str, locale_str],
|
||||||
callback: ContextMenuCallback,
|
callback: ContextMenuCallback,
|
||||||
type: AppCommandType = MISSING,
|
type: AppCommandType = MISSING,
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
guild_ids: Optional[List[int]] = None,
|
guild_ids: Optional[List[int]] = None,
|
||||||
extras: Dict[Any, Any] = MISSING,
|
extras: Dict[Any, Any] = MISSING,
|
||||||
):
|
):
|
||||||
|
name, locale = (name.message, name) if isinstance(name, locale_str) else (name, None)
|
||||||
self.name: str = validate_context_menu_name(name)
|
self.name: str = validate_context_menu_name(name)
|
||||||
|
self._locale_name: Optional[locale_str] = locale
|
||||||
self._callback: ContextMenuCallback = callback
|
self._callback: ContextMenuCallback = callback
|
||||||
(param, annotation, actual_type) = _get_context_menu_parameter(callback)
|
(param, annotation, actual_type) = _get_context_menu_parameter(callback)
|
||||||
if type is MISSING:
|
if type is MISSING:
|
||||||
@ -998,6 +1047,18 @@ class ContextMenu:
|
|||||||
""":class:`str`: Returns the fully qualified command name."""
|
""":class:`str`: Returns the fully qualified command name."""
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||||
|
base = self.to_dict()
|
||||||
|
if self._locale_name:
|
||||||
|
name_localizations: Dict[str, str] = {}
|
||||||
|
for locale in Locale:
|
||||||
|
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.command_name)
|
||||||
|
if translation is not None:
|
||||||
|
name_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
base['name_localizations'] = name_localizations
|
||||||
|
return base
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
@ -1107,11 +1168,13 @@ class Group:
|
|||||||
------------
|
------------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The name of the group. If not given, it defaults to a lower-case
|
The name of the group. If not given, it defaults to a lower-case
|
||||||
kebab-case version of the class name.
|
kebab-case version of the class name. When passed as an argument to
|
||||||
|
``__init__`` or the class this can be :class:`locale_str`.
|
||||||
description: :class:`str`
|
description: :class:`str`
|
||||||
The description of the group. This shows up in the UI to describe
|
The description of the group. This shows up in the UI to describe
|
||||||
the group. If not given, it defaults to the docstring of the
|
the group. If not given, it defaults to the docstring of the
|
||||||
class shortened to 100 characters.
|
class shortened to 100 characters. When passed as an argument to
|
||||||
|
``__init__`` or the class this can be :class:`locale_str`.
|
||||||
default_permissions: Optional[:class:`~discord.Permissions`]
|
default_permissions: Optional[:class:`~discord.Permissions`]
|
||||||
The default permissions that can execute this group on Discord. Note
|
The default permissions that can execute this group on Discord. Note
|
||||||
that server administrators can override this value in the client.
|
that server administrators can override this value in the client.
|
||||||
@ -1140,6 +1203,8 @@ class Group:
|
|||||||
__discord_app_commands_skip_init_binding__: bool = False
|
__discord_app_commands_skip_init_binding__: bool = False
|
||||||
__discord_app_commands_group_name__: str = MISSING
|
__discord_app_commands_group_name__: str = MISSING
|
||||||
__discord_app_commands_group_description__: str = MISSING
|
__discord_app_commands_group_description__: str = MISSING
|
||||||
|
__discord_app_commands_group_locale_name__: Optional[locale_str] = None
|
||||||
|
__discord_app_commands_group_locale_description__: Optional[locale_str] = None
|
||||||
__discord_app_commands_group_nsfw__: bool = False
|
__discord_app_commands_group_nsfw__: bool = False
|
||||||
__discord_app_commands_guild_only__: bool = MISSING
|
__discord_app_commands_guild_only__: bool = MISSING
|
||||||
__discord_app_commands_default_permissions__: Optional[Permissions] = MISSING
|
__discord_app_commands_default_permissions__: Optional[Permissions] = MISSING
|
||||||
@ -1151,8 +1216,8 @@ class Group:
|
|||||||
def __init_subclass__(
|
def __init_subclass__(
|
||||||
cls,
|
cls,
|
||||||
*,
|
*,
|
||||||
name: str = MISSING,
|
name: Union[str, locale_str] = MISSING,
|
||||||
description: str = MISSING,
|
description: Union[str, locale_str] = MISSING,
|
||||||
guild_only: bool = MISSING,
|
guild_only: bool = MISSING,
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
default_permissions: Optional[Permissions] = MISSING,
|
default_permissions: Optional[Permissions] = MISSING,
|
||||||
@ -1175,16 +1240,22 @@ class Group:
|
|||||||
|
|
||||||
if name is MISSING:
|
if name is MISSING:
|
||||||
cls.__discord_app_commands_group_name__ = validate_name(_to_kebab_case(cls.__name__))
|
cls.__discord_app_commands_group_name__ = validate_name(_to_kebab_case(cls.__name__))
|
||||||
else:
|
elif isinstance(name, str):
|
||||||
cls.__discord_app_commands_group_name__ = validate_name(name)
|
cls.__discord_app_commands_group_name__ = validate_name(name)
|
||||||
|
else:
|
||||||
|
cls.__discord_app_commands_group_name__ = validate_name(name.message)
|
||||||
|
cls.__discord_app_commands_group_locale_name__ = name
|
||||||
|
|
||||||
if description is MISSING:
|
if description is MISSING:
|
||||||
if cls.__doc__ is None:
|
if cls.__doc__ is None:
|
||||||
cls.__discord_app_commands_group_description__ = '…'
|
cls.__discord_app_commands_group_description__ = '…'
|
||||||
else:
|
else:
|
||||||
cls.__discord_app_commands_group_description__ = _shorten(cls.__doc__)
|
cls.__discord_app_commands_group_description__ = _shorten(cls.__doc__)
|
||||||
else:
|
elif isinstance(description, str):
|
||||||
cls.__discord_app_commands_group_description__ = description
|
cls.__discord_app_commands_group_description__ = description
|
||||||
|
else:
|
||||||
|
cls.__discord_app_commands_group_description__ = description.message
|
||||||
|
cls.__discord_app_commands_group_locale_description__ = description
|
||||||
|
|
||||||
if guild_only is not MISSING:
|
if guild_only is not MISSING:
|
||||||
cls.__discord_app_commands_guild_only__ = guild_only
|
cls.__discord_app_commands_guild_only__ = guild_only
|
||||||
@ -1199,8 +1270,8 @@ class Group:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: str = MISSING,
|
name: Union[str, locale_str] = MISSING,
|
||||||
description: str = MISSING,
|
description: Union[str, locale_str] = MISSING,
|
||||||
parent: Optional[Group] = None,
|
parent: Optional[Group] = None,
|
||||||
guild_ids: Optional[List[int]] = None,
|
guild_ids: Optional[List[int]] = None,
|
||||||
guild_only: bool = MISSING,
|
guild_only: bool = MISSING,
|
||||||
@ -1209,8 +1280,28 @@ class Group:
|
|||||||
extras: Dict[Any, Any] = MISSING,
|
extras: Dict[Any, Any] = MISSING,
|
||||||
):
|
):
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
self.name: str = validate_name(name) if name is not MISSING else cls.__discord_app_commands_group_name__
|
|
||||||
self.description: str = description or cls.__discord_app_commands_group_description__
|
if name is MISSING:
|
||||||
|
name, locale = cls.__discord_app_commands_group_name__, cls.__discord_app_commands_group_locale_name__
|
||||||
|
elif isinstance(name, str):
|
||||||
|
name, locale = validate_name(name), None
|
||||||
|
else:
|
||||||
|
name, locale = validate_name(name.message), name
|
||||||
|
self.name: str = name
|
||||||
|
self._locale_name: Optional[locale_str] = locale
|
||||||
|
|
||||||
|
if description is MISSING:
|
||||||
|
description, locale = (
|
||||||
|
cls.__discord_app_commands_group_description__,
|
||||||
|
cls.__discord_app_commands_group_locale_description__,
|
||||||
|
)
|
||||||
|
elif isinstance(description, str):
|
||||||
|
description, locale = description, None
|
||||||
|
else:
|
||||||
|
description, locale = description.message, description
|
||||||
|
self.description: str = description
|
||||||
|
self._locale_description: Optional[locale_str] = locale
|
||||||
|
|
||||||
self._attr: Optional[str] = None
|
self._attr: Optional[str] = None
|
||||||
self._owner_cls: Optional[Type[Any]] = None
|
self._owner_cls: Optional[Type[Any]] = None
|
||||||
self._guild_ids: Optional[List[int]] = guild_ids or getattr(cls, '__discord_app_commands_default_guilds__', None)
|
self._guild_ids: Optional[List[int]] = guild_ids or getattr(cls, '__discord_app_commands_default_guilds__', None)
|
||||||
@ -1291,8 +1382,10 @@ class Group:
|
|||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
copy = cls.__new__(cls)
|
copy = cls.__new__(cls)
|
||||||
copy.name = self.name
|
copy.name = self.name
|
||||||
|
copy._locale_name = self._locale_name
|
||||||
copy._guild_ids = self._guild_ids
|
copy._guild_ids = self._guild_ids
|
||||||
copy.description = self.description
|
copy.description = self.description
|
||||||
|
copy._locale_description = self._locale_description
|
||||||
copy.parent = parent
|
copy.parent = parent
|
||||||
copy.module = self.module
|
copy.module = self.module
|
||||||
copy.default_permissions = self.default_permissions
|
copy.default_permissions = self.default_permissions
|
||||||
@ -1321,6 +1414,28 @@ class Group:
|
|||||||
|
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
|
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||||
|
base = self.to_dict()
|
||||||
|
name_localizations: Dict[str, str] = {}
|
||||||
|
description_localizations: Dict[str, str] = {}
|
||||||
|
for locale in Locale:
|
||||||
|
if self._locale_name:
|
||||||
|
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.command_name)
|
||||||
|
if translation is not None:
|
||||||
|
name_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
if self._locale_description:
|
||||||
|
translation = await translator._checked_translate(
|
||||||
|
self._locale_description, locale, TranslationContext.command_description
|
||||||
|
)
|
||||||
|
if translation is not None:
|
||||||
|
description_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
base['name_localizations'] = name_localizations
|
||||||
|
base['description_localizations'] = description_localizations
|
||||||
|
base['options'] = [await child.get_translated_payload(translator) for child in self._children.values()]
|
||||||
|
return base
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
# If this has a parent command then it's part of a subcommand group
|
# If this has a parent command then it's part of a subcommand group
|
||||||
# Otherwise, it's just a regular command
|
# Otherwise, it's just a regular command
|
||||||
@ -1535,8 +1650,8 @@ class Group:
|
|||||||
def command(
|
def command(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: str = MISSING,
|
name: Union[str, locale_str] = MISSING,
|
||||||
description: str = MISSING,
|
description: Union[str, locale_str] = MISSING,
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
extras: Dict[Any, Any] = MISSING,
|
extras: Dict[Any, Any] = MISSING,
|
||||||
) -> Callable[[CommandCallback[GroupT, P, T]], Command[GroupT, P, T]]:
|
) -> Callable[[CommandCallback[GroupT, P, T]], Command[GroupT, P, T]]:
|
||||||
@ -1544,10 +1659,10 @@ class Group:
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
name: :class:`str`
|
name: Union[:class:`str`, :class:`locale_str`]
|
||||||
The name of the application command. If not given, it defaults to a lower-case
|
The name of the application command. If not given, it defaults to a lower-case
|
||||||
version of the callback name.
|
version of the callback name.
|
||||||
description: :class:`str`
|
description: Union[:class:`str`, :class:`locale_str`]
|
||||||
The description of the application command. This shows up in the UI to describe
|
The description of the application command. This shows up in the UI to describe
|
||||||
the application command. If not given, it defaults to the first line of the docstring
|
the application command. If not given, it defaults to the first line of the docstring
|
||||||
of the callback shortened to 100 characters.
|
of the callback shortened to 100 characters.
|
||||||
@ -1586,8 +1701,8 @@ class Group:
|
|||||||
|
|
||||||
def command(
|
def command(
|
||||||
*,
|
*,
|
||||||
name: str = MISSING,
|
name: Union[str, locale_str] = MISSING,
|
||||||
description: str = MISSING,
|
description: Union[str, locale_str] = MISSING,
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
extras: Dict[Any, Any] = MISSING,
|
extras: Dict[Any, Any] = MISSING,
|
||||||
) -> Callable[[CommandCallback[GroupT, P, T]], Command[GroupT, P, T]]:
|
) -> Callable[[CommandCallback[GroupT, P, T]], Command[GroupT, P, T]]:
|
||||||
@ -1637,7 +1752,7 @@ def command(
|
|||||||
|
|
||||||
def context_menu(
|
def context_menu(
|
||||||
*,
|
*,
|
||||||
name: str = MISSING,
|
name: Union[str, locale_str] = MISSING,
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
extras: Dict[Any, Any] = MISSING,
|
extras: Dict[Any, Any] = MISSING,
|
||||||
) -> Callable[[ContextMenuCallback], ContextMenu]:
|
) -> Callable[[ContextMenuCallback], ContextMenu]:
|
||||||
@ -1662,7 +1777,7 @@ def context_menu(
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
name: :class:`str`
|
name: Union[:class:`str`, :class:`locale_str`]
|
||||||
The name of the context menu command. If not given, it defaults to a title-case
|
The name of the context menu command. If not given, it defaults to a title-case
|
||||||
version of the callback name. Note that unlike regular slash commands this can
|
version of the callback name. Note that unlike regular slash commands this can
|
||||||
have spaces and upper case characters in the name.
|
have spaces and upper case characters in the name.
|
||||||
@ -1685,7 +1800,7 @@ def context_menu(
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def describe(**parameters: str) -> Callable[[T], T]:
|
def describe(**parameters: Union[str, locale_str]) -> Callable[[T], T]:
|
||||||
r"""Describes the given parameters by their name using the key of the keyword argument
|
r"""Describes the given parameters by their name using the key of the keyword argument
|
||||||
as the name.
|
as the name.
|
||||||
|
|
||||||
@ -1700,7 +1815,7 @@ def describe(**parameters: str) -> Callable[[T], T]:
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
\*\*parameters
|
\*\*parameters: Union[:class:`str`, :class:`locale_str`]
|
||||||
The description of the parameters.
|
The description of the parameters.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
@ -1723,7 +1838,7 @@ def describe(**parameters: str) -> Callable[[T], T]:
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def rename(**parameters: str) -> Callable[[T], T]:
|
def rename(**parameters: Union[str, locale_str]) -> Callable[[T], T]:
|
||||||
r"""Renames the given parameters by their name using the key of the keyword argument
|
r"""Renames the given parameters by their name using the key of the keyword argument
|
||||||
as the name.
|
as the name.
|
||||||
|
|
||||||
@ -1741,7 +1856,7 @@ def rename(**parameters: str) -> Callable[[T], T]:
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
\*\*parameters
|
\*\*parameters: Union[:class:`str`, :class:`locale_str`]
|
||||||
The name of the parameters.
|
The name of the parameters.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
|
@ -27,13 +27,14 @@ from __future__ import annotations
|
|||||||
from typing import Any, TYPE_CHECKING, List, Optional, Union
|
from typing import Any, TYPE_CHECKING, List, Optional, Union
|
||||||
|
|
||||||
|
|
||||||
from ..enums import AppCommandOptionType, AppCommandType
|
from ..enums import AppCommandOptionType, AppCommandType, Locale
|
||||||
from ..errors import DiscordException
|
from ..errors import DiscordException
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'AppCommandError',
|
'AppCommandError',
|
||||||
'CommandInvokeError',
|
'CommandInvokeError',
|
||||||
'TransformerError',
|
'TransformerError',
|
||||||
|
'TranslationError',
|
||||||
'CheckFailure',
|
'CheckFailure',
|
||||||
'CommandAlreadyRegistered',
|
'CommandAlreadyRegistered',
|
||||||
'CommandSignatureMismatch',
|
'CommandSignatureMismatch',
|
||||||
@ -51,6 +52,7 @@ __all__ = (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .commands import Command, Group, ContextMenu
|
from .commands import Command, Group, ContextMenu
|
||||||
from .transformers import Transformer
|
from .transformers import Transformer
|
||||||
|
from .translator import TranslationContext, locale_str
|
||||||
from ..types.snowflake import Snowflake, SnowflakeList
|
from ..types.snowflake import Snowflake, SnowflakeList
|
||||||
from .checks import Cooldown
|
from .checks import Cooldown
|
||||||
|
|
||||||
@ -133,6 +135,50 @@ class TransformerError(AppCommandError):
|
|||||||
super().__init__(f'Failed to convert {value} to {transformer._error_display_name!s}')
|
super().__init__(f'Failed to convert {value} to {transformer._error_display_name!s}')
|
||||||
|
|
||||||
|
|
||||||
|
class TranslationError(AppCommandError):
|
||||||
|
"""An exception raised when the library fails to translate a string.
|
||||||
|
|
||||||
|
This inherits from :exc:`~discord.app_commands.AppCommandError`.
|
||||||
|
|
||||||
|
If an exception occurs while calling :meth:`Translator.translate` that does
|
||||||
|
not subclass this then the exception is wrapped into this exception.
|
||||||
|
The original exception can be retrieved using the ``__cause__`` attribute.
|
||||||
|
Otherwise it will be propagated as-is.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
string: Optional[Union[:class:`str`, :class:`locale_str`]]
|
||||||
|
The string that caused the error, if any.
|
||||||
|
locale: Optional[:class:`~discord.Locale`]
|
||||||
|
The locale that caused the error, if any.
|
||||||
|
context: :class:`~discord.app_commands.TranslationContext`
|
||||||
|
The context of the translation that triggered the error.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*msg: str,
|
||||||
|
string: Optional[Union[str, locale_str]] = None,
|
||||||
|
locale: Optional[Locale] = None,
|
||||||
|
context: TranslationContext,
|
||||||
|
) -> None:
|
||||||
|
self.string: Optional[Union[str, locale_str]] = string
|
||||||
|
self.locale: Optional[Locale] = locale
|
||||||
|
self.context: TranslationContext = context
|
||||||
|
|
||||||
|
if msg:
|
||||||
|
super().__init__(*msg)
|
||||||
|
else:
|
||||||
|
ctx = context.name.replace('_', ' ')
|
||||||
|
fmt = f'Failed to translate {self.string!r} in a {ctx}'
|
||||||
|
if self.locale is not None:
|
||||||
|
fmt = f'{fmt} in the {self.locale.value} locale'
|
||||||
|
|
||||||
|
super().__init__(fmt)
|
||||||
|
|
||||||
|
|
||||||
class CheckFailure(AppCommandError):
|
class CheckFailure(AppCommandError):
|
||||||
"""An exception raised when check predicates in a command have failed.
|
"""An exception raised when check predicates in a command have failed.
|
||||||
|
|
||||||
|
@ -26,8 +26,9 @@ from __future__ import annotations
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .errors import MissingApplicationID
|
from .errors import MissingApplicationID
|
||||||
|
from .translator import Translator, TranslationContext, locale_str
|
||||||
from ..permissions import Permissions
|
from ..permissions import Permissions
|
||||||
from ..enums import AppCommandOptionType, AppCommandType, AppCommandPermissionType, ChannelType, try_enum
|
from ..enums import AppCommandOptionType, AppCommandType, AppCommandPermissionType, ChannelType, Locale, try_enum
|
||||||
from ..mixins import Hashable
|
from ..mixins import Hashable
|
||||||
from ..utils import _get_as_snowflake, parse_time, snowflake_time, MISSING
|
from ..utils import _get_as_snowflake, parse_time, snowflake_time, MISSING
|
||||||
from ..object import Object
|
from ..object import Object
|
||||||
@ -56,7 +57,6 @@ def is_app_command_argument_type(value: int) -> bool:
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..types.command import (
|
from ..types.command import (
|
||||||
ApplicationCommand as ApplicationCommandPayload,
|
ApplicationCommand as ApplicationCommandPayload,
|
||||||
ApplicationCommandOptionChoice,
|
|
||||||
ApplicationCommandOption,
|
ApplicationCommandOption,
|
||||||
ApplicationCommandPermissions,
|
ApplicationCommandPermissions,
|
||||||
GuildApplicationCommandPermissions,
|
GuildApplicationCommandPermissions,
|
||||||
@ -407,17 +407,22 @@ class Choice(Generic[ChoiceT]):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: :class:`str`
|
name: Union[:class:`str`, :class:`locale_str`]
|
||||||
The name of the choice. Used for display purposes.
|
The name of the choice. Used for display purposes.
|
||||||
|
name_localizations: Dict[:class:`~discord.Locale`, :class:`str`]
|
||||||
|
The localised names of the choice. Used for display purposes.
|
||||||
value: Union[:class:`int`, :class:`str`, :class:`float`]
|
value: Union[:class:`int`, :class:`str`, :class:`float`]
|
||||||
The value of the choice.
|
The value of the choice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('name', 'value')
|
__slots__ = ('name', 'value', '_locale_name', 'name_localizations')
|
||||||
|
|
||||||
def __init__(self, *, name: str, value: ChoiceT):
|
def __init__(self, *, name: Union[str, locale_str], value: ChoiceT, name_localizations: Dict[Locale, str] = MISSING):
|
||||||
|
name, locale = (name.message, name) if isinstance(name, locale_str) else (name, None)
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
|
self._locale_name: Optional[locale_str] = locale
|
||||||
self.value: ChoiceT = value
|
self.value: ChoiceT = value
|
||||||
|
self.name_localizations: Dict[Locale, str] = MISSING
|
||||||
|
|
||||||
def __eq__(self, o: object) -> bool:
|
def __eq__(self, o: object) -> bool:
|
||||||
return isinstance(o, Choice) and self.name == o.name and self.value == o.value
|
return isinstance(o, Choice) and self.name == o.name and self.value == o.value
|
||||||
@ -441,11 +446,28 @@ class Choice(Generic[ChoiceT]):
|
|||||||
f'invalid Choice value type given, expected int, str, or float but received {self.value.__class__!r}'
|
f'invalid Choice value type given, expected int, str, or float but received {self.value.__class__!r}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_dict(self) -> ApplicationCommandOptionChoice:
|
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||||
return { # type: ignore
|
base = self.to_dict()
|
||||||
|
name_localizations: Dict[str, str] = {}
|
||||||
|
if self._locale_name:
|
||||||
|
for locale in Locale:
|
||||||
|
translation = await translator._checked_translate(self._locale_name, locale, TranslationContext.choice_name)
|
||||||
|
if translation is not None:
|
||||||
|
name_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
if name_localizations:
|
||||||
|
base['name_localizations'] = name_localizations
|
||||||
|
|
||||||
|
return base
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
base = {
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'value': self.value,
|
'value': self.value,
|
||||||
}
|
}
|
||||||
|
if self.name_localizations is not MISSING:
|
||||||
|
base['name_localizations'] = {str(k): v for k, v in self.name_localizations.items()}
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
class AppCommandChannel(Hashable):
|
class AppCommandChannel(Hashable):
|
||||||
|
@ -46,10 +46,11 @@ from typing import (
|
|||||||
|
|
||||||
from .errors import AppCommandError, TransformerError
|
from .errors import AppCommandError, TransformerError
|
||||||
from .models import AppCommandChannel, AppCommandThread, Choice
|
from .models import AppCommandChannel, AppCommandThread, Choice
|
||||||
|
from .translator import locale_str, Translator, TranslationContext
|
||||||
from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel
|
from ..channel import StageChannel, VoiceChannel, TextChannel, CategoryChannel
|
||||||
from ..abc import GuildChannel
|
from ..abc import GuildChannel
|
||||||
from ..threads import Thread
|
from ..threads import Thread
|
||||||
from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType
|
from ..enums import Enum as InternalEnum, AppCommandOptionType, ChannelType, Locale
|
||||||
from ..utils import MISSING, maybe_coroutine
|
from ..utils import MISSING, maybe_coroutine
|
||||||
from ..user import User
|
from ..user import User
|
||||||
from ..role import Role
|
from ..role import Role
|
||||||
@ -95,8 +96,10 @@ class CommandParameter:
|
|||||||
The maximum supported value for this parameter.
|
The maximum supported value for this parameter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# The name of the parameter is *always* the parameter name in the code
|
||||||
|
# Therefore, it can't be Union[str, locale_str]
|
||||||
name: str = MISSING
|
name: str = MISSING
|
||||||
description: str = MISSING
|
description: Union[str, locale_str] = MISSING
|
||||||
required: bool = MISSING
|
required: bool = MISSING
|
||||||
default: Any = MISSING
|
default: Any = MISSING
|
||||||
choices: List[Choice[Union[str, int, float]]] = MISSING
|
choices: List[Choice[Union[str, int, float]]] = MISSING
|
||||||
@ -105,9 +108,49 @@ class CommandParameter:
|
|||||||
min_value: Optional[Union[int, float]] = None
|
min_value: Optional[Union[int, float]] = None
|
||||||
max_value: Optional[Union[int, float]] = None
|
max_value: Optional[Union[int, float]] = None
|
||||||
autocomplete: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None
|
autocomplete: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None
|
||||||
_rename: str = MISSING
|
_rename: Union[str, locale_str] = MISSING
|
||||||
_annotation: Any = MISSING
|
_annotation: Any = MISSING
|
||||||
|
|
||||||
|
async def get_translated_payload(self, translator: Translator) -> Dict[str, Any]:
|
||||||
|
base = self.to_dict()
|
||||||
|
|
||||||
|
needs_name_translations = isinstance(self._rename, locale_str)
|
||||||
|
needs_description_translations = isinstance(self.description, locale_str)
|
||||||
|
name_localizations: Dict[str, str] = {}
|
||||||
|
description_localizations: Dict[str, str] = {}
|
||||||
|
for locale in Locale:
|
||||||
|
if needs_name_translations:
|
||||||
|
translation = await translator._checked_translate(
|
||||||
|
self._rename, # type: ignore # This will always be locale_str
|
||||||
|
locale,
|
||||||
|
TranslationContext.parameter_name,
|
||||||
|
)
|
||||||
|
if translation is not None:
|
||||||
|
name_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
if needs_description_translations:
|
||||||
|
translation = await translator._checked_translate(
|
||||||
|
self.description, # type: ignore # This will always be locale_str
|
||||||
|
locale,
|
||||||
|
TranslationContext.parameter_description,
|
||||||
|
)
|
||||||
|
if translation is not None:
|
||||||
|
description_localizations[locale.value] = translation
|
||||||
|
|
||||||
|
if isinstance(self.description, locale_str):
|
||||||
|
base['description'] = self.description.message
|
||||||
|
|
||||||
|
if self.choices:
|
||||||
|
base['choices'] = [await choice.get_translated_payload(translator) for choice in self.choices]
|
||||||
|
|
||||||
|
if name_localizations:
|
||||||
|
base['name_localizations'] = name_localizations
|
||||||
|
|
||||||
|
if description_localizations:
|
||||||
|
base['description_localizations'] = description_localizations
|
||||||
|
|
||||||
|
return base
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
base = {
|
base = {
|
||||||
'type': self.type.value,
|
'type': self.type.value,
|
||||||
@ -158,7 +201,7 @@ class CommandParameter:
|
|||||||
@property
|
@property
|
||||||
def display_name(self) -> str:
|
def display_name(self) -> str:
|
||||||
""":class:`str`: The name of the parameter as it should be displayed to the user."""
|
""":class:`str`: The name of the parameter as it should be displayed to the user."""
|
||||||
return self.name if self._rename is MISSING else self._rename
|
return self.name if self._rename is MISSING else str(self._rename)
|
||||||
|
|
||||||
|
|
||||||
class Transformer:
|
class Transformer:
|
||||||
|
195
discord/app_commands/translator.py
Normal file
195
discord/app_commands/translator.py
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
"""
|
||||||
|
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 Any, Optional
|
||||||
|
from .errors import TranslationError
|
||||||
|
from ..enums import Enum, Locale
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'TranslationContext',
|
||||||
|
'Translator',
|
||||||
|
'locale_str',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TranslationContext(Enum):
|
||||||
|
command_name = 0
|
||||||
|
command_description = 1
|
||||||
|
parameter_name = 2
|
||||||
|
parameter_description = 3
|
||||||
|
choice_name = 4
|
||||||
|
|
||||||
|
|
||||||
|
class Translator:
|
||||||
|
"""A class that handles translations for commands, parameters, and choices.
|
||||||
|
|
||||||
|
Translations are done lazily in order to allow for async enabled translations as well
|
||||||
|
as supporting a wide array of translation systems such as :mod:`gettext` and
|
||||||
|
`Project Fluent <https://projectfluent.org>`_.
|
||||||
|
|
||||||
|
In order for a translator to be used, it must be set using the :meth:`CommandTree.set_translator`
|
||||||
|
method. The translation flow for a string is as follows:
|
||||||
|
|
||||||
|
1. Use :class:`locale_str` instead of :class:`str` in areas of a command you want to be translated.
|
||||||
|
- Currently, these are command names, command descriptions, parameter names, parameter descriptions, and choice names.
|
||||||
|
- This can also be used inside the :func:`~discord.app_commands.describe` decorator.
|
||||||
|
2. Call :meth:`CommandTree.set_translator` to the translator instance that will handle the translations.
|
||||||
|
3. Call :meth:`CommandTree.sync`
|
||||||
|
4. The library will call :meth:`Translator.translate` on all the relevant strings being translated.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def load(self) -> None:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
An asynchronous setup function for loading the translation system.
|
||||||
|
|
||||||
|
The default implementation does nothing.
|
||||||
|
|
||||||
|
This is invoked when :meth:`CommandTree.set_translator` is called.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def unload(self) -> None:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
An asynchronous teardown function for unloading the translation system.
|
||||||
|
|
||||||
|
The default implementation does nothing.
|
||||||
|
|
||||||
|
This is invoked when :meth:`CommandTree.set_translator` is called
|
||||||
|
if a tree already has a translator or when :meth:`discord.Client.close` is called.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _checked_translate(self, string: locale_str, locale: Locale, context: TranslationContext) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
return await self.translate(string, locale, context)
|
||||||
|
except TranslationError:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise TranslationError(string=string, locale=locale, context=context) from e
|
||||||
|
|
||||||
|
async def translate(self, string: locale_str, locale: Locale, context: TranslationContext) -> Optional[str]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Translates the given string to the specified locale.
|
||||||
|
|
||||||
|
If the string cannot be translated, ``None`` should be returned.
|
||||||
|
|
||||||
|
The default implementation returns ``None``.
|
||||||
|
|
||||||
|
If an exception is raised in this method, it should inherit from :exc:`TranslationError`.
|
||||||
|
If it doesn't, then when this is called the exception will be chained with it instead.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
string: :class:`locale_str`
|
||||||
|
The string being translated.
|
||||||
|
locale: :class:`~discord.Locale`
|
||||||
|
The locale being requested for translation.
|
||||||
|
context: :class:`TranslationContext`
|
||||||
|
The translation context where the string originated from.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class locale_str:
|
||||||
|
"""Marks a string as ready for translation.
|
||||||
|
|
||||||
|
This is done lazily and is not actually translated until :meth:`CommandTree.sync` is called.
|
||||||
|
|
||||||
|
The sync method then ultimately defers the responsibility of translating to the :class:`Translator`
|
||||||
|
instance used by the :class:`CommandTree`. For more information on the translation flow, see the
|
||||||
|
:class:`Translator` documentation.
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: str(x)
|
||||||
|
|
||||||
|
Returns the message passed to the string.
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if the string is equal to another string.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if the string is not equal to another string.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the hash of the string.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
------------
|
||||||
|
message: :class:`str`
|
||||||
|
The message being translated. Once set, this cannot be changed.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This must be the default "message" that you send to Discord.
|
||||||
|
Discord sends this message back to the library and the library
|
||||||
|
uses it to access the data in order to dispatch commands.
|
||||||
|
|
||||||
|
For example, in a command name context, if the command
|
||||||
|
name is ``foo`` then the message *must* also be ``foo``.
|
||||||
|
For other translation systems that require a message ID such
|
||||||
|
as Fluent, consider using a keyword argument to pass it in.
|
||||||
|
extras: :class:`dict`
|
||||||
|
A dict of user provided extras to attach to the translated string.
|
||||||
|
This can be used to add more context, information, or any metadata necessary
|
||||||
|
to aid in actually translating the string.
|
||||||
|
|
||||||
|
Since these are passed via keyword arguments, the keys are strings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message: str, /, **kwargs: Any) -> None:
|
||||||
|
self.__message: str = message
|
||||||
|
self.extras: dict[str, Any] = kwargs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self) -> str:
|
||||||
|
return self.__message
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.__message
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
kwargs = ', '.join(f'{k}={v!r}' for k, v in self.extras.items())
|
||||||
|
if kwargs:
|
||||||
|
return f'{self.__class__.__name__}({self.__message!r}, {kwargs})'
|
||||||
|
return f'{self.__class__.__name__}({self.__message!r})'
|
||||||
|
|
||||||
|
def __eq__(self, obj: object) -> bool:
|
||||||
|
return isinstance(obj, locale_str) and self.message == obj.message
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.__message)
|
@ -58,6 +58,7 @@ from .errors import (
|
|||||||
CommandLimitReached,
|
CommandLimitReached,
|
||||||
MissingApplicationID,
|
MissingApplicationID,
|
||||||
)
|
)
|
||||||
|
from .translator import Translator, locale_str
|
||||||
from ..errors import ClientException
|
from ..errors import ClientException
|
||||||
from ..enums import AppCommandType, InteractionType
|
from ..enums import AppCommandType, InteractionType
|
||||||
from ..utils import MISSING, _get_as_snowflake, _is_submodule
|
from ..utils import MISSING, _get_as_snowflake, _is_submodule
|
||||||
@ -832,8 +833,8 @@ class CommandTree(Generic[ClientT]):
|
|||||||
def command(
|
def command(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: str = MISSING,
|
name: Union[str, locale_str] = MISSING,
|
||||||
description: str = MISSING,
|
description: Union[str, locale_str] = MISSING,
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
guild: Optional[Snowflake] = MISSING,
|
guild: Optional[Snowflake] = MISSING,
|
||||||
guilds: Sequence[Snowflake] = MISSING,
|
guilds: Sequence[Snowflake] = MISSING,
|
||||||
@ -843,10 +844,10 @@ class CommandTree(Generic[ClientT]):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
name: :class:`str`
|
name: Union[:class:`str`, :class:`locale_str`]
|
||||||
The name of the application command. If not given, it defaults to a lower-case
|
The name of the application command. If not given, it defaults to a lower-case
|
||||||
version of the callback name.
|
version of the callback name.
|
||||||
description: :class:`str`
|
description: Union[:class:`str`, :class:`locale_str`]
|
||||||
The description of the application command. This shows up in the UI to describe
|
The description of the application command. This shows up in the UI to describe
|
||||||
the application command. If not given, it defaults to the first line of the docstring
|
the application command. If not given, it defaults to the first line of the docstring
|
||||||
of the callback shortened to 100 characters.
|
of the callback shortened to 100 characters.
|
||||||
@ -894,7 +895,7 @@ class CommandTree(Generic[ClientT]):
|
|||||||
def context_menu(
|
def context_menu(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
name: str = MISSING,
|
name: Union[str, locale_str] = MISSING,
|
||||||
nsfw: bool = False,
|
nsfw: bool = False,
|
||||||
guild: Optional[Snowflake] = MISSING,
|
guild: Optional[Snowflake] = MISSING,
|
||||||
guilds: Sequence[Snowflake] = MISSING,
|
guilds: Sequence[Snowflake] = MISSING,
|
||||||
@ -921,7 +922,7 @@ class CommandTree(Generic[ClientT]):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
------------
|
------------
|
||||||
name: :class:`str`
|
name: Union[:class:`str`, :class:`locale_str`]
|
||||||
The name of the context menu command. If not given, it defaults to a title-case
|
The name of the context menu command. If not given, it defaults to a title-case
|
||||||
version of the callback name. Note that unlike regular slash commands this can
|
version of the callback name. Note that unlike regular slash commands this can
|
||||||
have spaces and upper case characters in the name.
|
have spaces and upper case characters in the name.
|
||||||
@ -952,11 +953,54 @@ class CommandTree(Generic[ClientT]):
|
|||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@property
|
||||||
|
def translator(self) -> Optional[Translator]:
|
||||||
|
"""Optional[:class:`Translator`]: The translator, if any, responsible for handling translation of commands.
|
||||||
|
|
||||||
|
To change the translator, use :meth:`set_translator`.
|
||||||
|
"""
|
||||||
|
return self._state._translator
|
||||||
|
|
||||||
|
async def set_translator(self, translator: Optional[Translator]) -> None:
|
||||||
|
"""Sets the translator to use for translating commands.
|
||||||
|
|
||||||
|
If a translator was previously set, it will be unloaded using its
|
||||||
|
:meth:`Translator.unload` method.
|
||||||
|
|
||||||
|
When a translator is set, it will be loaded using its :meth:`Translator.load` method.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
translator: Optional[:class:`Translator`]
|
||||||
|
The translator to use. If ``None`` then the translator is just removed and unloaded.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
TypeError
|
||||||
|
The translator was not ``None`` or a :class:`Translator` instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if translator is not None and not isinstance(translator, Translator):
|
||||||
|
raise TypeError(f'expected None or Translator instance, received {translator.__class__!r} instead')
|
||||||
|
|
||||||
|
old_translator = self._state._translator
|
||||||
|
if old_translator is not None:
|
||||||
|
await old_translator.unload()
|
||||||
|
|
||||||
|
if translator is None:
|
||||||
|
self._state._translator = None
|
||||||
|
else:
|
||||||
|
await translator.load()
|
||||||
|
self._state._translator = translator
|
||||||
|
|
||||||
async def sync(self, *, guild: Optional[Snowflake] = None) -> List[AppCommand]:
|
async def sync(self, *, guild: Optional[Snowflake] = None) -> List[AppCommand]:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Syncs the application commands to Discord.
|
Syncs the application commands to Discord.
|
||||||
|
|
||||||
|
This also runs the translator to get the translated strings necessary for
|
||||||
|
feeding back into Discord.
|
||||||
|
|
||||||
This must be called for the application commands to show up.
|
This must be called for the application commands to show up.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -973,6 +1017,8 @@ class CommandTree(Generic[ClientT]):
|
|||||||
The client does not have the ``applications.commands`` scope in the guild.
|
The client does not have the ``applications.commands`` scope in the guild.
|
||||||
MissingApplicationID
|
MissingApplicationID
|
||||||
The client does not have an application ID.
|
The client does not have an application ID.
|
||||||
|
TranslationError
|
||||||
|
An error occurred while translating the commands.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
@ -984,7 +1030,13 @@ class CommandTree(Generic[ClientT]):
|
|||||||
raise MissingApplicationID
|
raise MissingApplicationID
|
||||||
|
|
||||||
commands = self._get_all_commands(guild=guild)
|
commands = self._get_all_commands(guild=guild)
|
||||||
payload = [command.to_dict() for command in commands]
|
|
||||||
|
translator = self.translator
|
||||||
|
if translator:
|
||||||
|
payload = [await command.get_translated_payload(translator) for command in commands]
|
||||||
|
else:
|
||||||
|
payload = [command.to_dict() for command in commands]
|
||||||
|
|
||||||
if guild is None:
|
if guild is None:
|
||||||
data = await self._http.bulk_upsert_global_commands(self.client.application_id, payload=payload)
|
data = await self._http.bulk_upsert_global_commands(self.client.application_id, payload=payload)
|
||||||
else:
|
else:
|
||||||
|
@ -737,12 +737,7 @@ class Client:
|
|||||||
|
|
||||||
self._closed = True
|
self._closed = True
|
||||||
|
|
||||||
for voice in self.voice_clients:
|
await self._connection.close()
|
||||||
try:
|
|
||||||
await voice.disconnect(force=True)
|
|
||||||
except Exception:
|
|
||||||
# if an error happens during disconnects, disregard it.
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.ws is not None and self.ws.open:
|
if self.ws is not None and self.ws.open:
|
||||||
await self.ws.close(code=1000)
|
await self.ws.close(code=1000)
|
||||||
|
@ -253,7 +253,7 @@ class BotBase(GroupMixin[None]):
|
|||||||
|
|
||||||
def hybrid_command(
|
def hybrid_command(
|
||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
with_app_command: bool = True,
|
with_app_command: bool = True,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
@ -277,7 +277,7 @@ class BotBase(GroupMixin[None]):
|
|||||||
|
|
||||||
def hybrid_group(
|
def hybrid_group(
|
||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
with_app_command: bool = True,
|
with_app_command: bool = True,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
|
@ -124,12 +124,12 @@ class CogMeta(type):
|
|||||||
async def bar(self, ctx):
|
async def bar(self, ctx):
|
||||||
pass # hidden -> False
|
pass # hidden -> False
|
||||||
|
|
||||||
group_name: :class:`str`
|
group_name: Union[:class:`str`, :class:`~discord.app_commands.locale_str`]
|
||||||
The group name of a cog. This is only applicable for :class:`GroupCog` instances.
|
The group name of a cog. This is only applicable for :class:`GroupCog` instances.
|
||||||
By default, it's the same value as :attr:`name`.
|
By default, it's the same value as :attr:`name`.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
group_description: :class:`str`
|
group_description: Union[:class:`str`, :class:`~discord.app_commands.locale_str`]
|
||||||
The group description of a cog. This is only applicable for :class:`GroupCog` instances.
|
The group description of a cog. This is only applicable for :class:`GroupCog` instances.
|
||||||
By default, it's the same value as :attr:`description`.
|
By default, it's the same value as :attr:`description`.
|
||||||
|
|
||||||
@ -143,8 +143,8 @@ class CogMeta(type):
|
|||||||
|
|
||||||
__cog_name__: str
|
__cog_name__: str
|
||||||
__cog_description__: str
|
__cog_description__: str
|
||||||
__cog_group_name__: str
|
__cog_group_name__: Union[str, app_commands.locale_str]
|
||||||
__cog_group_description__: str
|
__cog_group_description__: Union[str, app_commands.locale_str]
|
||||||
__cog_group_nsfw__: bool
|
__cog_group_nsfw__: bool
|
||||||
__cog_settings__: Dict[str, Any]
|
__cog_settings__: Dict[str, Any]
|
||||||
__cog_commands__: List[Command[Any, ..., Any]]
|
__cog_commands__: List[Command[Any, ..., Any]]
|
||||||
@ -260,8 +260,8 @@ class Cog(metaclass=CogMeta):
|
|||||||
|
|
||||||
__cog_name__: str
|
__cog_name__: str
|
||||||
__cog_description__: str
|
__cog_description__: str
|
||||||
__cog_group_name__: str
|
__cog_group_name__: Union[str, app_commands.locale_str]
|
||||||
__cog_group_description__: str
|
__cog_group_description__: Union[str, app_commands.locale_str]
|
||||||
__cog_settings__: Dict[str, Any]
|
__cog_settings__: Dict[str, Any]
|
||||||
__cog_commands__: List[Command[Self, ..., Any]]
|
__cog_commands__: List[Command[Self, ..., Any]]
|
||||||
__cog_app_commands__: List[Union[app_commands.Group, app_commands.Command[Self, ..., Any]]]
|
__cog_app_commands__: List[Union[app_commands.Group, app_commands.Command[Self, ..., Any]]]
|
||||||
|
@ -297,22 +297,22 @@ def replace_parameters(
|
|||||||
|
|
||||||
|
|
||||||
class HybridAppCommand(discord.app_commands.Command[CogT, P, T]):
|
class HybridAppCommand(discord.app_commands.Command[CogT, P, T]):
|
||||||
def __init__(self, wrapped: Command[CogT, Any, T]) -> None:
|
def __init__(self, wrapped: Union[HybridCommand[CogT, Any, T], HybridGroup[CogT, Any, T]]) -> None:
|
||||||
signature = inspect.signature(wrapped.callback)
|
signature = inspect.signature(wrapped.callback)
|
||||||
params = replace_parameters(wrapped.params, wrapped.callback, signature)
|
params = replace_parameters(wrapped.params, wrapped.callback, signature)
|
||||||
wrapped.callback.__signature__ = signature.replace(parameters=params)
|
wrapped.callback.__signature__ = signature.replace(parameters=params)
|
||||||
nsfw = getattr(wrapped.callback, '__discord_app_commands_is_nsfw__', False)
|
nsfw = getattr(wrapped.callback, '__discord_app_commands_is_nsfw__', False)
|
||||||
try:
|
try:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=wrapped.name,
|
name=wrapped._locale_name or wrapped.name,
|
||||||
callback=wrapped.callback, # type: ignore # Signature doesn't match but we're overriding the invoke
|
callback=wrapped.callback, # type: ignore # Signature doesn't match but we're overriding the invoke
|
||||||
description=wrapped.description or wrapped.short_doc or '…',
|
description=wrapped._locale_description or wrapped.description or wrapped.short_doc or '…',
|
||||||
nsfw=nsfw,
|
nsfw=nsfw,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
del wrapped.callback.__signature__
|
del wrapped.callback.__signature__
|
||||||
|
|
||||||
self.wrapped: Command[CogT, Any, T] = wrapped
|
self.wrapped: Union[HybridCommand[CogT, Any, T], HybridGroup[CogT, Any, T]] = wrapped
|
||||||
self.binding: Optional[CogT] = wrapped.cog
|
self.binding: Optional[CogT] = wrapped.cog
|
||||||
# This technically means only one flag converter is supported
|
# This technically means only one flag converter is supported
|
||||||
self.flag_converter: Optional[Tuple[str, Type[FlagConverter]]] = getattr(
|
self.flag_converter: Optional[Tuple[str, Type[FlagConverter]]] = getattr(
|
||||||
@ -484,11 +484,25 @@ class HybridCommand(Command[CogT, P, T]):
|
|||||||
self,
|
self,
|
||||||
func: CommandCallback[CogT, Context[Any], P, T],
|
func: CommandCallback[CogT, Context[Any], P, T],
|
||||||
/,
|
/,
|
||||||
|
*,
|
||||||
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
|
description: Union[str, app_commands.locale_str] = MISSING,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
name, name_locale = (name.message, name) if isinstance(name, app_commands.locale_str) else (name, None)
|
||||||
|
if name is not MISSING:
|
||||||
|
kwargs['name'] = name
|
||||||
|
description, description_locale = (
|
||||||
|
(description.message, description) if isinstance(description, app_commands.locale_str) else (description, None)
|
||||||
|
)
|
||||||
|
if description is not MISSING:
|
||||||
|
kwargs['description'] = description
|
||||||
|
|
||||||
super().__init__(func, **kwargs)
|
super().__init__(func, **kwargs)
|
||||||
self.with_app_command: bool = kwargs.pop('with_app_command', True)
|
self.with_app_command: bool = kwargs.pop('with_app_command', True)
|
||||||
self.with_command: bool = kwargs.pop('with_command', True)
|
self.with_command: bool = kwargs.pop('with_command', True)
|
||||||
|
self._locale_name: Optional[app_commands.locale_str] = name_locale
|
||||||
|
self._locale_description: Optional[app_commands.locale_str] = description_locale
|
||||||
|
|
||||||
if not self.with_command and not self.with_app_command:
|
if not self.with_command and not self.with_app_command:
|
||||||
raise TypeError('cannot set both with_command and with_app_command to False')
|
raise TypeError('cannot set both with_command and with_app_command to False')
|
||||||
@ -586,10 +600,27 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
__commands_is_hybrid__: ClassVar[bool] = True
|
__commands_is_hybrid__: ClassVar[bool] = True
|
||||||
|
|
||||||
def __init__(self, *args: Any, fallback: Optional[str] = None, **attrs: Any) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
*args: Any,
|
||||||
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
|
description: Union[str, app_commands.locale_str] = MISSING,
|
||||||
|
fallback: Optional[str] = None,
|
||||||
|
**attrs: Any,
|
||||||
|
) -> None:
|
||||||
|
name, name_locale = (name.message, name) if isinstance(name, app_commands.locale_str) else (name, None)
|
||||||
|
if name is not MISSING:
|
||||||
|
attrs['name'] = name
|
||||||
|
description, description_locale = (
|
||||||
|
(description.message, description) if isinstance(description, app_commands.locale_str) else (description, None)
|
||||||
|
)
|
||||||
|
if description is not MISSING:
|
||||||
|
attrs['description'] = description
|
||||||
super().__init__(*args, **attrs)
|
super().__init__(*args, **attrs)
|
||||||
self.invoke_without_command = True
|
self.invoke_without_command = True
|
||||||
self.with_app_command: bool = attrs.pop('with_app_command', True)
|
self.with_app_command: bool = attrs.pop('with_app_command', True)
|
||||||
|
self._locale_name: Optional[app_commands.locale_str] = name_locale
|
||||||
|
self._locale_description: Optional[app_commands.locale_str] = description_locale
|
||||||
|
|
||||||
parent = None
|
parent = None
|
||||||
if self.parent is not None:
|
if self.parent is not None:
|
||||||
@ -612,8 +643,8 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
default_permissions = getattr(self.callback, '__discord_app_commands_default_permissions__', None)
|
default_permissions = getattr(self.callback, '__discord_app_commands_default_permissions__', None)
|
||||||
nsfw = getattr(self.callback, '__discord_app_commands_is_nsfw__', False)
|
nsfw = getattr(self.callback, '__discord_app_commands_is_nsfw__', False)
|
||||||
self.app_command = app_commands.Group(
|
self.app_command = app_commands.Group(
|
||||||
name=self.name,
|
name=self._locale_name or self.name,
|
||||||
description=self.description or self.short_doc or '…',
|
description=self._locale_description or self.description or self.short_doc or '…',
|
||||||
guild_ids=guild_ids,
|
guild_ids=guild_ids,
|
||||||
guild_only=guild_only,
|
guild_only=guild_only,
|
||||||
default_permissions=default_permissions,
|
default_permissions=default_permissions,
|
||||||
@ -762,7 +793,7 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
def command(
|
def command(
|
||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
with_app_command: bool = True,
|
with_app_command: bool = True,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
@ -786,7 +817,7 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
def group(
|
def group(
|
||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
with_app_command: bool = True,
|
with_app_command: bool = True,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
@ -810,7 +841,7 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
|
|
||||||
def hybrid_command(
|
def hybrid_command(
|
||||||
name: str = MISSING,
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
*,
|
*,
|
||||||
with_app_command: bool = True,
|
with_app_command: bool = True,
|
||||||
**attrs: Any,
|
**attrs: Any,
|
||||||
@ -837,7 +868,7 @@ def hybrid_command(
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: :class:`str`
|
name: Union[:class:`str`, :class:`~discord.app_commands.locale_str`]
|
||||||
The name to create the command with. By default this uses the
|
The name to create the command with. By default this uses the
|
||||||
function name unchanged.
|
function name unchanged.
|
||||||
with_app_command: :class:`bool`
|
with_app_command: :class:`bool`
|
||||||
@ -861,7 +892,7 @@ def hybrid_command(
|
|||||||
|
|
||||||
|
|
||||||
def hybrid_group(
|
def hybrid_group(
|
||||||
name: str = MISSING,
|
name: Union[str, app_commands.locale_str] = MISSING,
|
||||||
*,
|
*,
|
||||||
with_app_command: bool = True,
|
with_app_command: bool = True,
|
||||||
**attrs: Any,
|
**attrs: Any,
|
||||||
|
@ -878,9 +878,15 @@ class InteractionResponse:
|
|||||||
if self._response_type:
|
if self._response_type:
|
||||||
raise InteractionResponded(self._parent)
|
raise InteractionResponded(self._parent)
|
||||||
|
|
||||||
payload: Dict[str, Any] = {
|
translator = self._parent._state._translator
|
||||||
'choices': [option.to_dict() for option in choices],
|
if translator is not None:
|
||||||
}
|
payload: Dict[str, Any] = {
|
||||||
|
'choices': [await option.get_translated_payload(translator) for option in choices],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
payload: Dict[str, Any] = {
|
||||||
|
'choices': [option.to_dict() for option in choices],
|
||||||
|
}
|
||||||
|
|
||||||
parent = self._parent
|
parent = self._parent
|
||||||
if parent.type is not InteractionType.autocomplete:
|
if parent.type is not InteractionType.autocomplete:
|
||||||
|
@ -471,12 +471,7 @@ class AutoShardedClient(Client):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._closed = True
|
self._closed = True
|
||||||
|
await self._connection.close()
|
||||||
for vc in self.voice_clients:
|
|
||||||
try:
|
|
||||||
await vc.disconnect(force=True)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
to_close = [asyncio.ensure_future(shard.close(), loop=self.loop) for shard in self.__shards.values()]
|
to_close = [asyncio.ensure_future(shard.close(), loop=self.loop) for shard in self.__shards.values()]
|
||||||
if to_close:
|
if to_close:
|
||||||
|
@ -83,7 +83,7 @@ if TYPE_CHECKING:
|
|||||||
from .voice_client import VoiceProtocol
|
from .voice_client import VoiceProtocol
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .gateway import DiscordWebSocket
|
from .gateway import DiscordWebSocket
|
||||||
from .app_commands import CommandTree
|
from .app_commands import CommandTree, Translator
|
||||||
|
|
||||||
from .types.automod import AutoModerationRule, AutoModerationActionExecution
|
from .types.automod import AutoModerationRule, AutoModerationActionExecution
|
||||||
from .types.snowflake import Snowflake
|
from .types.snowflake import Snowflake
|
||||||
@ -245,6 +245,7 @@ class ConnectionState:
|
|||||||
self._status: Optional[str] = status
|
self._status: Optional[str] = status
|
||||||
self._intents: Intents = intents
|
self._intents: Intents = intents
|
||||||
self._command_tree: Optional[CommandTree] = None
|
self._command_tree: Optional[CommandTree] = None
|
||||||
|
self._translator: Optional[Translator] = None
|
||||||
|
|
||||||
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
|
||||||
@ -257,6 +258,19 @@ class ConnectionState:
|
|||||||
|
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
|
async def close(self) -> None:
|
||||||
|
for voice in self.voice_clients:
|
||||||
|
try:
|
||||||
|
await voice.disconnect(force=True)
|
||||||
|
except Exception:
|
||||||
|
# if an error happens during disconnects, disregard it.
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self._translator:
|
||||||
|
await self._translator.unload()
|
||||||
|
|
||||||
|
# Purposefully don't call `clear` because users rely on cache being available post-close
|
||||||
|
|
||||||
def clear(self, *, views: bool = True) -> None:
|
def clear(self, *, views: bool = True) -> None:
|
||||||
self.user: Optional[ClientUser] = None
|
self.user: Optional[ClientUser] = None
|
||||||
self._users: weakref.WeakValueDictionary[int, User] = weakref.WeakValueDictionary()
|
self._users: weakref.WeakValueDictionary[int, User] = weakref.WeakValueDictionary()
|
||||||
|
@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List, Literal, Optional, TypedDict, Union
|
from typing import Dict, List, Literal, Optional, TypedDict, Union
|
||||||
from typing_extensions import NotRequired, Required
|
from typing_extensions import NotRequired, Required
|
||||||
|
|
||||||
from .channel import ChannelType
|
from .channel import ChannelType
|
||||||
@ -37,6 +37,8 @@ ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
|||||||
class _BaseApplicationCommandOption(TypedDict):
|
class _BaseApplicationCommandOption(TypedDict):
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
|
name_localizations: NotRequired[Optional[Dict[str, str]]]
|
||||||
|
description_localizations: NotRequired[Optional[Dict[str, str]]]
|
||||||
|
|
||||||
|
|
||||||
class _SubCommandCommandOption(_BaseApplicationCommandOption):
|
class _SubCommandCommandOption(_BaseApplicationCommandOption):
|
||||||
@ -55,6 +57,7 @@ class _BaseValueApplicationCommandOption(_BaseApplicationCommandOption, total=Fa
|
|||||||
|
|
||||||
class _StringApplicationCommandOptionChoice(TypedDict):
|
class _StringApplicationCommandOptionChoice(TypedDict):
|
||||||
name: str
|
name: str
|
||||||
|
name_localizations: NotRequired[Optional[Dict[str, str]]]
|
||||||
value: str
|
value: str
|
||||||
|
|
||||||
|
|
||||||
@ -68,6 +71,7 @@ class _StringApplicationCommandOption(_BaseApplicationCommandOption):
|
|||||||
|
|
||||||
class _IntegerApplicationCommandOptionChoice(TypedDict):
|
class _IntegerApplicationCommandOptionChoice(TypedDict):
|
||||||
name: str
|
name: str
|
||||||
|
name_localizations: NotRequired[Optional[Dict[str, str]]]
|
||||||
value: int
|
value: int
|
||||||
|
|
||||||
|
|
||||||
@ -100,6 +104,7 @@ _SnowflakeApplicationCommandOptionChoice = Union[
|
|||||||
|
|
||||||
class _NumberApplicationCommandOptionChoice(TypedDict):
|
class _NumberApplicationCommandOptionChoice(TypedDict):
|
||||||
name: str
|
name: str
|
||||||
|
name_localizations: NotRequired[Optional[Dict[str, str]]]
|
||||||
value: float
|
value: float
|
||||||
|
|
||||||
|
|
||||||
@ -140,6 +145,8 @@ class _BaseApplicationCommand(TypedDict):
|
|||||||
default_member_permissions: NotRequired[Optional[str]]
|
default_member_permissions: NotRequired[Optional[str]]
|
||||||
nsfw: NotRequired[bool]
|
nsfw: NotRequired[bool]
|
||||||
version: Snowflake
|
version: Snowflake
|
||||||
|
name_localizations: NotRequired[Optional[Dict[str, str]]]
|
||||||
|
description_localizations: NotRequired[Optional[Dict[str, str]]]
|
||||||
|
|
||||||
|
|
||||||
class _ChatInputApplicationCommand(_BaseApplicationCommand, total=False):
|
class _ChatInputApplicationCommand(_BaseApplicationCommand, total=False):
|
||||||
|
@ -377,7 +377,7 @@ Enumerations
|
|||||||
A message context menu command.
|
A message context menu command.
|
||||||
|
|
||||||
.. class:: AppCommandPermissionType
|
.. class:: AppCommandPermissionType
|
||||||
|
|
||||||
The application command's permission type.
|
The application command's permission type.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
@ -596,6 +596,52 @@ Range
|
|||||||
.. autoclass:: discord.app_commands.Range
|
.. autoclass:: discord.app_commands.Range
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Translations
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Translator
|
||||||
|
+++++++++++
|
||||||
|
|
||||||
|
.. attributetable:: discord.app_commands.Translator
|
||||||
|
|
||||||
|
.. autoclass:: discord.app_commands.Translator
|
||||||
|
:members:
|
||||||
|
|
||||||
|
locale_str
|
||||||
|
+++++++++++
|
||||||
|
|
||||||
|
.. attributetable:: discord.app_commands.locale_str
|
||||||
|
|
||||||
|
.. autoclass:: discord.app_commands.locale_str
|
||||||
|
:members:
|
||||||
|
|
||||||
|
TranslationContext
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
.. class:: TranslationContext
|
||||||
|
:module: discord.app_commands
|
||||||
|
|
||||||
|
An enum representing the context that the translation occurs in when requested for translation.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. attribute:: command_name
|
||||||
|
|
||||||
|
The translation involved a command name.
|
||||||
|
.. attribute:: command_description
|
||||||
|
|
||||||
|
The translation involved a command description.
|
||||||
|
.. attribute:: parameter_name
|
||||||
|
|
||||||
|
The translation involved a parameter name.
|
||||||
|
.. attribute:: parameter_description
|
||||||
|
|
||||||
|
The translation involved a parameter description.
|
||||||
|
.. attribute:: choice_name
|
||||||
|
|
||||||
|
The translation involved a choice name.
|
||||||
|
|
||||||
|
|
||||||
Exceptions
|
Exceptions
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
@ -608,6 +654,9 @@ Exceptions
|
|||||||
.. autoexception:: discord.app_commands.TransformerError
|
.. autoexception:: discord.app_commands.TransformerError
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoexception:: discord.app_commands.TranslationError
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoexception:: discord.app_commands.CheckFailure
|
.. autoexception:: discord.app_commands.CheckFailure
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -653,6 +702,7 @@ Exception Hierarchy
|
|||||||
- :exc:`~discord.app_commands.AppCommandError`
|
- :exc:`~discord.app_commands.AppCommandError`
|
||||||
- :exc:`~discord.app_commands.CommandInvokeError`
|
- :exc:`~discord.app_commands.CommandInvokeError`
|
||||||
- :exc:`~discord.app_commands.TransformerError`
|
- :exc:`~discord.app_commands.TransformerError`
|
||||||
|
- :exc:`~discord.app_commands.TranslationError`
|
||||||
- :exc:`~discord.app_commands.CheckFailure`
|
- :exc:`~discord.app_commands.CheckFailure`
|
||||||
- :exc:`~discord.app_commands.NoPrivateMessage`
|
- :exc:`~discord.app_commands.NoPrivateMessage`
|
||||||
- :exc:`~discord.app_commands.MissingRole`
|
- :exc:`~discord.app_commands.MissingRole`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user