mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
Add support for Hybrid commands in Red (#5681)
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com> Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com> Co-authored-by: Jakub Kuczys <6032823+jack1142@users.noreply.github.com> Co-authored-by: Kreusada <67752638+Kreusada@users.noreply.github.com> Co-authored-by: Candy <28566705+mina9999@users.noreply.github.com> Co-authored-by: Matt Chandra <55866950+matcha19@users.noreply.github.com> Co-authored-by: Lemon Rose <78662983+japandotorg@users.noreply.github.com> Co-authored-by: Honkertonken <94032937+Honkertonken@users.noreply.github.com> Co-authored-by: Flame442 <34169552+Flame442@users.noreply.github.com> Co-authored-by: River <18037011+RheingoldRiver@users.noreply.github.com> Co-authored-by: AAA3A <89632044+AAA3A-AAA3A@users.noreply.github.com> Co-authored-by: Lemon Rose <japandotorg@users.noreply.github.com> Co-authored-by: Julien Mauroy <pro.julien.mauroy@gmail.com> Co-authored-by: TheThomanski <15034759+TheThomanski@users.noreply.github.com>
This commit is contained in:
parent
7ff89302b2
commit
f8b0cc6c6a
@ -11,8 +11,12 @@ extend functionalities used throughout the bot, as outlined below.
|
|||||||
|
|
||||||
.. autofunction:: redbot.core.commands.command
|
.. autofunction:: redbot.core.commands.command
|
||||||
|
|
||||||
|
.. autofunction:: redbot.core.commands.hybrid_command
|
||||||
|
|
||||||
.. autofunction:: redbot.core.commands.group
|
.. autofunction:: redbot.core.commands.group
|
||||||
|
|
||||||
|
.. autofunction:: redbot.core.commands.hybrid_group
|
||||||
|
|
||||||
.. autoclass:: redbot.core.commands.Cog
|
.. autoclass:: redbot.core.commands.Cog
|
||||||
|
|
||||||
.. automethod:: format_help_for_context
|
.. automethod:: format_help_for_context
|
||||||
@ -21,13 +25,21 @@ extend functionalities used throughout the bot, as outlined below.
|
|||||||
|
|
||||||
.. automethod:: red_delete_data_for_user
|
.. automethod:: red_delete_data_for_user
|
||||||
|
|
||||||
|
.. autoclass:: redbot.core.commands.GroupCog
|
||||||
|
|
||||||
.. autoclass:: redbot.core.commands.Command
|
.. autoclass:: redbot.core.commands.Command
|
||||||
:members:
|
:members:
|
||||||
:inherited-members: format_help_for_context
|
:inherited-members: format_help_for_context
|
||||||
|
|
||||||
|
.. autoclass:: redbot.core.commands.HybridCommand
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: redbot.core.commands.Group
|
.. autoclass:: redbot.core.commands.Group
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: redbot.core.commands.HybridGroup
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: redbot.core.commands.Context
|
.. autoclass:: redbot.core.commands.Context
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
@ -29,6 +30,7 @@ from typing import (
|
|||||||
MutableMapping,
|
MutableMapping,
|
||||||
Set,
|
Set,
|
||||||
overload,
|
overload,
|
||||||
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
|
||||||
@ -54,6 +56,13 @@ from .rpc import RPCMixin
|
|||||||
from .utils import can_user_send_messages_in, common_filters, AsyncIter
|
from .utils import can_user_send_messages_in, common_filters, AsyncIter
|
||||||
from .utils._internal_utils import send_to_owners_with_prefix_replaced
|
from .utils._internal_utils import send_to_owners_with_prefix_replaced
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from discord.ext.commands.hybrid import CommandCallback, ContextT, P
|
||||||
|
from discord import app_commands
|
||||||
|
|
||||||
|
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
CUSTOM_GROUPS = "CUSTOM_GROUPS"
|
CUSTOM_GROUPS = "CUSTOM_GROUPS"
|
||||||
COMMAND_SCOPE = "COMMAND"
|
COMMAND_SCOPE = "COMMAND"
|
||||||
SHARED_API_TOKENS = "SHARED_API_TOKENS"
|
SHARED_API_TOKENS = "SHARED_API_TOKENS"
|
||||||
@ -1796,6 +1805,58 @@ class Red(
|
|||||||
subcommand.requires.reset()
|
subcommand.requires.reset()
|
||||||
return command
|
return command
|
||||||
|
|
||||||
|
def hybrid_command(
|
||||||
|
self,
|
||||||
|
name: Union[str, app_commands.locale_str] = discord.utils.MISSING,
|
||||||
|
with_app_command: bool = True,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Callable[[CommandCallback[Any, ContextT, P, _T]], commands.HybridCommand[Any, P, _T]]:
|
||||||
|
"""A shortcut decorator that invokes :func:`~redbot.core.commands.hybrid_command` and adds it to
|
||||||
|
the internal command list via :meth:`add_command`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Callable[..., :class:`HybridCommand`]
|
||||||
|
A decorator that converts the provided method into a Command, adds it to the bot, then returns it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: CommandCallback[Any, ContextT, P, _T]):
|
||||||
|
kwargs.setdefault("parent", self)
|
||||||
|
result = commands.hybrid_command(
|
||||||
|
name=name, *args, with_app_command=with_app_command, **kwargs
|
||||||
|
)(func)
|
||||||
|
self.add_command(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def hybrid_group(
|
||||||
|
self,
|
||||||
|
name: Union[str, app_commands.locale_str] = discord.utils.MISSING,
|
||||||
|
with_app_command: bool = True,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> Callable[[CommandCallback[Any, ContextT, P, _T]], commands.HybridGroup[Any, P, _T]]:
|
||||||
|
"""A shortcut decorator that invokes :func:`~redbot.core.commands.hybrid_group` and adds it to
|
||||||
|
the internal command list via :meth:`add_command`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Callable[..., :class:`HybridGroup`]
|
||||||
|
A decorator that converts the provided method into a Group, adds it to the bot, then returns it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: CommandCallback[Any, ContextT, P, _T]):
|
||||||
|
kwargs.setdefault("parent", self)
|
||||||
|
result = commands.hybrid_group(
|
||||||
|
name=name, *args, with_app_command=with_app_command, **kwargs
|
||||||
|
)(func)
|
||||||
|
self.add_command(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
def clear_permission_rules(self, guild_id: Optional[int], **kwargs) -> None:
|
def clear_permission_rules(self, guild_id: Optional[int], **kwargs) -> None:
|
||||||
"""Clear all permission overrides in a scope.
|
"""Clear all permission overrides in a scope.
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,13 @@ from .commands import (
|
|||||||
CogGroupMixin as CogGroupMixin,
|
CogGroupMixin as CogGroupMixin,
|
||||||
Command as Command,
|
Command as Command,
|
||||||
Group as Group,
|
Group as Group,
|
||||||
|
GroupCog as GroupCog,
|
||||||
GroupMixin as GroupMixin,
|
GroupMixin as GroupMixin,
|
||||||
command as command,
|
command as command,
|
||||||
|
HybridCommand as HybridCommand,
|
||||||
|
HybridGroup as HybridGroup,
|
||||||
|
hybrid_command as hybrid_command,
|
||||||
|
hybrid_group as hybrid_group,
|
||||||
group as group,
|
group as group,
|
||||||
RedUnhandledAPI as RedUnhandledAPI,
|
RedUnhandledAPI as RedUnhandledAPI,
|
||||||
RESERVED_COMMAND_NAMES as RESERVED_COMMAND_NAMES,
|
RESERVED_COMMAND_NAMES as RESERVED_COMMAND_NAMES,
|
||||||
@ -198,4 +203,5 @@ from discord.ext.commands import (
|
|||||||
Range as Range,
|
Range as Range,
|
||||||
RangeError as RangeError,
|
RangeError as RangeError,
|
||||||
parameter as parameter,
|
parameter as parameter,
|
||||||
|
HybridCommandError as HybridCommandError,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -14,11 +14,13 @@ from typing import (
|
|||||||
Any,
|
Any,
|
||||||
Awaitable,
|
Awaitable,
|
||||||
Callable,
|
Callable,
|
||||||
|
ClassVar,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
Literal,
|
Literal,
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
MutableMapping,
|
MutableMapping,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
@ -33,6 +35,9 @@ from discord.ext.commands import (
|
|||||||
DisabledCommand,
|
DisabledCommand,
|
||||||
command as dpy_command_deco,
|
command as dpy_command_deco,
|
||||||
Command as DPYCommand,
|
Command as DPYCommand,
|
||||||
|
GroupCog as DPYGroupCog,
|
||||||
|
HybridCommand as DPYHybridCommand,
|
||||||
|
HybridGroup as DPYHybridGroup,
|
||||||
Cog as DPYCog,
|
Cog as DPYCog,
|
||||||
CogMeta as DPYCogMeta,
|
CogMeta as DPYCogMeta,
|
||||||
Group as DPYGroup,
|
Group as DPYGroup,
|
||||||
@ -43,9 +48,24 @@ from .errors import ConversionFailure
|
|||||||
from .requires import PermState, PrivilegeLevel, Requires, PermStateAllowedStates
|
from .requires import PermState, PrivilegeLevel, Requires, PermStateAllowedStates
|
||||||
from ..i18n import Translator
|
from ..i18n import Translator
|
||||||
|
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
_CogT = TypeVar("_CogT", bound="Cog")
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# circular import avoidance
|
# circular import avoidance
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from typing_extensions import ParamSpec, Concatenate
|
||||||
|
from discord.ext.commands._types import ContextT, Coro
|
||||||
|
|
||||||
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
|
CommandCallback = Union[
|
||||||
|
Callable[Concatenate[_CogT, ContextT, _P], Coro[_T]],
|
||||||
|
Callable[Concatenate[ContextT, _P], Coro[_T]],
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
_P = TypeVar("_P")
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -55,9 +75,12 @@ __all__ = [
|
|||||||
"CogGroupMixin",
|
"CogGroupMixin",
|
||||||
"Command",
|
"Command",
|
||||||
"Group",
|
"Group",
|
||||||
|
"GroupCog",
|
||||||
"GroupMixin",
|
"GroupMixin",
|
||||||
"command",
|
"command",
|
||||||
"group",
|
"group",
|
||||||
|
"hybrid_command",
|
||||||
|
"hybrid_group",
|
||||||
"RESERVED_COMMAND_NAMES",
|
"RESERVED_COMMAND_NAMES",
|
||||||
"RedUnhandledAPI",
|
"RedUnhandledAPI",
|
||||||
]
|
]
|
||||||
@ -287,9 +310,9 @@ class Command(CogCommandMixin, DPYCommand):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.ignore_optional_for_conversion = kwargs.pop("ignore_optional_for_conversion", False)
|
self.ignore_optional_for_conversion = kwargs.pop("ignore_optional_for_conversion", False)
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self._help_override = kwargs.pop("help_override", None)
|
self._help_override = kwargs.pop("help_override", None)
|
||||||
self.translator = kwargs.pop("i18n", None)
|
self.translator = kwargs.pop("i18n", None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
if self.parent is None:
|
if self.parent is None:
|
||||||
for name in (self.name, *self.aliases):
|
for name in (self.name, *self.aliases):
|
||||||
if name in RESERVED_COMMAND_NAMES:
|
if name in RESERVED_COMMAND_NAMES:
|
||||||
@ -984,6 +1007,131 @@ class Cog(CogMixin, DPYCog, metaclass=DPYCogMeta):
|
|||||||
return {cmd.name: cmd for cmd in self.__cog_commands__}
|
return {cmd.name: cmd for cmd in self.__cog_commands__}
|
||||||
|
|
||||||
|
|
||||||
|
class GroupCog(Cog, DPYGroupCog):
|
||||||
|
"""
|
||||||
|
Red's Cog base class with app commands group as the base.
|
||||||
|
|
||||||
|
This class inherits from `Cog` and `discord.ext.commands.GroupCog`
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class HybridCommand(Command, DPYHybridCommand[_CogT, _P, _T]):
|
||||||
|
"""HybridCommand class for Red.
|
||||||
|
|
||||||
|
This should not be created directly, and instead via the decorator.
|
||||||
|
|
||||||
|
This class inherits from `Command` and `discord.ext.commands.HybridCommand`.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This class is not intended to be subclassed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class HybridGroup(Group, DPYHybridGroup[_CogT, _P, _T]):
|
||||||
|
"""HybridGroup command class for Red.
|
||||||
|
|
||||||
|
This should not be created directly, and instead via the decorator.
|
||||||
|
|
||||||
|
This class inherits from `Group` and `discord.ext.commands.HybridGroup`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Red's HybridGroups differ from `discord.ext.commands.HybridGroup`
|
||||||
|
by setting `discord.ext.commands.Group.invoke_without_command` to be `False` by default.
|
||||||
|
If `discord.ext.commands.HybridGroup.fallback` is provided then
|
||||||
|
`discord.ext.commands.Group.invoke_without_command` is
|
||||||
|
set to `True`.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This class is not intended to be subclassed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, **kwargs: Any):
|
||||||
|
fallback = "fallback" in kwargs and kwargs["fallback"] is not None
|
||||||
|
invoke_without_command = kwargs.pop("invoke_without_command", False) or fallback
|
||||||
|
kwargs["invoke_without_command"] = invoke_without_command
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.invoke_without_command = invoke_without_command
|
||||||
|
|
||||||
|
@property
|
||||||
|
def callback(self):
|
||||||
|
return self._callback
|
||||||
|
|
||||||
|
@callback.setter
|
||||||
|
def callback(self, function):
|
||||||
|
# Below should be mostly the same as discord.py
|
||||||
|
super(__class__, __class__).callback.__set__(self, function)
|
||||||
|
|
||||||
|
if not self.invoke_without_command and self.params:
|
||||||
|
raise TypeError(
|
||||||
|
"You cannot have a group command with callbacks and `invoke_without_command` set to False."
|
||||||
|
)
|
||||||
|
|
||||||
|
def command(self, name: str = discord.utils.MISSING, *args: Any, **kwargs: Any):
|
||||||
|
def decorator(func):
|
||||||
|
kwargs.setdefault("parent", self)
|
||||||
|
result = hybrid_command(name=name, *args, **kwargs)(func)
|
||||||
|
self.add_command(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def group(
|
||||||
|
self,
|
||||||
|
name: str = discord.utils.MISSING,
|
||||||
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
):
|
||||||
|
def decorator(func):
|
||||||
|
kwargs.setdefault("parent", self)
|
||||||
|
result = hybrid_group(name=name, *args, **kwargs)(func)
|
||||||
|
self.add_command(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def hybrid_command(
|
||||||
|
name: Union[str, discord.app_commands.locale_str] = discord.utils.MISSING,
|
||||||
|
*,
|
||||||
|
with_app_command: bool = True,
|
||||||
|
**attrs: Any,
|
||||||
|
) -> Callable[[CommandCallback[_CogT, ContextT, _P, _T]], HybridCommand[_CogT, _P, _T]]:
|
||||||
|
"""A decorator which transforms an async function into a `HybridCommand`.
|
||||||
|
|
||||||
|
Same interface as `discord.ext.commands.hybrid_command`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: CommandCallback[_CogT, ContextT, _P, _T]) -> HybridCommand[_CogT, _P, _T]:
|
||||||
|
if isinstance(func, Command):
|
||||||
|
raise TypeError("callback is already a command.")
|
||||||
|
attrs["help_override"] = attrs.pop("help", None)
|
||||||
|
return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def hybrid_group(
|
||||||
|
name: Union[str, discord.app_commands.locale_str] = discord.utils.MISSING,
|
||||||
|
*,
|
||||||
|
with_app_command: bool = True,
|
||||||
|
**attrs: Any,
|
||||||
|
) -> Callable[[CommandCallback[_CogT, ContextT, _P, _T]], HybridGroup[_CogT, _P, _T]]:
|
||||||
|
"""A decorator which transforms an async function into a `HybridGroup`.
|
||||||
|
|
||||||
|
Same interface as `discord.ext.commands.hybrid_group`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: CommandCallback[_CogT, ContextT, _P, _T]):
|
||||||
|
if isinstance(func, Command):
|
||||||
|
raise TypeError("callback is already a command.")
|
||||||
|
attrs["help_override"] = attrs.pop("help", None)
|
||||||
|
return HybridGroup(func, name=name, with_app_command=with_app_command, **attrs)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def command(name=None, cls=Command, **attrs):
|
def command(name=None, cls=Command, **attrs):
|
||||||
"""A decorator which transforms an async function into a `Command`.
|
"""A decorator which transforms an async function into a `Command`.
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user