mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-07-02 00:00:02 +00:00
Add Interaction.command and Interaction.namespace attributes
This commit is contained in:
parent
3c6daff473
commit
202b993da3
@ -47,7 +47,6 @@ from textwrap import TextWrapper
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from ..enums import AppCommandOptionType, AppCommandType
|
from ..enums import AppCommandOptionType, AppCommandType
|
||||||
from ..interactions import Interaction
|
|
||||||
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, CommandInvokeError, CommandSignatureMismatch, CommandAlreadyRegistered
|
from .errors import AppCommandError, CommandInvokeError, CommandSignatureMismatch, CommandAlreadyRegistered
|
||||||
@ -58,6 +57,7 @@ from ..utils import resolve_annotation, MISSING, is_inside_class
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import ParamSpec, Concatenate
|
from typing_extensions import ParamSpec, Concatenate
|
||||||
|
from ..interactions import Interaction
|
||||||
from ..abc import Snowflake
|
from ..abc import Snowflake
|
||||||
from .namespace import Namespace
|
from .namespace import Namespace
|
||||||
from .models import ChoiceT
|
from .models import ChoiceT
|
||||||
@ -88,32 +88,32 @@ T = TypeVar('T')
|
|||||||
GroupT = TypeVar('GroupT', bound='Union[Group, Cog]')
|
GroupT = TypeVar('GroupT', bound='Union[Group, Cog]')
|
||||||
Coro = Coroutine[Any, Any, T]
|
Coro = Coroutine[Any, Any, T]
|
||||||
Error = Union[
|
Error = Union[
|
||||||
Callable[[GroupT, Interaction, AppCommandError], Coro[Any]],
|
Callable[[GroupT, 'Interaction', AppCommandError], Coro[Any]],
|
||||||
Callable[[Interaction, AppCommandError], Coro[Any]],
|
Callable[['Interaction', AppCommandError], Coro[Any]],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
CommandCallback = Union[
|
CommandCallback = Union[
|
||||||
Callable[Concatenate[GroupT, Interaction, P], Coro[T]],
|
Callable[Concatenate[GroupT, 'Interaction', P], Coro[T]],
|
||||||
Callable[Concatenate[Interaction, P], Coro[T]],
|
Callable[Concatenate['Interaction', P], Coro[T]],
|
||||||
]
|
]
|
||||||
|
|
||||||
ContextMenuCallback = Union[
|
ContextMenuCallback = Union[
|
||||||
# If groups end up support context menus these would be uncommented
|
# If groups end up support context menus these would be uncommented
|
||||||
# Callable[[GroupT, Interaction, Member], Coro[Any]],
|
# Callable[[GroupT, 'Interaction', Member], Coro[Any]],
|
||||||
# Callable[[GroupT, Interaction, User], Coro[Any]],
|
# Callable[[GroupT, 'Interaction', User], Coro[Any]],
|
||||||
# Callable[[GroupT, Interaction, Message], Coro[Any]],
|
# Callable[[GroupT, 'Interaction', Message], Coro[Any]],
|
||||||
# Callable[[GroupT, Interaction, Union[Member, User]], Coro[Any]],
|
# Callable[[GroupT, 'Interaction', Union[Member, User]], Coro[Any]],
|
||||||
Callable[[Interaction, Member], Coro[Any]],
|
Callable[['Interaction', Member], Coro[Any]],
|
||||||
Callable[[Interaction, User], Coro[Any]],
|
Callable[['Interaction', User], Coro[Any]],
|
||||||
Callable[[Interaction, Message], Coro[Any]],
|
Callable[['Interaction', Message], Coro[Any]],
|
||||||
Callable[[Interaction, Union[Member, User]], Coro[Any]],
|
Callable[['Interaction', Union[Member, User]], Coro[Any]],
|
||||||
]
|
]
|
||||||
|
|
||||||
AutocompleteCallback = Union[
|
AutocompleteCallback = Union[
|
||||||
Callable[[GroupT, Interaction, ChoiceT, Namespace], Coro[List[Choice[ChoiceT]]]],
|
Callable[[GroupT, 'Interaction', ChoiceT, Namespace], Coro[List[Choice[ChoiceT]]]],
|
||||||
Callable[[Interaction, ChoiceT, Namespace], Coro[List[Choice[ChoiceT]]]],
|
Callable[['Interaction', ChoiceT, Namespace], Coro[List[Choice[ChoiceT]]]],
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
CommandCallback = Callable[..., Coro[T]]
|
CommandCallback = Callable[..., Coro[T]]
|
||||||
|
@ -25,7 +25,6 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, NamedTuple, Tuple
|
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, NamedTuple, Tuple
|
||||||
from ..interactions import Interaction
|
|
||||||
from ..member import Member
|
from ..member import Member
|
||||||
from ..object import Object
|
from ..object import Object
|
||||||
from ..role import Role
|
from ..role import Role
|
||||||
@ -35,6 +34,7 @@ from ..enums import AppCommandOptionType
|
|||||||
from .models import AppCommandChannel, AppCommandThread
|
from .models import AppCommandChannel, AppCommandThread
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from ..interactions import Interaction
|
||||||
from ..types.interactions import ResolvedData, ApplicationCommandInteractionDataOption
|
from ..types.interactions import ResolvedData, ApplicationCommandInteractionDataOption
|
||||||
|
|
||||||
__all__ = ('Namespace',)
|
__all__ = ('Namespace',)
|
||||||
|
@ -878,10 +878,69 @@ class CommandTree(Generic[ClientT]):
|
|||||||
|
|
||||||
self.client.loop.create_task(wrapper(), name='CommandTree-invoker')
|
self.client.loop.create_task(wrapper(), name='CommandTree-invoker')
|
||||||
|
|
||||||
|
def _get_context_menu(self, data: ApplicationCommandInteractionData) -> Optional[ContextMenu]:
|
||||||
|
name = data['name']
|
||||||
|
guild_id = _get_as_snowflake(data, 'guild_id')
|
||||||
|
return self._context_menus.get((name, guild_id, data.get('type', 1)))
|
||||||
|
|
||||||
|
def _get_app_command_options(
|
||||||
|
self, data: ApplicationCommandInteractionData
|
||||||
|
) -> Tuple[Command[Any, ..., Any], List[ApplicationCommandInteractionDataOption]]:
|
||||||
|
parents: List[str] = []
|
||||||
|
name = data['name']
|
||||||
|
|
||||||
|
command_guild_id = _get_as_snowflake(data, 'guild_id')
|
||||||
|
if command_guild_id:
|
||||||
|
try:
|
||||||
|
guild_commands = self._guild_commands[command_guild_id]
|
||||||
|
except KeyError:
|
||||||
|
command = None
|
||||||
|
else:
|
||||||
|
command = guild_commands.get(name)
|
||||||
|
else:
|
||||||
|
command = self._global_commands.get(name)
|
||||||
|
|
||||||
|
# If it's not found at this point then it's not gonna be found at any point
|
||||||
|
if command is None:
|
||||||
|
raise CommandNotFound(name, parents)
|
||||||
|
|
||||||
|
# This could be done recursively but it'd be a bother due to the state needed
|
||||||
|
# to be tracked above like the parents, the actual command type, and the
|
||||||
|
# resulting options we care about
|
||||||
|
searching = True
|
||||||
|
options: List[ApplicationCommandInteractionDataOption] = data.get('options', [])
|
||||||
|
while searching:
|
||||||
|
for option in options:
|
||||||
|
# Find subcommands
|
||||||
|
if option.get('type', 0) in (1, 2):
|
||||||
|
parents.append(name)
|
||||||
|
name = option['name']
|
||||||
|
command = command._get_internal_command(name)
|
||||||
|
if command is None:
|
||||||
|
raise CommandNotFound(name, parents)
|
||||||
|
options = option.get('options', [])
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
searching = False
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if isinstance(command, Group):
|
||||||
|
# Right now, groups can't be invoked. This is a Discord limitation in how they
|
||||||
|
# do slash commands. So if we're here and we have a Group rather than a Command instance
|
||||||
|
# then something in the code is out of date from the data that Discord has.
|
||||||
|
raise CommandSignatureMismatch(command)
|
||||||
|
|
||||||
|
return (command, options)
|
||||||
|
|
||||||
async def _call_context_menu(self, interaction: Interaction, data: ApplicationCommandInteractionData, type: int) -> None:
|
async def _call_context_menu(self, interaction: Interaction, data: ApplicationCommandInteractionData, type: int) -> None:
|
||||||
name = data['name']
|
name = data['name']
|
||||||
guild_id = _get_as_snowflake(data, 'guild_id')
|
guild_id = _get_as_snowflake(data, 'guild_id')
|
||||||
ctx_menu = self._context_menus.get((name, guild_id, type))
|
ctx_menu = self._context_menus.get((name, guild_id, type))
|
||||||
|
# Pre-fill the cached slot to prevent re-computation
|
||||||
|
interaction._cs_command = ctx_menu
|
||||||
|
|
||||||
if ctx_menu is None:
|
if ctx_menu is None:
|
||||||
raise CommandNotFound(name, [], AppCommandType(type))
|
raise CommandNotFound(name, [], AppCommandType(type))
|
||||||
|
|
||||||
@ -936,56 +995,18 @@ class CommandTree(Generic[ClientT]):
|
|||||||
await self._call_context_menu(interaction, data, type)
|
await self._call_context_menu(interaction, data, type)
|
||||||
return
|
return
|
||||||
|
|
||||||
parents: List[str] = []
|
command, options = self._get_app_command_options(data)
|
||||||
name = data['name']
|
|
||||||
|
|
||||||
command_guild_id = _get_as_snowflake(data, 'guild_id')
|
# Pre-fill the cached slot to prevent re-computation
|
||||||
if command_guild_id:
|
interaction._cs_command = command
|
||||||
try:
|
|
||||||
guild_commands = self._guild_commands[command_guild_id]
|
|
||||||
except KeyError:
|
|
||||||
command = None
|
|
||||||
else:
|
|
||||||
command = guild_commands.get(name)
|
|
||||||
else:
|
|
||||||
command = self._global_commands.get(name)
|
|
||||||
|
|
||||||
# If it's not found at this point then it's not gonna be found at any point
|
|
||||||
if command is None:
|
|
||||||
raise CommandNotFound(name, parents)
|
|
||||||
|
|
||||||
# This could be done recursively but it'd be a bother due to the state needed
|
|
||||||
# to be tracked above like the parents, the actual command type, and the
|
|
||||||
# resulting options we care about
|
|
||||||
searching = True
|
|
||||||
options: List[ApplicationCommandInteractionDataOption] = data.get('options', [])
|
|
||||||
while searching:
|
|
||||||
for option in options:
|
|
||||||
# Find subcommands
|
|
||||||
if option.get('type', 0) in (1, 2):
|
|
||||||
parents.append(name)
|
|
||||||
name = option['name']
|
|
||||||
command = command._get_internal_command(name)
|
|
||||||
if command is None:
|
|
||||||
raise CommandNotFound(name, parents)
|
|
||||||
options = option.get('options', [])
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
searching = False
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
if isinstance(command, Group):
|
|
||||||
# Right now, groups can't be invoked. This is a Discord limitation in how they
|
|
||||||
# do slash commands. So if we're here and we have a Group rather than a Command instance
|
|
||||||
# then something in the code is out of date from the data that Discord has.
|
|
||||||
raise CommandSignatureMismatch(command)
|
|
||||||
|
|
||||||
# At this point options refers to the arguments of the command
|
# At this point options refers to the arguments of the command
|
||||||
# and command refers to the class type we care about
|
# and command refers to the class type we care about
|
||||||
namespace = Namespace(interaction, data.get('resolved', {}), options)
|
namespace = Namespace(interaction, data.get('resolved', {}), options)
|
||||||
|
|
||||||
|
# Same pre-fill as above
|
||||||
|
interaction._cs_namespace = namespace
|
||||||
|
|
||||||
# Auto complete handles the namespace differently... so at this point this is where we decide where that is.
|
# Auto complete handles the namespace differently... so at this point this is where we decide where that is.
|
||||||
if interaction.type is InteractionType.autocomplete:
|
if interaction.type is InteractionType.autocomplete:
|
||||||
focused = next((opt['name'] for opt in options if opt.get('focused')), None)
|
focused = next((opt['name'] for opt in options if opt.get('focused')), None)
|
||||||
|
@ -25,13 +25,13 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Sequence, Tuple, Union
|
from typing import Any, Dict, Optional, TYPE_CHECKING, Sequence, Tuple, Union
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .enums import try_enum, Locale, InteractionType, InteractionResponseType
|
from .enums import try_enum, Locale, InteractionType, InteractionResponseType
|
||||||
from .errors import InteractionResponded, HTTPException, ClientException
|
from .errors import InteractionResponded, HTTPException, ClientException, DiscordException
|
||||||
from .flags import MessageFlags
|
from .flags import MessageFlags
|
||||||
from .channel import PartialMessageable, ChannelType
|
from .channel import PartialMessageable, ChannelType
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ from .object import Object
|
|||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .http import handle_message_parameters
|
from .http import handle_message_parameters
|
||||||
from .webhook.async_ import async_context, Webhook, interaction_response_params, interaction_message_response_params
|
from .webhook.async_ import async_context, Webhook, interaction_response_params, interaction_message_response_params
|
||||||
|
from .app_commands.namespace import Namespace
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Interaction',
|
'Interaction',
|
||||||
@ -53,6 +54,7 @@ if TYPE_CHECKING:
|
|||||||
from .types.interactions import (
|
from .types.interactions import (
|
||||||
Interaction as InteractionPayload,
|
Interaction as InteractionPayload,
|
||||||
InteractionData,
|
InteractionData,
|
||||||
|
ApplicationCommandInteractionData,
|
||||||
)
|
)
|
||||||
from .types.webhook import (
|
from .types.webhook import (
|
||||||
Webhook as WebhookPayload,
|
Webhook as WebhookPayload,
|
||||||
@ -69,6 +71,7 @@ if TYPE_CHECKING:
|
|||||||
from .ui.modal import Modal
|
from .ui.modal import Modal
|
||||||
from .channel import VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, PartialMessageable
|
from .channel import VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, PartialMessageable
|
||||||
from .threads import Thread
|
from .threads import Thread
|
||||||
|
from .app_commands.commands import Command, ContextMenu
|
||||||
|
|
||||||
InteractionChannel = Union[
|
InteractionChannel = Union[
|
||||||
VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, Thread, PartialMessageable
|
VoiceChannel, StageChannel, TextChannel, CategoryChannel, StoreChannel, Thread, PartialMessageable
|
||||||
@ -133,6 +136,8 @@ class Interaction:
|
|||||||
'_cs_response',
|
'_cs_response',
|
||||||
'_cs_followup',
|
'_cs_followup',
|
||||||
'_cs_channel',
|
'_cs_channel',
|
||||||
|
'_cs_namespace',
|
||||||
|
'_cs_command',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *, data: InteractionPayload, state: ConnectionState):
|
def __init__(self, *, data: InteractionPayload, state: ConnectionState):
|
||||||
@ -220,6 +225,58 @@ class Interaction:
|
|||||||
"""
|
"""
|
||||||
return Permissions(self._permissions)
|
return Permissions(self._permissions)
|
||||||
|
|
||||||
|
@utils.cached_slot_property('_cs_namespace')
|
||||||
|
def namespace(self) -> Namespace:
|
||||||
|
""":class:`app_commands.Namespace`: The resolved namespace for this interaction.
|
||||||
|
|
||||||
|
If the interaction is not an application command related interaction or the client does not have a
|
||||||
|
tree attached to it then this returns an empty namespace.
|
||||||
|
"""
|
||||||
|
if self.type not in (InteractionType.application_command, InteractionType.autocomplete):
|
||||||
|
return Namespace(self, {}, [])
|
||||||
|
|
||||||
|
tree = self._state._command_tree
|
||||||
|
if tree is None:
|
||||||
|
return Namespace(self, {}, [])
|
||||||
|
|
||||||
|
# The type checker does not understand this narrowing
|
||||||
|
data: ApplicationCommandInteractionData = self.data # type: ignore
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, options = tree._get_app_command_options(data)
|
||||||
|
except DiscordException:
|
||||||
|
options = []
|
||||||
|
|
||||||
|
return Namespace(self, data.get('resolved', {}), options)
|
||||||
|
|
||||||
|
@utils.cached_slot_property('_cs_command')
|
||||||
|
def command(self) -> Optional[Union[Command[Any, ..., Any], ContextMenu]]:
|
||||||
|
"""Optional[Union[:class:`app_commands.Command`, :class:`app_commands.ContextMenu`]]: The command being called from
|
||||||
|
this interaction.
|
||||||
|
|
||||||
|
If the interaction is not an application command related interaction or the command is not found in the client's
|
||||||
|
attached tree then ``None`` is returned.
|
||||||
|
"""
|
||||||
|
if self.type not in (InteractionType.application_command, InteractionType.autocomplete):
|
||||||
|
return None
|
||||||
|
|
||||||
|
tree = self._state._command_tree
|
||||||
|
if tree is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# The type checker does not understand this narrowing
|
||||||
|
data: ApplicationCommandInteractionData = self.data # type: ignore
|
||||||
|
cmd_type = data.get('type', 1)
|
||||||
|
if cmd_type == 1:
|
||||||
|
try:
|
||||||
|
command, _ = tree._get_app_command_options(data)
|
||||||
|
except DiscordException:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return command
|
||||||
|
else:
|
||||||
|
return tree._get_context_menu(data)
|
||||||
|
|
||||||
@utils.cached_slot_property('_cs_response')
|
@utils.cached_slot_property('_cs_response')
|
||||||
def response(self) -> InteractionResponse:
|
def response(self) -> InteractionResponse:
|
||||||
""":class:`InteractionResponse`: Returns an object responsible for handling responding to the interaction.
|
""":class:`InteractionResponse`: Returns an object responsible for handling responding to the interaction.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user