diff --git a/discord/ext/commands/hybrid.py b/discord/ext/commands/hybrid.py index 144c28864..2c9587614 100644 --- a/discord/ext/commands/hybrid.py +++ b/discord/ext/commands/hybrid.py @@ -202,7 +202,7 @@ def replace_parameters(parameters: Dict[str, Parameter], signature: inspect.Sign class HybridAppCommand(discord.app_commands.Command[CogT, P, T]): - def __init__(self, wrapped: HybridCommand[CogT, Any, T]) -> None: + def __init__(self, wrapped: Command[CogT, Any, T]) -> None: signature = inspect.signature(wrapped.callback) params = replace_parameters(wrapped.params, signature) wrapped.callback.__signature__ = signature.replace(parameters=params) @@ -216,7 +216,7 @@ class HybridAppCommand(discord.app_commands.Command[CogT, P, T]): finally: del wrapped.callback.__signature__ - self.wrapped: HybridCommand[CogT, Any, T] = wrapped + self.wrapped: Command[CogT, Any, T] = wrapped self.binding = wrapped.cog def _copy_with(self, **kwargs) -> Self: @@ -435,11 +435,19 @@ class HybridGroup(Group[CogT, P, T]): decorator or functional interface. .. versionadded:: 2.0 + + Parameters + ----------- + fallback: Optional[:class:`str`] + The command name to use as a fallback for the application command. Since + application command groups cannot be invoked, this creates a subcommand within + the group that can be invoked with the given group callback. If ``None`` + then no fallback command is given. Defaults to ``None``. """ __commands_is_hybrid__: ClassVar[bool] = True - def __init__(self, *args: Any, **attrs: Any) -> None: + def __init__(self, *args: Any, fallback: Optional[str] = None, **attrs: Any) -> None: super().__init__(*args, **attrs) self.invoke_without_command = True parent = None @@ -458,6 +466,83 @@ class HybridGroup(Group[CogT, P, T]): # This prevents the group from re-adding the command at __init__ self.app_command.parent = parent + self.fallback: Optional[str] = fallback + + if fallback is not None: + command = HybridAppCommand(self) + command.name = fallback + self.app_command.add_command(command) + + @property + def _fallback_command(self) -> Optional[HybridAppCommand[CogT, ..., T]]: + return self.app_command.get_command(self.fallback) # type: ignore + + @property + def cog(self) -> CogT: + return self._cog + + @cog.setter + def cog(self, value: CogT) -> None: + self._cog = value + fallback = self._fallback_command + if fallback: + fallback.binding = value + + async def can_run(self, ctx: Context[BotT], /) -> bool: + fallback = self._fallback_command + if ctx.interaction is not None and fallback: + return await fallback._check_can_run(ctx.interaction) + else: + return await super().can_run(ctx) + + async def _parse_arguments(self, ctx: Context[BotT]) -> None: + interaction = ctx.interaction + fallback = self._fallback_command + if interaction is not None and fallback: + ctx.kwargs = await fallback._transform_arguments(interaction, interaction.namespace) + else: + return await super()._parse_arguments(ctx) + + def _ensure_assignment_on_copy(self, other: Self) -> Self: + copy = super()._ensure_assignment_on_copy(other) + copy.fallback = self.fallback + return copy + + def autocomplete( + self, name: str + ) -> Callable[[AutocompleteCallback[CogT, ChoiceT]], AutocompleteCallback[CogT, ChoiceT]]: + """A decorator that registers a coroutine as an autocomplete prompt for a parameter. + + This is the same as :meth:`~discord.app_commands.Command.autocomplete`. It is only + applicable for the application command and doesn't do anything if the command is + a regular command. + + This is only available if the group has a fallback application command registered. + + .. note:: + + Similar to the :meth:`~discord.app_commands.Command.autocomplete` method, this + takes :class:`~discord.Interaction` as a parameter rather than a :class:`Context`. + + Parameters + ----------- + name: :class:`str` + The parameter name to register as autocomplete. + + Raises + ------- + TypeError + The coroutine passed is not actually a coroutine or + the parameter is not found or of an invalid type. + """ + if self._fallback_command: + return self._fallback_command.autocomplete(name) + else: + + def decorator(func: AutocompleteCallback[CogT, ChoiceT]) -> AutocompleteCallback[CogT, ChoiceT]: + return func + + return decorator def add_command(self, command: Union[HybridGroup[CogT, ..., Any], HybridCommand[CogT, ..., Any]], /) -> None: """Adds a :class:`.HybridCommand` into the internal list of commands.