You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
968 lines
31 KiB
968 lines
31 KiB
"""
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2021-present Dolfies
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
DEALINGS IN THE SOFTWARE.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Protocol, Tuple, Type, Union, runtime_checkable
|
|
|
|
from .enums import AppCommandOptionType, AppCommandType, ChannelType, InteractionType, try_enum
|
|
from .interactions import _wrapped_interaction
|
|
from .mixins import Hashable
|
|
from .permissions import Permissions
|
|
from .utils import _generate_nonce, _get_as_snowflake
|
|
|
|
if TYPE_CHECKING:
|
|
from .abc import Messageable, Snowflake
|
|
from .appinfo import IntegrationApplication
|
|
from .file import File
|
|
from .guild import Guild
|
|
from .interactions import Interaction
|
|
from .message import Attachment, Message
|
|
from .state import ConnectionState
|
|
|
|
|
|
__all__ = (
|
|
'BaseCommand',
|
|
'UserCommand',
|
|
'MessageCommand',
|
|
'SlashCommand',
|
|
'SubCommand',
|
|
'Option',
|
|
'OptionChoice',
|
|
)
|
|
|
|
|
|
@runtime_checkable
|
|
class ApplicationCommand(Protocol):
|
|
"""An ABC that represents a usable application command.
|
|
|
|
The following implement this ABC:
|
|
|
|
- :class:`~discord.UserCommand`
|
|
- :class:`~discord.MessageCommand`
|
|
- :class:`~discord.SlashCommand`
|
|
- :class:`~discord.SubCommand`
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Attributes
|
|
-----------
|
|
name: :class:`str`
|
|
The command's name.
|
|
description: :class:`str`
|
|
The command's description, if any.
|
|
type: :class:`~discord.AppCommandType`
|
|
The type of application command.
|
|
default_permission: :class:`bool`
|
|
Whether the command is enabled in guilds by default.
|
|
dm_permission: :class:`bool`
|
|
Whether the command is enabled in DMs.
|
|
nsfw: :class:`bool`
|
|
Whether the command is marked NSFW and only available in NSFW channels.
|
|
application: Optional[:class:`~discord.IntegrationApplication`]
|
|
The application this command belongs to.
|
|
Only available if requested.
|
|
application_id: :class:`int`
|
|
The ID of the application this command belongs to.
|
|
guild_id: Optional[:class:`int`]
|
|
The ID of the guild this command is registered in. A value of ``None``
|
|
denotes that it is a global command.
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
if TYPE_CHECKING:
|
|
_state: ConnectionState
|
|
_channel: Optional[Messageable]
|
|
_default_member_permissions: Optional[int]
|
|
name: str
|
|
description: str
|
|
version: int
|
|
type: AppCommandType
|
|
default_permission: bool
|
|
dm_permission: bool
|
|
nsfw: bool
|
|
application_id: int
|
|
application: Optional[IntegrationApplication]
|
|
mention: str
|
|
guild_id: Optional[int]
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
async def __call__(
|
|
self, data: dict, files: Optional[List[File]] = None, channel: Optional[Messageable] = None
|
|
) -> Interaction:
|
|
channel = channel or self.target_channel
|
|
if channel is None:
|
|
raise TypeError('__call__() missing 1 required argument: \'channel\'')
|
|
|
|
return await _wrapped_interaction(
|
|
self._state,
|
|
_generate_nonce(),
|
|
InteractionType.application_command,
|
|
data['name'],
|
|
await channel._get_channel(), # type: ignore # acc_channel is always correct here
|
|
data,
|
|
files=files,
|
|
application_id=self.application_id,
|
|
)
|
|
|
|
@property
|
|
def guild(self) -> Optional[Guild]:
|
|
"""Optional[:class:`~discord.Guild`]: Returns the guild this command is registered to
|
|
if it exists.
|
|
"""
|
|
return self._state._get_guild(self.guild_id)
|
|
|
|
def is_group(self) -> bool:
|
|
"""Query whether this command is a group.
|
|
|
|
Returns
|
|
-------
|
|
:class:`bool`
|
|
Whether this command is a group.
|
|
"""
|
|
return False
|
|
|
|
@property
|
|
def target_channel(self) -> Optional[Messageable]:
|
|
"""Optional[:class:`.abc.Messageable`]: The channel this application command will be used on.
|
|
|
|
You can set this in order to use this command in a different channel without re-fetching it.
|
|
"""
|
|
return self._channel
|
|
|
|
@target_channel.setter
|
|
def target_channel(self, value: Optional[Messageable]) -> None:
|
|
from .abc import Messageable
|
|
|
|
if not isinstance(value, Messageable) and value is not None:
|
|
raise TypeError('channel must derive from Messageable')
|
|
self._channel = value
|
|
|
|
@property
|
|
def default_member_permissions(self) -> Optional[Permissions]:
|
|
"""Optional[:class:`~discord.Permissions`]: The default permissions required to use this command.
|
|
|
|
.. note::
|
|
This may be overrided on a guild-by-guild basis.
|
|
"""
|
|
perms = self._default_member_permissions
|
|
return Permissions(perms) if perms is not None else None
|
|
|
|
|
|
class BaseCommand(ApplicationCommand, Hashable):
|
|
__slots__ = (
|
|
'name',
|
|
'description',
|
|
'id',
|
|
'version',
|
|
'type',
|
|
'default_permission',
|
|
'application',
|
|
'application_id',
|
|
'dm_permission',
|
|
'nsfw',
|
|
'guild_id',
|
|
'_data',
|
|
'_state',
|
|
'_channel',
|
|
'_default_member_permissions',
|
|
)
|
|
|
|
def __init__(
|
|
self, *, state: ConnectionState, data: Dict[str, Any], channel: Optional[Messageable] = None, **kwargs
|
|
) -> None:
|
|
self._state = state
|
|
self._data = data
|
|
self.name = data['name']
|
|
self.description = data['description']
|
|
self._channel = channel
|
|
self.application_id: int = int(data['application_id'])
|
|
self.id: int = int(data['id'])
|
|
self.version = int(data['version'])
|
|
self.type = try_enum(AppCommandType, data['type'])
|
|
|
|
application = data.get('application')
|
|
self.application = state.create_integration_application(application) if application else None
|
|
|
|
self._default_member_permissions = _get_as_snowflake(data, 'default_member_permissions')
|
|
self.default_permission: bool = data.get('default_permission', True)
|
|
dm_permission = data.get('dm_permission') # Null means true?
|
|
self.dm_permission = dm_permission if dm_permission is not None else True
|
|
self.nsfw: bool = data.get('nsfw', False)
|
|
self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id')
|
|
|
|
def __repr__(self) -> str:
|
|
return f'<{self.__class__.__name__} id={self.id} name={self.name!r}>'
|
|
|
|
@property
|
|
def mention(self) -> str:
|
|
""":class:`str`: Returns a string that allows you to mention the command."""
|
|
return f'</{self.name}:{self.id}>'
|
|
|
|
|
|
class SlashMixin(ApplicationCommand, Protocol):
|
|
if TYPE_CHECKING:
|
|
_parent: SlashCommand
|
|
options: List[Option]
|
|
children: List[SubCommand]
|
|
|
|
async def __call__(
|
|
self,
|
|
options: List[dict],
|
|
files: Optional[List[File]],
|
|
attachments: List[Attachment],
|
|
channel: Optional[Messageable] = None,
|
|
) -> Interaction:
|
|
obj = self._parent
|
|
command = obj._data
|
|
command['name_localized'] = command['name']
|
|
data = {
|
|
'application_command': command,
|
|
'attachments': attachments,
|
|
'id': str(obj.id),
|
|
'name': obj.name,
|
|
'options': options,
|
|
'type': obj.type.value,
|
|
'version': str(obj.version),
|
|
}
|
|
if self.guild_id:
|
|
data['guild_id'] = str(self.guild_id)
|
|
return await super().__call__(data, files, channel)
|
|
|
|
def _parse_kwargs(self, kwargs: Dict[str, Any]) -> Tuple[List[Dict[str, Any]], List[File], List[Attachment]]:
|
|
possible_options = {o.name: o for o in self.options}
|
|
kwargs = {k: v for k, v in kwargs.items() if k in possible_options}
|
|
options = []
|
|
files = []
|
|
|
|
for k, v in kwargs.items():
|
|
option = possible_options[k]
|
|
type = option.type
|
|
|
|
if type in {
|
|
AppCommandOptionType.user,
|
|
AppCommandOptionType.channel,
|
|
AppCommandOptionType.role,
|
|
AppCommandOptionType.mentionable,
|
|
}:
|
|
v = str(v.id)
|
|
elif type is AppCommandOptionType.boolean:
|
|
v = bool(v)
|
|
elif type is AppCommandOptionType.attachment:
|
|
files.append(v)
|
|
v = len(files) - 1
|
|
else:
|
|
v = option._convert(v)
|
|
|
|
if type is AppCommandOptionType.string:
|
|
v = str(v)
|
|
elif type is AppCommandOptionType.integer:
|
|
v = int(v)
|
|
elif type is AppCommandOptionType.number:
|
|
v = float(v)
|
|
|
|
options.append({'name': k, 'value': v, 'type': type.value})
|
|
|
|
attachments = []
|
|
for index, file in enumerate(files):
|
|
attachments.append(file.to_dict(index))
|
|
|
|
return options, files, attachments
|
|
|
|
def _unwrap_options(self, data: List[Dict[str, Any]]) -> None:
|
|
options = []
|
|
children = []
|
|
for option in data:
|
|
type = try_enum(AppCommandOptionType, option['type'])
|
|
if type in {
|
|
AppCommandOptionType.sub_command,
|
|
AppCommandOptionType.sub_command_group,
|
|
}:
|
|
children.append(SubCommand(parent=self, data=option))
|
|
else:
|
|
options.append(Option(option))
|
|
|
|
self.options = options
|
|
self.children = children
|
|
|
|
|
|
class UserCommand(BaseCommand):
|
|
"""Represents a user command.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two commands are equal.
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two commands are not equal.
|
|
|
|
.. describe:: hash(x)
|
|
|
|
Return the command's hash.
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the command's name.
|
|
|
|
Attributes
|
|
----------
|
|
id: :class:`int`
|
|
The command's ID.
|
|
version: :class:`int`
|
|
The command's version.
|
|
name: :class:`str`
|
|
The command's name.
|
|
description: :class:`str`
|
|
The command's description, if any.
|
|
type: :class:`AppCommandType`
|
|
The type of application command. This will always be :attr:`AppCommandType.user`.
|
|
default_permission: :class:`bool`
|
|
Whether the command is enabled in guilds by default.
|
|
dm_permission: :class:`bool`
|
|
Whether the command is enabled in DMs.
|
|
nsfw: :class:`bool`
|
|
Whether the command is marked NSFW and only available in NSFW channels.
|
|
application: Optional[:class:`IntegrationApplication`]
|
|
The application this command belongs to.
|
|
Only available if requested.
|
|
application_id: :class:`int`
|
|
The ID of the application this command belongs to.
|
|
guild_id: Optional[:class:`int`]
|
|
The ID of the guild this command is registered in. A value of ``None``
|
|
denotes that it is a global command.
|
|
"""
|
|
|
|
__slots__ = ('_user',)
|
|
|
|
def __init__(self, *, target: Optional[Snowflake] = None, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self._user = target
|
|
|
|
async def __call__(self, user: Optional[Snowflake] = None, *, channel: Optional[Messageable] = None):
|
|
"""Use the user command.
|
|
|
|
Parameters
|
|
----------
|
|
user: Optional[:class:`User`]
|
|
The user to use the command on. Overrides :attr:`target_user`.
|
|
Required if :attr:`target_user` is not set.
|
|
channel: Optional[:class:`abc.Messageable`]
|
|
The channel to use the command on. Overrides :attr:`target_channel`.
|
|
Required if :attr:`target_channel` is not set.
|
|
|
|
Returns
|
|
-------
|
|
:class:`Interaction`
|
|
The interaction that was created.
|
|
"""
|
|
user = user or self._user
|
|
if user is None:
|
|
raise TypeError('__call__() missing 1 required positional argument: \'user\'')
|
|
|
|
command = self._data
|
|
data = {
|
|
'application_command': command,
|
|
'attachments': [],
|
|
'id': str(self.id),
|
|
'name': self.name,
|
|
'options': [],
|
|
'target_id': str(user.id),
|
|
'type': self.type.value,
|
|
'version': str(self.version),
|
|
}
|
|
return await super().__call__(data, None, channel)
|
|
|
|
@property
|
|
def target_user(self) -> Optional[Snowflake]:
|
|
"""Optional[:class:`~abc.Snowflake`]: The user this application command will be used on.
|
|
|
|
You can set this in order to use this command on a different user without re-fetching it.
|
|
"""
|
|
return self._user
|
|
|
|
@target_user.setter
|
|
def target_user(self, value: Optional[Snowflake]) -> None:
|
|
from .abc import Snowflake
|
|
|
|
if not isinstance(value, Snowflake) and value is not None:
|
|
raise TypeError('user must be Snowflake')
|
|
self._user = value
|
|
|
|
|
|
class MessageCommand(BaseCommand):
|
|
"""Represents a message command.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two commands are equal.
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two commands are not equal.
|
|
|
|
.. describe:: hash(x)
|
|
|
|
Return the command's hash.
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the command's name.
|
|
|
|
Attributes
|
|
----------
|
|
id: :class:`int`
|
|
The command's ID.
|
|
version: :class:`int`
|
|
The command's version.
|
|
name: :class:`str`
|
|
The command's name.
|
|
description: :class:`str`
|
|
The command's description, if any.
|
|
type: :class:`AppCommandType`
|
|
The type of application command. This will always be :attr:`AppCommandType.message`.
|
|
default_permission: :class:`bool`
|
|
Whether the command is enabled in guilds by default.
|
|
dm_permission: :class:`bool`
|
|
Whether the command is enabled in DMs.
|
|
nsfw: :class:`bool`
|
|
Whether the command is marked NSFW and only available in NSFW channels.
|
|
application: Optional[:class:`IntegrationApplication`]
|
|
The application this command belongs to.
|
|
Only available if requested.
|
|
application_id: :class:`int`
|
|
The ID of the application this command belongs to.
|
|
guild_id: Optional[:class:`int`]
|
|
The ID of the guild this command is registered in. A value of ``None``
|
|
denotes that it is a global command.
|
|
"""
|
|
|
|
__slots__ = ('_message',)
|
|
|
|
def __init__(self, *, target: Optional[Message] = None, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self._message = target
|
|
|
|
async def __call__(self, message: Optional[Message] = None, *, channel: Optional[Messageable] = None):
|
|
"""Use the message command.
|
|
|
|
Parameters
|
|
----------
|
|
message: Optional[:class:`Message`]
|
|
The message to use the command on. Overrides :attr:`target_message`.
|
|
Required if :attr:`target_message` is not set.
|
|
channel: Optional[:class:`abc.Messageable`]
|
|
The channel to use the command on. Overrides :attr:`target_channel`.
|
|
Required if :attr:`target_channel` is not set.
|
|
|
|
Returns
|
|
-------
|
|
:class:`Interaction`
|
|
The interaction that was created.
|
|
"""
|
|
message = message or self._message
|
|
if message is None:
|
|
raise TypeError('__call__() missing 1 required positional argument: \'message\'')
|
|
|
|
command = self._data
|
|
command['name_localized'] = command['name']
|
|
data = {
|
|
'application_command': command,
|
|
'attachments': [],
|
|
'id': str(self.id),
|
|
'name': self.name,
|
|
'options': [],
|
|
'target_id': str(message.id),
|
|
'type': self.type.value,
|
|
'version': str(self.version),
|
|
}
|
|
return await super().__call__(data, None, channel)
|
|
|
|
@property
|
|
def target_message(self) -> Optional[Message]:
|
|
"""Optional[:class:`Message`]: The message this application command will be used on.
|
|
|
|
You can set this in order to use this command on a different message without re-fetching it.
|
|
"""
|
|
return self._message
|
|
|
|
@target_message.setter
|
|
def target_message(self, value: Optional[Message]) -> None:
|
|
from .message import Message
|
|
|
|
if not isinstance(value, Message) and value is not None:
|
|
raise TypeError('message must be Message')
|
|
self._message = value
|
|
|
|
|
|
class SlashCommand(BaseCommand, SlashMixin):
|
|
"""Represents a slash command.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two commands are equal.
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two commands are not equal.
|
|
|
|
.. describe:: hash(x)
|
|
|
|
Return the command's hash.
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the command's name.
|
|
|
|
Attributes
|
|
----------
|
|
id: :class:`int`
|
|
The command's ID.
|
|
version: :class:`int`
|
|
The command's version.
|
|
name: :class:`str`
|
|
The command's name.
|
|
description: :class:`str`
|
|
The command's description, if any.
|
|
type: :class:`AppCommandType`
|
|
The type of application command.
|
|
default_permission: :class:`bool`
|
|
Whether the command is enabled in guilds by default.
|
|
dm_permission: :class:`bool`
|
|
Whether the command is enabled in DMs.
|
|
nsfw: :class:`bool`
|
|
Whether the command is marked NSFW and only available in NSFW channels.
|
|
application: Optional[:class:`IntegrationApplication`]
|
|
The application this command belongs to.
|
|
Only available if requested.
|
|
application_id: :class:`int`
|
|
The ID of the application this command belongs to.
|
|
guild_id: Optional[:class:`int`]
|
|
The ID of the guild this command is registered in. A value of ``None``
|
|
denotes that it is a global command.
|
|
options: List[:class:`Option`]
|
|
The command's options.
|
|
children: List[:class:`SubCommand`]
|
|
The command's subcommands. If a command has subcommands, it is a group and cannot be used.
|
|
"""
|
|
|
|
__slots__ = ('_parent', 'options', 'children')
|
|
|
|
def __init__(self, *, data: Dict[str, Any], **kwargs) -> None:
|
|
super().__init__(data=data, **kwargs)
|
|
self._parent = self
|
|
self._unwrap_options(data.get('options', []))
|
|
|
|
async def __call__(self, channel: Optional[Messageable] = None, /, **kwargs):
|
|
r"""Use the slash command.
|
|
|
|
Parameters
|
|
----------
|
|
channel: Optional[:class:`abc.Messageable`]
|
|
The channel to use the command on. Overrides :attr:`target_channel`.
|
|
Required if :attr:`target_message` is not set.
|
|
\*\*kwargs: Any
|
|
The options to use. These will be casted to the correct type.
|
|
If an option has choices, they are automatically converted from name to value for you.
|
|
|
|
Raises
|
|
------
|
|
TypeError
|
|
Attempted to use a group.
|
|
|
|
Returns
|
|
-------
|
|
:class:`Interaction`
|
|
The interaction that was created.
|
|
"""
|
|
if self.is_group():
|
|
raise TypeError('Cannot use a group')
|
|
|
|
return await super().__call__(*self._parse_kwargs(kwargs), channel)
|
|
|
|
def __repr__(self) -> str:
|
|
BASE = f'<SlashCommand id={self.id} name={self.name!r}'
|
|
if self.options:
|
|
BASE += f' options={len(self.options)}'
|
|
if self.children:
|
|
BASE += f' children={len(self.children)}'
|
|
return BASE + '>'
|
|
|
|
def is_group(self) -> bool:
|
|
"""Query whether this command is a group.
|
|
|
|
Returns
|
|
-------
|
|
:class:`bool`
|
|
Whether this command is a group.
|
|
"""
|
|
return bool(self.children)
|
|
|
|
|
|
class SubCommand(SlashMixin):
|
|
"""Represents a slash command child.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
This could be a subcommand, or a subgroup.
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the command's name.
|
|
|
|
Attributes
|
|
----------
|
|
name: :class:`str`
|
|
The subcommand's name.
|
|
description: :class:`str`
|
|
The subcommand's description, if any.
|
|
type: :class:`AppCommandType`
|
|
The type of application command. Always :attr:`AppCommandType.chat_input`.
|
|
parent: Union[:class:`SlashCommand`, :class:`SubCommand`]
|
|
The parent command.
|
|
options: List[:class:`Option`]
|
|
The subcommand's options.
|
|
children: List[:class:`SubCommand`]
|
|
The subcommand's subcommands. If a subcommand has subcommands, it is a group and cannot be used.
|
|
"""
|
|
|
|
__slots__ = (
|
|
'_parent',
|
|
'_state',
|
|
'_type',
|
|
'parent',
|
|
'options',
|
|
'children',
|
|
'type',
|
|
)
|
|
|
|
def __init__(self, *, parent, data):
|
|
self.name = data['name']
|
|
self.description = data.get('description')
|
|
self._state = parent._state
|
|
self.parent: Union[SlashCommand, SubCommand] = parent
|
|
self._parent: SlashCommand = getattr(parent, 'parent', parent) # type: ignore
|
|
self.type = AppCommandType.chat_input # Avoid confusion I guess
|
|
self._type: AppCommandOptionType = try_enum(AppCommandOptionType, data['type'])
|
|
self._unwrap_options(data.get('options', []))
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def _walk_parents(self):
|
|
parent = self.parent
|
|
while True:
|
|
if isinstance(parent, SlashCommand):
|
|
break
|
|
else:
|
|
yield parent
|
|
parent = parent.parent
|
|
|
|
async def __call__(self, channel: Optional[Messageable] = None, /, **kwargs):
|
|
r"""Use the sub command.
|
|
|
|
Parameters
|
|
----------
|
|
channel: Optional[:class:`abc.Messageable`]
|
|
The channel to use the command on. Overrides :attr:`target_channel`.
|
|
Required if :attr:`target_message` is not set.
|
|
\*\*kwargs: Any
|
|
The options to use. These will be casted to the correct type.
|
|
If an option has choices, they are automatically converted from name to value for you.
|
|
|
|
Raises
|
|
------
|
|
TypeError
|
|
Attempted to use a group.
|
|
|
|
Returns
|
|
-------
|
|
:class:`Interaction`
|
|
The interaction that was created.
|
|
"""
|
|
if self.is_group():
|
|
raise TypeError('Cannot use a group')
|
|
|
|
options, files, attachments = self._parse_kwargs(kwargs)
|
|
|
|
options = [
|
|
{
|
|
'type': self._type.value,
|
|
'name': self.name,
|
|
'options': options,
|
|
}
|
|
]
|
|
for parent in self._walk_parents():
|
|
options = [
|
|
{
|
|
'type': parent._type.value,
|
|
'name': parent.name,
|
|
'options': options,
|
|
}
|
|
]
|
|
|
|
return await super().__call__(options, files, attachments, channel)
|
|
|
|
def __repr__(self) -> str:
|
|
BASE = f'<SubCommand name={self.name!r}'
|
|
if self.options:
|
|
BASE += f' options={len(self.options)}'
|
|
if self.children:
|
|
BASE += f' children={len(self.children)}'
|
|
return BASE + '>'
|
|
|
|
@property
|
|
def qualified_name(self) -> str:
|
|
""":class:`str`: Returns the fully qualified command name.
|
|
The qualified name includes the parent name as well. For example,
|
|
in a command like ``/foo bar`` the qualified name is ``foo bar``.
|
|
"""
|
|
names = [self.name, self.parent.name]
|
|
if isinstance(self.parent, SubCommand):
|
|
names.append(self._parent.name)
|
|
return ' '.join(reversed(names))
|
|
|
|
@property
|
|
def mention(self) -> str:
|
|
""":class:`str`: Returns a string that allows you to mention the subcommand."""
|
|
return f'</{self.qualified_name}:{self._parent.id}>'
|
|
|
|
@property
|
|
def _default_member_permissions(self) -> Optional[int]:
|
|
return self._parent._default_member_permissions
|
|
|
|
@property
|
|
def application_id(self) -> int:
|
|
""":class:`int`: The ID of the application this command belongs to."""
|
|
return self._parent.application_id
|
|
|
|
@property
|
|
def version(self) -> int:
|
|
""":class:`int`: The version of the command."""
|
|
return self._parent.version
|
|
|
|
@property
|
|
def default_permission(self) -> bool:
|
|
""":class:`bool`: Whether the command is enabled in guilds by default."""
|
|
return self._parent.default_permission
|
|
|
|
@property
|
|
def dm_permission(self) -> bool:
|
|
""":class:`bool`: Whether the command is enabled in DMs."""
|
|
return self._parent.dm_permission
|
|
|
|
@property
|
|
def nsfw(self) -> bool:
|
|
""":class:`bool`: Whether the command is marked NSFW and only available in NSFW channels."""
|
|
return self._parent.nsfw
|
|
|
|
@property
|
|
def guild_id(self) -> Optional[int]:
|
|
"""Optional[:class:`int`]: The ID of the guild this command is registered in. A value of ``None``
|
|
denotes that it is a global command."""
|
|
return self._parent.guild_id
|
|
|
|
@property
|
|
def guild(self) -> Optional[Guild]:
|
|
"""Optional[:class:`~discord.Guild`]: Returns the guild this command is registered to
|
|
if it exists.
|
|
"""
|
|
return self._parent.guild
|
|
|
|
def is_group(self) -> bool:
|
|
"""Query whether this command is a group.
|
|
|
|
Returns
|
|
-------
|
|
:class:`bool`
|
|
Whether this command is a group.
|
|
"""
|
|
return self._type is AppCommandOptionType.sub_command_group
|
|
|
|
@property
|
|
def application(self):
|
|
"""Optional[:class:`IntegrationApplication`]: The application this command belongs to.
|
|
Only available if requested.
|
|
"""
|
|
return self._parent.application
|
|
|
|
@property
|
|
def target_channel(self) -> Optional[Messageable]:
|
|
"""Optional[:class:`.abc.Messageable`]: The channel this command will be used on.
|
|
|
|
You can set this in order to use this command on a different channel without re-fetching it.
|
|
"""
|
|
return self._parent.target_channel
|
|
|
|
@target_channel.setter
|
|
def target_channel(self, value: Optional[Messageable]) -> None:
|
|
self._parent.target_channel = value
|
|
|
|
|
|
class Option:
|
|
"""Represents a command option.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the option's name.
|
|
|
|
Attributes
|
|
----------
|
|
name: :class:`str`
|
|
The option's name.
|
|
description: :class:`str`
|
|
The option's description, if any.
|
|
type: :class:`AppCommandOptionType`
|
|
The type of option.
|
|
required: :class:`bool`
|
|
Whether the option is required.
|
|
min_value: Optional[Union[:class:`int`, :class:`float`]]
|
|
Minimum value of the option. Only applicable to :attr:`AppCommandOptionType.integer` and :attr:`AppCommandOptionType.number`.
|
|
max_value: Optional[Union[:class:`int`, :class:`float`]]
|
|
Maximum value of the option. Only applicable to :attr:`AppCommandOptionType.integer` and :attr:`AppCommandOptionType.number`.
|
|
choices: List[:class:`OptionChoice`]
|
|
A list of possible choices to choose from. If these are present, you must choose one from them.
|
|
|
|
Only applicable to :attr:`AppCommandOptionType.string`, :attr:`AppCommandOptionType.integer`, and :attr:`AppCommandOptionType.number`.
|
|
channel_types: List[:class:`ChannelType`]
|
|
A list of channel types that you can choose from. If these are present, you must choose a channel that is one of these types.
|
|
|
|
Only applicable to :attr:`AppCommandOptionType.channel`.
|
|
autocomplete: :class:`bool`
|
|
Whether the option autocompletes.
|
|
|
|
Only applicable to :attr:`AppCommandOptionType.string`, :attr:`AppCommandOptionType.integer`, and :attr:`AppCommandOptionType.number`.
|
|
Always ``False`` if :attr:`choices` are present.
|
|
"""
|
|
|
|
__slots__ = (
|
|
'name',
|
|
'description',
|
|
'type',
|
|
'required',
|
|
'min_value',
|
|
'max_value',
|
|
'choices',
|
|
'channel_types',
|
|
'autocomplete',
|
|
)
|
|
|
|
def __init__(self, data):
|
|
self.name: str = data['name']
|
|
self.description: str = data['description']
|
|
self.type: AppCommandOptionType = try_enum(AppCommandOptionType, data['type'])
|
|
self.required: bool = data.get('required', False)
|
|
self.min_value: Optional[Union[int, float]] = data.get('min_value')
|
|
self.max_value: Optional[int] = data.get('max_value')
|
|
self.choices = [OptionChoice(choice, self.type) for choice in data.get('choices', [])]
|
|
self.channel_types: List[ChannelType] = [try_enum(ChannelType, c) for c in data.get('channel_types', [])]
|
|
self.autocomplete: bool = data.get('autocomplete', False)
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def __repr__(self) -> str:
|
|
return f'<Option name={self.name!r} type={self.type!r} required={self.required}>'
|
|
|
|
def _convert(self, value):
|
|
for choice in self.choices:
|
|
if (new_value := choice._convert(value)) != value:
|
|
return new_value
|
|
return value
|
|
|
|
|
|
class OptionChoice:
|
|
"""Represents a choice for an option.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: str(x)
|
|
|
|
Returns the choice's name.
|
|
|
|
Attributes
|
|
----------
|
|
name: :class:`str`
|
|
The choice's displayed name.
|
|
value: Union[:class:`str`, :class:`int`, :class:`float`]
|
|
The choice's value. The type of this depends on the option's type.
|
|
"""
|
|
|
|
__slots__ = ('name', 'value')
|
|
|
|
def __init__(self, data: Dict[str, str], type: AppCommandOptionType):
|
|
self.name: str = data['name']
|
|
self.value: Union[str, int, float]
|
|
if type is AppCommandOptionType.string:
|
|
self.value = data['value']
|
|
elif type is AppCommandOptionType.integer:
|
|
self.value = int(data['value'])
|
|
elif type is AppCommandOptionType.number:
|
|
self.value = float(data['value'])
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def __repr__(self) -> str:
|
|
return f'<OptionChoice name={self.name!r} value={self.value!r}>'
|
|
|
|
def _convert(self, value):
|
|
if value == self.name:
|
|
return self.value
|
|
return value
|
|
|
|
|
|
def _command_factory(command_type: int) -> Tuple[AppCommandType, Type[BaseCommand]]:
|
|
value = try_enum(AppCommandType, command_type)
|
|
if value is AppCommandType.chat_input:
|
|
return value, SlashCommand
|
|
elif value is AppCommandType.user:
|
|
return value, UserCommand
|
|
elif value is AppCommandType.message:
|
|
return value, MessageCommand
|
|
else:
|
|
return value, BaseCommand # IDK about this
|
|
|