mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -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.hybrid_command
|
||||
|
||||
.. autofunction:: redbot.core.commands.group
|
||||
|
||||
.. autofunction:: redbot.core.commands.hybrid_group
|
||||
|
||||
.. autoclass:: redbot.core.commands.Cog
|
||||
|
||||
.. automethod:: format_help_for_context
|
||||
@ -21,13 +25,21 @@ extend functionalities used throughout the bot, as outlined below.
|
||||
|
||||
.. automethod:: red_delete_data_for_user
|
||||
|
||||
.. autoclass:: redbot.core.commands.GroupCog
|
||||
|
||||
.. autoclass:: redbot.core.commands.Command
|
||||
:members:
|
||||
:inherited-members: format_help_for_context
|
||||
|
||||
.. autoclass:: redbot.core.commands.HybridCommand
|
||||
:members:
|
||||
|
||||
.. autoclass:: redbot.core.commands.Group
|
||||
:members:
|
||||
|
||||
.. autoclass:: redbot.core.commands.HybridGroup
|
||||
:members:
|
||||
|
||||
.. autoclass:: redbot.core.commands.Context
|
||||
:members:
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import inspect
|
||||
import logging
|
||||
@ -29,6 +30,7 @@ from typing import (
|
||||
MutableMapping,
|
||||
Set,
|
||||
overload,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
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._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"
|
||||
COMMAND_SCOPE = "COMMAND"
|
||||
SHARED_API_TOKENS = "SHARED_API_TOKENS"
|
||||
@ -1796,6 +1805,58 @@ class Red(
|
||||
subcommand.requires.reset()
|
||||
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:
|
||||
"""Clear all permission overrides in a scope.
|
||||
|
||||
|
||||
@ -12,8 +12,13 @@ from .commands import (
|
||||
CogGroupMixin as CogGroupMixin,
|
||||
Command as Command,
|
||||
Group as Group,
|
||||
GroupCog as GroupCog,
|
||||
GroupMixin as GroupMixin,
|
||||
command as command,
|
||||
HybridCommand as HybridCommand,
|
||||
HybridGroup as HybridGroup,
|
||||
hybrid_command as hybrid_command,
|
||||
hybrid_group as hybrid_group,
|
||||
group as group,
|
||||
RedUnhandledAPI as RedUnhandledAPI,
|
||||
RESERVED_COMMAND_NAMES as RESERVED_COMMAND_NAMES,
|
||||
@ -198,4 +203,5 @@ from discord.ext.commands import (
|
||||
Range as Range,
|
||||
RangeError as RangeError,
|
||||
parameter as parameter,
|
||||
HybridCommandError as HybridCommandError,
|
||||
)
|
||||
|
||||
@ -14,11 +14,13 @@ from typing import (
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Dict,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
MutableMapping,
|
||||
TYPE_CHECKING,
|
||||
@ -33,6 +35,9 @@ from discord.ext.commands import (
|
||||
DisabledCommand,
|
||||
command as dpy_command_deco,
|
||||
Command as DPYCommand,
|
||||
GroupCog as DPYGroupCog,
|
||||
HybridCommand as DPYHybridCommand,
|
||||
HybridGroup as DPYHybridGroup,
|
||||
Cog as DPYCog,
|
||||
CogMeta as DPYCogMeta,
|
||||
Group as DPYGroup,
|
||||
@ -43,9 +48,24 @@ from .errors import ConversionFailure
|
||||
from .requires import PermState, PrivilegeLevel, Requires, PermStateAllowedStates
|
||||
from ..i18n import Translator
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_CogT = TypeVar("_CogT", bound="Cog")
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# circular import avoidance
|
||||
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__ = [
|
||||
@ -55,9 +75,12 @@ __all__ = [
|
||||
"CogGroupMixin",
|
||||
"Command",
|
||||
"Group",
|
||||
"GroupCog",
|
||||
"GroupMixin",
|
||||
"command",
|
||||
"group",
|
||||
"hybrid_command",
|
||||
"hybrid_group",
|
||||
"RESERVED_COMMAND_NAMES",
|
||||
"RedUnhandledAPI",
|
||||
]
|
||||
@ -287,9 +310,9 @@ class Command(CogCommandMixin, DPYCommand):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
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.translator = kwargs.pop("i18n", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.parent is None:
|
||||
for name in (self.name, *self.aliases):
|
||||
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__}
|
||||
|
||||
|
||||
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):
|
||||
"""A decorator which transforms an async function into a `Command`.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user