mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-05-10 15:59:50 +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)
|
||||
def add_command(self, command: Command[Any, ..., Any], /) -> None:
|
||||
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
|
||||
# add the hybrid commands as text commands, which would recursively add the
|
||||
# hybrid commands as slash commands. This check just terminates that recursion
|
||||
# from happening
|
||||
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)
|
||||
def remove_command(self, name: str, /) -> Optional[Command[Any, ..., Any]]:
|
||||
cmd = super().remove_command(name)
|
||||
if cmd is not None and hasattr(cmd, '__commands_is_hybrid__'):
|
||||
cmd: Optional[Command[Any, ..., Any]] = super().remove_command(name)
|
||||
if isinstance(cmd, (HybridCommand, HybridGroup)) and cmd.app_command:
|
||||
# See above
|
||||
if cmd.cog is not None and cmd.cog.__cog_is_app_commands_group__:
|
||||
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:
|
||||
self.__tree.remove_command(name)
|
||||
else:
|
||||
@ -252,6 +252,7 @@ class BotBase(GroupMixin[None]):
|
||||
def hybrid_command(
|
||||
self,
|
||||
name: str = MISSING,
|
||||
with_app_command: bool = True,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> 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]):
|
||||
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)
|
||||
return result
|
||||
|
||||
@ -275,6 +276,7 @@ class BotBase(GroupMixin[None]):
|
||||
def hybrid_group(
|
||||
self,
|
||||
name: str = MISSING,
|
||||
with_app_command: bool = True,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> 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]):
|
||||
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)
|
||||
return result
|
||||
|
||||
|
@ -291,10 +291,15 @@ class Cog(metaclass=CogMeta):
|
||||
parent.add_command(command) # type: ignore
|
||||
elif self.__cog_app_commands_group__:
|
||||
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__
|
||||
command.app_command = command.app_command._copy_with(parent=parent, binding=self) # type: ignore
|
||||
children.append(command.app_command) # type: ignore
|
||||
app_command: Optional[Union[app_commands.Group, app_commands.Command[Self, ..., Any]]] = getattr(
|
||||
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__:
|
||||
copy = command._copy_with(parent=self.__cog_app_commands_group__, binding=self)
|
||||
|
@ -386,7 +386,15 @@ class HybridCommand(Command[CogT, P, T]):
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
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
|
||||
def cog(self) -> CogT:
|
||||
@ -395,25 +403,29 @@ class HybridCommand(Command[CogT, P, T]):
|
||||
@cog.setter
|
||||
def cog(self, value: CogT) -> None:
|
||||
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:
|
||||
if ctx.interaction is None:
|
||||
return await super().can_run(ctx)
|
||||
else:
|
||||
if ctx.interaction is not None and self.app_command:
|
||||
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:
|
||||
interaction = ctx.interaction
|
||||
if interaction is None:
|
||||
return await super()._parse_arguments(ctx)
|
||||
else:
|
||||
elif self.app_command:
|
||||
ctx.kwargs = await self.app_command._transform_arguments(interaction, interaction.namespace)
|
||||
|
||||
def _ensure_assignment_on_copy(self, other: Self) -> Self:
|
||||
copy = super()._ensure_assignment_on_copy(other)
|
||||
copy.app_command = self.app_command.copy()
|
||||
copy.app_command.wrapped = copy
|
||||
if self.app_command is None:
|
||||
copy.app_command = None
|
||||
else:
|
||||
copy.app_command = self.app_command.copy()
|
||||
copy.app_command.wrapped = copy
|
||||
return copy
|
||||
|
||||
def autocomplete(
|
||||
@ -441,6 +453,9 @@ class HybridCommand(Command[CogT, P, T]):
|
||||
The coroutine passed is not actually a coroutine or
|
||||
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)
|
||||
|
||||
|
||||
@ -473,6 +488,8 @@ class HybridGroup(Group[CogT, P, T]):
|
||||
def __init__(self, *args: Any, fallback: Optional[str] = None, **attrs: Any) -> None:
|
||||
super().__init__(*args, **attrs)
|
||||
self.invoke_without_command = True
|
||||
self.with_app_command: bool = attrs.pop('with_app_command', True)
|
||||
|
||||
parent = None
|
||||
if self.parent is not None:
|
||||
if isinstance(self.parent, HybridGroup):
|
||||
@ -480,28 +497,38 @@ class HybridGroup(Group[CogT, P, T]):
|
||||
else:
|
||||
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)
|
||||
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 = 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
|
||||
# I would love for this to be Optional[app_commands.Group]
|
||||
# However, Python does not have conditional typing so it's very hard to
|
||||
# make this type depend on the with_app_command bool without a lot of needless repetition
|
||||
self.app_command: app_commands.Group = MISSING
|
||||
self.fallback: Optional[str] = fallback
|
||||
|
||||
if fallback is not None:
|
||||
command = HybridAppCommand(self)
|
||||
command.name = fallback
|
||||
self.app_command.add_command(command)
|
||||
if self.with_app_command:
|
||||
guild_ids = attrs.pop('guild_ids', None) or getattr(
|
||||
self.callback, '__discord_app_commands_default_guilds__', None
|
||||
)
|
||||
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
|
||||
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
|
||||
|
||||
@property
|
||||
@ -596,7 +623,9 @@ class HybridGroup(Group[CogT, P, T]):
|
||||
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')
|
||||
|
||||
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
|
||||
|
||||
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]]:
|
||||
cmd = super().remove_command(name)
|
||||
self.app_command.remove_command(name)
|
||||
if self.app_command:
|
||||
self.app_command.remove_command(name)
|
||||
return cmd
|
||||
|
||||
def command(
|
||||
self,
|
||||
name: str = MISSING,
|
||||
*args: Any,
|
||||
with_app_command: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> 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
|
||||
@ -631,7 +662,7 @@ class HybridGroup(Group[CogT, P, T]):
|
||||
|
||||
def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
|
||||
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)
|
||||
return result
|
||||
|
||||
@ -641,6 +672,7 @@ class HybridGroup(Group[CogT, P, T]):
|
||||
self,
|
||||
name: str = MISSING,
|
||||
*args: Any,
|
||||
with_app_command: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> 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
|
||||
@ -654,7 +686,7 @@ class HybridGroup(Group[CogT, P, T]):
|
||||
|
||||
def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
|
||||
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)
|
||||
return result
|
||||
|
||||
@ -663,9 +695,11 @@ class HybridGroup(Group[CogT, P, T]):
|
||||
|
||||
def hybrid_command(
|
||||
name: str = MISSING,
|
||||
*,
|
||||
with_app_command: bool = True,
|
||||
**attrs: Any,
|
||||
) -> 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`
|
||||
and one that is also a :class:`app_commands.Command <discord.app_commands.Command>`.
|
||||
@ -690,7 +724,9 @@ def hybrid_command(
|
||||
name: :class:`str`
|
||||
The name to create the command with. By default this uses the
|
||||
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
|
||||
hybrid command.
|
||||
|
||||
@ -703,24 +739,36 @@ def hybrid_command(
|
||||
def decorator(func: CommandCallback[CogT, ContextT, P, T]):
|
||||
if isinstance(func, 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
|
||||
|
||||
|
||||
def hybrid_group(
|
||||
name: str = MISSING,
|
||||
*,
|
||||
with_app_command: bool = True,
|
||||
**attrs: Any,
|
||||
) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridGroup[CogT, P, T]]:
|
||||
"""A decorator that transforms a function into a :class:`.HybridGroup`.
|
||||
|
||||
This is similar to the :func:`~discord.ext.commands.group` decorator except it creates
|
||||
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]):
|
||||
if isinstance(func, 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user