mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-05-11 08:19:54 +00:00
[commands] Add support for with_app_command in hybrid commands
This allows the user to make a text-only command without it registering as an application command
This commit is contained in:
parent
e3ea4706f9
commit
ccc737eb07
@ -224,23 +224,23 @@ class BotBase(GroupMixin[None]):
|
|||||||
@discord.utils.copy_doc(GroupMixin.add_command)
|
@discord.utils.copy_doc(GroupMixin.add_command)
|
||||||
def add_command(self, command: Command[Any, ..., Any], /) -> None:
|
def add_command(self, command: Command[Any, ..., Any], /) -> None:
|
||||||
super().add_command(command)
|
super().add_command(command)
|
||||||
if hasattr(command, '__commands_is_hybrid__'):
|
if isinstance(command, (HybridCommand, HybridGroup)) and command.app_command:
|
||||||
# If a cog is also inheriting from app_commands.Group then it'll also
|
# If a cog is also inheriting from app_commands.Group then it'll also
|
||||||
# add the hybrid commands as text commands, which would recursively add the
|
# add the hybrid commands as text commands, which would recursively add the
|
||||||
# hybrid commands as slash commands. This check just terminates that recursion
|
# hybrid commands as slash commands. This check just terminates that recursion
|
||||||
# from happening
|
# from happening
|
||||||
if command.cog is None or not command.cog.__cog_is_app_commands_group__:
|
if command.cog is None or not command.cog.__cog_is_app_commands_group__:
|
||||||
self.tree.add_command(command.app_command) # type: ignore
|
self.tree.add_command(command.app_command)
|
||||||
|
|
||||||
@discord.utils.copy_doc(GroupMixin.remove_command)
|
@discord.utils.copy_doc(GroupMixin.remove_command)
|
||||||
def remove_command(self, name: str, /) -> Optional[Command[Any, ..., Any]]:
|
def remove_command(self, name: str, /) -> Optional[Command[Any, ..., Any]]:
|
||||||
cmd = super().remove_command(name)
|
cmd: Optional[Command[Any, ..., Any]] = super().remove_command(name)
|
||||||
if cmd is not None and hasattr(cmd, '__commands_is_hybrid__'):
|
if isinstance(cmd, (HybridCommand, HybridGroup)) and cmd.app_command:
|
||||||
# See above
|
# See above
|
||||||
if cmd.cog is not None and cmd.cog.__cog_is_app_commands_group__:
|
if cmd.cog is not None and cmd.cog.__cog_is_app_commands_group__:
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
guild_ids: Optional[List[int]] = cmd.app_command._guild_ids # type: ignore
|
guild_ids: Optional[List[int]] = cmd.app_command._guild_ids
|
||||||
if guild_ids is None:
|
if guild_ids is None:
|
||||||
self.__tree.remove_command(name)
|
self.__tree.remove_command(name)
|
||||||
else:
|
else:
|
||||||
@ -252,6 +252,7 @@ class BotBase(GroupMixin[None]):
|
|||||||
def hybrid_command(
|
def hybrid_command(
|
||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: str = MISSING,
|
||||||
|
with_app_command: bool = True,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridCommand[Any, P, T]]:
|
) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridCommand[Any, P, T]]:
|
||||||
@ -266,7 +267,7 @@ class BotBase(GroupMixin[None]):
|
|||||||
|
|
||||||
def decorator(func: CommandCallback[Any, ContextT, P, T]):
|
def decorator(func: CommandCallback[Any, ContextT, P, T]):
|
||||||
kwargs.setdefault('parent', self)
|
kwargs.setdefault('parent', self)
|
||||||
result = hybrid_command(name=name, *args, **kwargs)(func)
|
result = hybrid_command(name=name, *args, with_app_command=with_app_command, **kwargs)(func)
|
||||||
self.add_command(result)
|
self.add_command(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -275,6 +276,7 @@ class BotBase(GroupMixin[None]):
|
|||||||
def hybrid_group(
|
def hybrid_group(
|
||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: str = MISSING,
|
||||||
|
with_app_command: bool = True,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridGroup[Any, P, T]]:
|
) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridGroup[Any, P, T]]:
|
||||||
@ -289,7 +291,7 @@ class BotBase(GroupMixin[None]):
|
|||||||
|
|
||||||
def decorator(func: CommandCallback[Any, ContextT, P, T]):
|
def decorator(func: CommandCallback[Any, ContextT, P, T]):
|
||||||
kwargs.setdefault('parent', self)
|
kwargs.setdefault('parent', self)
|
||||||
result = hybrid_group(name=name, *args, **kwargs)(func)
|
result = hybrid_group(name=name, *args, with_app_command=with_app_command, **kwargs)(func)
|
||||||
self.add_command(result)
|
self.add_command(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -291,10 +291,15 @@ class Cog(metaclass=CogMeta):
|
|||||||
parent.add_command(command) # type: ignore
|
parent.add_command(command) # type: ignore
|
||||||
elif self.__cog_app_commands_group__:
|
elif self.__cog_app_commands_group__:
|
||||||
if hasattr(command, '__commands_is_hybrid__') and command.parent is None:
|
if hasattr(command, '__commands_is_hybrid__') and command.parent is None:
|
||||||
# In both of these, the type checker does not see the app_command attribute even though it exists
|
|
||||||
parent = self.__cog_app_commands_group__
|
parent = self.__cog_app_commands_group__
|
||||||
command.app_command = command.app_command._copy_with(parent=parent, binding=self) # type: ignore
|
app_command: Optional[Union[app_commands.Group, app_commands.Command[Self, ..., Any]]] = getattr(
|
||||||
children.append(command.app_command) # type: ignore
|
command, 'app_command', None
|
||||||
|
)
|
||||||
|
if app_command:
|
||||||
|
app_command = app_command._copy_with(parent=parent, binding=self)
|
||||||
|
children.append(app_command)
|
||||||
|
# The type checker does not see the app_command attribute even though it exists
|
||||||
|
command.app_command = app_command # type: ignore
|
||||||
|
|
||||||
for command in cls.__cog_app_commands__:
|
for command in cls.__cog_app_commands__:
|
||||||
copy = command._copy_with(parent=self.__cog_app_commands_group__, binding=self)
|
copy = command._copy_with(parent=self.__cog_app_commands_group__, binding=self)
|
||||||
|
@ -386,7 +386,15 @@ class HybridCommand(Command[CogT, P, T]):
|
|||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(func, **kwargs)
|
super().__init__(func, **kwargs)
|
||||||
self.app_command: HybridAppCommand[CogT, Any, T] = HybridAppCommand(self)
|
self.with_app_command: bool = kwargs.pop('with_app_command', True)
|
||||||
|
self.with_command: bool = kwargs.pop('with_command', True)
|
||||||
|
|
||||||
|
if not self.with_command and not self.with_app_command:
|
||||||
|
raise TypeError('cannot set both with_command and with_app_command to False')
|
||||||
|
|
||||||
|
self.app_command: Optional[HybridAppCommand[CogT, Any, T]] = (
|
||||||
|
HybridAppCommand(self) if self.with_app_command else None
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cog(self) -> CogT:
|
def cog(self) -> CogT:
|
||||||
@ -395,25 +403,29 @@ class HybridCommand(Command[CogT, P, T]):
|
|||||||
@cog.setter
|
@cog.setter
|
||||||
def cog(self, value: CogT) -> None:
|
def cog(self, value: CogT) -> None:
|
||||||
self._cog = value
|
self._cog = value
|
||||||
self.app_command.binding = value
|
if self.app_command is not None:
|
||||||
|
self.app_command.binding = value
|
||||||
|
|
||||||
async def can_run(self, ctx: Context[BotT], /) -> bool:
|
async def can_run(self, ctx: Context[BotT], /) -> bool:
|
||||||
if ctx.interaction is None:
|
if ctx.interaction is not None and self.app_command:
|
||||||
return await super().can_run(ctx)
|
|
||||||
else:
|
|
||||||
return await self.app_command._check_can_run(ctx.interaction)
|
return await self.app_command._check_can_run(ctx.interaction)
|
||||||
|
else:
|
||||||
|
return await super().can_run(ctx)
|
||||||
|
|
||||||
async def _parse_arguments(self, ctx: Context[BotT]) -> None:
|
async def _parse_arguments(self, ctx: Context[BotT]) -> None:
|
||||||
interaction = ctx.interaction
|
interaction = ctx.interaction
|
||||||
if interaction is None:
|
if interaction is None:
|
||||||
return await super()._parse_arguments(ctx)
|
return await super()._parse_arguments(ctx)
|
||||||
else:
|
elif self.app_command:
|
||||||
ctx.kwargs = await self.app_command._transform_arguments(interaction, interaction.namespace)
|
ctx.kwargs = await self.app_command._transform_arguments(interaction, interaction.namespace)
|
||||||
|
|
||||||
def _ensure_assignment_on_copy(self, other: Self) -> Self:
|
def _ensure_assignment_on_copy(self, other: Self) -> Self:
|
||||||
copy = super()._ensure_assignment_on_copy(other)
|
copy = super()._ensure_assignment_on_copy(other)
|
||||||
copy.app_command = self.app_command.copy()
|
if self.app_command is None:
|
||||||
copy.app_command.wrapped = copy
|
copy.app_command = None
|
||||||
|
else:
|
||||||
|
copy.app_command = self.app_command.copy()
|
||||||
|
copy.app_command.wrapped = copy
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
def autocomplete(
|
def autocomplete(
|
||||||
@ -441,6 +453,9 @@ class HybridCommand(Command[CogT, P, T]):
|
|||||||
The coroutine passed is not actually a coroutine or
|
The coroutine passed is not actually a coroutine or
|
||||||
the parameter is not found or of an invalid type.
|
the parameter is not found or of an invalid type.
|
||||||
"""
|
"""
|
||||||
|
if self.app_command is None:
|
||||||
|
raise TypeError('This command does not have a registered application command')
|
||||||
|
|
||||||
return self.app_command.autocomplete(name)
|
return self.app_command.autocomplete(name)
|
||||||
|
|
||||||
|
|
||||||
@ -473,6 +488,8 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
def __init__(self, *args: Any, fallback: Optional[str] = None, **attrs: Any) -> None:
|
def __init__(self, *args: Any, fallback: Optional[str] = None, **attrs: Any) -> None:
|
||||||
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)
|
||||||
|
|
||||||
parent = None
|
parent = None
|
||||||
if self.parent is not None:
|
if self.parent is not None:
|
||||||
if isinstance(self.parent, HybridGroup):
|
if isinstance(self.parent, HybridGroup):
|
||||||
@ -480,28 +497,38 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
else:
|
else:
|
||||||
raise TypeError(f'HybridGroup parent must be HybridGroup not {self.parent.__class__}')
|
raise TypeError(f'HybridGroup parent must be HybridGroup not {self.parent.__class__}')
|
||||||
|
|
||||||
guild_ids = attrs.pop('guild_ids', None) or getattr(self.callback, '__discord_app_commands_default_guilds__', None)
|
# I would love for this to be Optional[app_commands.Group]
|
||||||
guild_only = getattr(self.callback, '__discord_app_commands_guild_only__', False)
|
# However, Python does not have conditional typing so it's very hard to
|
||||||
default_permissions = getattr(self.callback, '__discord_app_commands_default_permissions__', None)
|
# make this type depend on the with_app_command bool without a lot of needless repetition
|
||||||
self.app_command: app_commands.Group = app_commands.Group(
|
self.app_command: app_commands.Group = MISSING
|
||||||
name=self.name,
|
|
||||||
description=self.description or self.short_doc or '…',
|
|
||||||
guild_ids=guild_ids,
|
|
||||||
guild_only=guild_only,
|
|
||||||
default_permissions=default_permissions,
|
|
||||||
)
|
|
||||||
|
|
||||||
# This prevents the group from re-adding the command at __init__
|
|
||||||
self.app_command.parent = parent
|
|
||||||
self.fallback: Optional[str] = fallback
|
self.fallback: Optional[str] = fallback
|
||||||
|
|
||||||
if fallback is not None:
|
if self.with_app_command:
|
||||||
command = HybridAppCommand(self)
|
guild_ids = attrs.pop('guild_ids', None) or getattr(
|
||||||
command.name = fallback
|
self.callback, '__discord_app_commands_default_guilds__', None
|
||||||
self.app_command.add_command(command)
|
)
|
||||||
|
guild_only = getattr(self.callback, '__discord_app_commands_guild_only__', False)
|
||||||
|
default_permissions = getattr(self.callback, '__discord_app_commands_default_permissions__', None)
|
||||||
|
self.app_command = app_commands.Group(
|
||||||
|
name=self.name,
|
||||||
|
description=self.description or self.short_doc or '…',
|
||||||
|
guild_ids=guild_ids,
|
||||||
|
guild_only=guild_only,
|
||||||
|
default_permissions=default_permissions,
|
||||||
|
)
|
||||||
|
|
||||||
|
# This prevents the group from re-adding the command at __init__
|
||||||
|
self.app_command.parent = parent
|
||||||
|
|
||||||
|
if fallback is not None:
|
||||||
|
command = HybridAppCommand(self)
|
||||||
|
command.name = fallback
|
||||||
|
self.app_command.add_command(command)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _fallback_command(self) -> Optional[HybridAppCommand[CogT, ..., T]]:
|
def _fallback_command(self) -> Optional[HybridAppCommand[CogT, ..., T]]:
|
||||||
|
if self.app_command is MISSING:
|
||||||
|
return None
|
||||||
return self.app_command.get_command(self.fallback) # type: ignore
|
return self.app_command.get_command(self.fallback) # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -596,7 +623,9 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
if isinstance(command, HybridGroup) and self.parent is not None:
|
if isinstance(command, HybridGroup) and self.parent is not None:
|
||||||
raise ValueError(f'{command.qualified_name!r} is too nested, groups can only be nested at most one level')
|
raise ValueError(f'{command.qualified_name!r} is too nested, groups can only be nested at most one level')
|
||||||
|
|
||||||
self.app_command.add_command(command.app_command)
|
if command.app_command and self.app_command:
|
||||||
|
self.app_command.add_command(command.app_command)
|
||||||
|
|
||||||
command.parent = self
|
command.parent = self
|
||||||
|
|
||||||
if command.name in self.all_commands:
|
if command.name in self.all_commands:
|
||||||
@ -611,13 +640,15 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
def remove_command(self, name: str, /) -> Optional[Command[CogT, ..., Any]]:
|
def remove_command(self, name: str, /) -> Optional[Command[CogT, ..., Any]]:
|
||||||
cmd = super().remove_command(name)
|
cmd = super().remove_command(name)
|
||||||
self.app_command.remove_command(name)
|
if self.app_command:
|
||||||
|
self.app_command.remove_command(name)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def command(
|
def command(
|
||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: str = MISSING,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
|
with_app_command: bool = True,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridCommand[CogT, P2, U]]:
|
) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridCommand[CogT, P2, U]]:
|
||||||
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_command` and adds it to
|
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_command` and adds it to
|
||||||
@ -631,7 +662,7 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
|
def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
|
||||||
kwargs.setdefault('parent', self)
|
kwargs.setdefault('parent', self)
|
||||||
result = hybrid_command(name=name, *args, **kwargs)(func)
|
result = hybrid_command(name=name, *args, with_app_command=with_app_command, **kwargs)(func)
|
||||||
self.add_command(result)
|
self.add_command(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -641,6 +672,7 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
self,
|
self,
|
||||||
name: str = MISSING,
|
name: str = MISSING,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
|
with_app_command: bool = True,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridGroup[CogT, P2, U]]:
|
) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridGroup[CogT, P2, U]]:
|
||||||
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_group` and adds it to
|
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_group` and adds it to
|
||||||
@ -654,7 +686,7 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
|
def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
|
||||||
kwargs.setdefault('parent', self)
|
kwargs.setdefault('parent', self)
|
||||||
result = hybrid_group(name=name, *args, **kwargs)(func)
|
result = hybrid_group(name=name, *args, with_app_command=with_app_command, **kwargs)(func)
|
||||||
self.add_command(result)
|
self.add_command(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -663,9 +695,11 @@ class HybridGroup(Group[CogT, P, T]):
|
|||||||
|
|
||||||
def hybrid_command(
|
def hybrid_command(
|
||||||
name: str = MISSING,
|
name: str = MISSING,
|
||||||
|
*,
|
||||||
|
with_app_command: bool = True,
|
||||||
**attrs: Any,
|
**attrs: Any,
|
||||||
) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridCommand[CogT, P, T]]:
|
) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridCommand[CogT, P, T]]:
|
||||||
"""A decorator that transforms a function into a :class:`.HybridCommand`.
|
r"""A decorator that transforms a function into a :class:`.HybridCommand`.
|
||||||
|
|
||||||
A hybrid command is one that functions both as a regular :class:`.Command`
|
A hybrid command is one that functions both as a regular :class:`.Command`
|
||||||
and one that is also a :class:`app_commands.Command <discord.app_commands.Command>`.
|
and one that is also a :class:`app_commands.Command <discord.app_commands.Command>`.
|
||||||
@ -690,7 +724,9 @@ def hybrid_command(
|
|||||||
name: :class:`str`
|
name: :class:`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.
|
||||||
attrs
|
with_app_command: :class:`bool`
|
||||||
|
Whether to register the command as an application command.
|
||||||
|
\*\*attrs
|
||||||
Keyword arguments to pass into the construction of the
|
Keyword arguments to pass into the construction of the
|
||||||
hybrid command.
|
hybrid command.
|
||||||
|
|
||||||
@ -703,24 +739,36 @@ def hybrid_command(
|
|||||||
def decorator(func: CommandCallback[CogT, ContextT, P, T]):
|
def decorator(func: CommandCallback[CogT, ContextT, P, T]):
|
||||||
if isinstance(func, Command):
|
if isinstance(func, Command):
|
||||||
raise TypeError('Callback is already a command.')
|
raise TypeError('Callback is already a command.')
|
||||||
return HybridCommand(func, name=name, **attrs)
|
return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def hybrid_group(
|
def hybrid_group(
|
||||||
name: str = MISSING,
|
name: str = MISSING,
|
||||||
|
*,
|
||||||
|
with_app_command: bool = True,
|
||||||
**attrs: Any,
|
**attrs: Any,
|
||||||
) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridGroup[CogT, P, T]]:
|
) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridGroup[CogT, P, T]]:
|
||||||
"""A decorator that transforms a function into a :class:`.HybridGroup`.
|
"""A decorator that transforms a function into a :class:`.HybridGroup`.
|
||||||
|
|
||||||
This is similar to the :func:`~discord.ext.commands.group` decorator except it creates
|
This is similar to the :func:`~discord.ext.commands.group` decorator except it creates
|
||||||
a hybrid group instead.
|
a hybrid group instead.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
with_app_command: :class:`bool`
|
||||||
|
Whether to register the command as an application command.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
TypeError
|
||||||
|
If the function is not a coroutine or is already a command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func: CommandCallback[CogT, ContextT, P, T]):
|
def decorator(func: CommandCallback[CogT, ContextT, P, T]):
|
||||||
if isinstance(func, Command):
|
if isinstance(func, Command):
|
||||||
raise TypeError('Callback is already a command.')
|
raise TypeError('Callback is already a command.')
|
||||||
return HybridGroup(func, name=name, **attrs)
|
return HybridGroup(func, name=name, with_app_command=with_app_command, **attrs)
|
||||||
|
|
||||||
return decorator # type: ignore
|
return decorator # type: ignore
|
||||||
|
Loading…
x
Reference in New Issue
Block a user