aminoacid.util.commands

  1from __future__ import annotations
  2from functools import wraps
  3
  4from inspect import signature
  5from time import time
  6from typing import (
  7    TYPE_CHECKING,
  8    Any,
  9    Callable,
 10    Coroutine,
 11    List,
 12    Literal,
 13    Optional,
 14    TypeVar,
 15    Union,
 16)
 17
 18from .events import empty_cb
 19from ..exceptions import AminoBaseException, CheckFailed, CommandNotFound
 20
 21if TYPE_CHECKING:
 22    from ..abc import Context
 23
 24T = TypeVar("T")
 25
 26
 27class UserCommand:
 28    """Command defined by User"""
 29
 30    def __init__(
 31        self,
 32        func: Callable[..., Coroutine[Any, Any, T]],
 33        command_name: str = "",
 34        check: Optional[Callable[[Context], bool]] = lambda ctx: True,
 35        check_any: Optional[List[Callable[[Context], bool]]] = list(),
 36        cooldown: Optional[int] = 0,
 37    ) -> None:
 38        """Initialises a new UserCommand with a given function to call and a given name
 39
 40        Parameters
 41        ----------
 42        func : Callable[..., Coroutine[Any, Any, T]]
 43            The function that is called when the command is executed
 44        command_name : str, optional
 45            Name of the command, by default the function name
 46        check : Optional[Callable[[Context], bool]], optional
 47            Function which is called to see if the command may be called, by default always returns True
 48        check_any : Optional[List[Callable[[Context], bool]]], optional
 49            List of checks, command will execute if any of them return True, by default []
 50        cooldown : Optional[int]
 51            Time to sleep before another instance of this command can be executed by the same user
 52        """
 53
 54        self.callback = func
 55        self.check = check
 56        self.check_any = check_any
 57        self.cooldown = cooldown
 58        self.name = command_name or func.__name__
 59
 60        self.handler: Callable[
 61            [AminoBaseException, Context], Coroutine[Any, Any, T]
 62        ] = empty_cb
 63        """Handler containing a function to handle exceptions raised
 64        
 65        Parameters
 66        ----------
 67        exc : AminoBaseException
 68            The exception raised by the command
 69        ctx : Context
 70            The context of the command
 71        """
 72        self.calls = {}
 73
 74    def error(self):
 75        """Register a function to be the error handler of this command, calls `self.register_handler()`"""
 76
 77        def wrap(f: Callable[[AminoBaseException, Context], Coroutine[Any, Any, T]]):
 78            @wraps(f)
 79            def func(self: UserCommand):
 80                self.register_handler(f)
 81
 82            return func(self)
 83
 84        return wrap
 85
 86    def register_handler(
 87        self, func: Callable[[AminoBaseException, Context], Coroutine[Any, Any, T]]
 88    ):
 89        """Register a given function as a handler, this is used by the error decorator
 90
 91        Parameters
 92        ----------
 93        func : Callable[[AminoBaseException, Context]]
 94            the function to register as the handler, this is called with the exception and the command context
 95        """
 96        self.handler = func
 97
 98    def get_signature(self) -> str:
 99        """Returns the signature of the Command
100
101        Returns
102        -------
103        str
104            Signature of the command
105        """
106
107        params = signature(self.callback).parameters
108        result = list()
109        for name, param in params.items():
110            optional = bool(param.default)
111            annotation: Any = param.annotation
112            origin = getattr(annotation, "__origin__", None)
113            if origin is Union:
114                none_cls = type(None)
115                union_args = annotation.__args__
116                optional = union_args[-1] is none_cls
117                if len(union_args) == 2 and optional:
118                    annotation = union_args[0]
119                    origin = getattr(annotation, "__origin__", None)
120            if origin is Literal:
121                name = "|".join(
122                    f'"{v}"' if isinstance(v, str) else str(v)
123                    for v in annotation.__args__
124                )
125            if optional:
126                result.append(f"[{name}]")
127            elif param.kind == param.VAR_POSITIONAL:
128                result.append(f"[{name}...]")
129            else:
130                result.append(f"<{name}>")
131        return " ".join(result)
132
133    def __call__(
134        self, context: Context, *args: Any, **kwargs: Any
135    ) -> Coroutine[Any, Any, T]:
136        """Allow the Command to be executed by calling the UserCommand instance.
137        like `await UserCommand()`
138
139        Parameters
140        ----------
141        context : Context
142            Context to pass to the callback
143
144        Returns
145        -------
146        T
147            returns the Callback return value
148        """
149
150        if not any(
151            check(context) for check in self.check_any or [lambda _: True]
152        ) or not self.check(context):
153            return context.client.logger.exception(CheckFailed(context))
154        if (current_time := int(time())) <= self.calls.get(
155            context.author.id, 0
156        ) + self.cooldown:
157            return
158        self.calls[context.author.id] = current_time
159        context.client.logger.warning((self.calls, self.cooldown))
160        return self.callback(context, *args, **kwargs)
161
162    def __str__(self) -> str:
163        return self.get_signature()
164
165    def __repr__(self) -> str:
166        return str(self)
167
168
169class HelpCommand(UserCommand):
170    def __init__(self) -> None:
171        super().__init__(self.help, "help")
172
173    async def help(self, ctx: Context, command: str = ""):
174        if not command:
175            await ctx.send(
176                f"\n".join(
177                    [
178                        ctx.client.prefix + name
179                        for name, _ in ctx.client.__command_map__.items()
180                    ]
181                )
182            )
183        if command not in ctx.client.__command_map__:
184            return ctx.client.logger.exception(CommandNotFound(ctx))
185        await ctx.send(ctx.client.__command_map__[command].get_signature())
class UserCommand:
 28class UserCommand:
 29    """Command defined by User"""
 30
 31    def __init__(
 32        self,
 33        func: Callable[..., Coroutine[Any, Any, T]],
 34        command_name: str = "",
 35        check: Optional[Callable[[Context], bool]] = lambda ctx: True,
 36        check_any: Optional[List[Callable[[Context], bool]]] = list(),
 37        cooldown: Optional[int] = 0,
 38    ) -> None:
 39        """Initialises a new UserCommand with a given function to call and a given name
 40
 41        Parameters
 42        ----------
 43        func : Callable[..., Coroutine[Any, Any, T]]
 44            The function that is called when the command is executed
 45        command_name : str, optional
 46            Name of the command, by default the function name
 47        check : Optional[Callable[[Context], bool]], optional
 48            Function which is called to see if the command may be called, by default always returns True
 49        check_any : Optional[List[Callable[[Context], bool]]], optional
 50            List of checks, command will execute if any of them return True, by default []
 51        cooldown : Optional[int]
 52            Time to sleep before another instance of this command can be executed by the same user
 53        """
 54
 55        self.callback = func
 56        self.check = check
 57        self.check_any = check_any
 58        self.cooldown = cooldown
 59        self.name = command_name or func.__name__
 60
 61        self.handler: Callable[
 62            [AminoBaseException, Context], Coroutine[Any, Any, T]
 63        ] = empty_cb
 64        """Handler containing a function to handle exceptions raised
 65        
 66        Parameters
 67        ----------
 68        exc : AminoBaseException
 69            The exception raised by the command
 70        ctx : Context
 71            The context of the command
 72        """
 73        self.calls = {}
 74
 75    def error(self):
 76        """Register a function to be the error handler of this command, calls `self.register_handler()`"""
 77
 78        def wrap(f: Callable[[AminoBaseException, Context], Coroutine[Any, Any, T]]):
 79            @wraps(f)
 80            def func(self: UserCommand):
 81                self.register_handler(f)
 82
 83            return func(self)
 84
 85        return wrap
 86
 87    def register_handler(
 88        self, func: Callable[[AminoBaseException, Context], Coroutine[Any, Any, T]]
 89    ):
 90        """Register a given function as a handler, this is used by the error decorator
 91
 92        Parameters
 93        ----------
 94        func : Callable[[AminoBaseException, Context]]
 95            the function to register as the handler, this is called with the exception and the command context
 96        """
 97        self.handler = func
 98
 99    def get_signature(self) -> str:
100        """Returns the signature of the Command
101
102        Returns
103        -------
104        str
105            Signature of the command
106        """
107
108        params = signature(self.callback).parameters
109        result = list()
110        for name, param in params.items():
111            optional = bool(param.default)
112            annotation: Any = param.annotation
113            origin = getattr(annotation, "__origin__", None)
114            if origin is Union:
115                none_cls = type(None)
116                union_args = annotation.__args__
117                optional = union_args[-1] is none_cls
118                if len(union_args) == 2 and optional:
119                    annotation = union_args[0]
120                    origin = getattr(annotation, "__origin__", None)
121            if origin is Literal:
122                name = "|".join(
123                    f'"{v}"' if isinstance(v, str) else str(v)
124                    for v in annotation.__args__
125                )
126            if optional:
127                result.append(f"[{name}]")
128            elif param.kind == param.VAR_POSITIONAL:
129                result.append(f"[{name}...]")
130            else:
131                result.append(f"<{name}>")
132        return " ".join(result)
133
134    def __call__(
135        self, context: Context, *args: Any, **kwargs: Any
136    ) -> Coroutine[Any, Any, T]:
137        """Allow the Command to be executed by calling the UserCommand instance.
138        like `await UserCommand()`
139
140        Parameters
141        ----------
142        context : Context
143            Context to pass to the callback
144
145        Returns
146        -------
147        T
148            returns the Callback return value
149        """
150
151        if not any(
152            check(context) for check in self.check_any or [lambda _: True]
153        ) or not self.check(context):
154            return context.client.logger.exception(CheckFailed(context))
155        if (current_time := int(time())) <= self.calls.get(
156            context.author.id, 0
157        ) + self.cooldown:
158            return
159        self.calls[context.author.id] = current_time
160        context.client.logger.warning((self.calls, self.cooldown))
161        return self.callback(context, *args, **kwargs)
162
163    def __str__(self) -> str:
164        return self.get_signature()
165
166    def __repr__(self) -> str:
167        return str(self)

Command defined by User

UserCommand( func: Callable[..., Coroutine[Any, Any, ~T]], command_name: str = '', check: Optional[Callable[[aminoacid.abc.Context], bool]] = <function UserCommand.<lambda>>, check_any: Optional[List[Callable[[aminoacid.abc.Context], bool]]] = [], cooldown: Optional[int] = 0)
31    def __init__(
32        self,
33        func: Callable[..., Coroutine[Any, Any, T]],
34        command_name: str = "",
35        check: Optional[Callable[[Context], bool]] = lambda ctx: True,
36        check_any: Optional[List[Callable[[Context], bool]]] = list(),
37        cooldown: Optional[int] = 0,
38    ) -> None:
39        """Initialises a new UserCommand with a given function to call and a given name
40
41        Parameters
42        ----------
43        func : Callable[..., Coroutine[Any, Any, T]]
44            The function that is called when the command is executed
45        command_name : str, optional
46            Name of the command, by default the function name
47        check : Optional[Callable[[Context], bool]], optional
48            Function which is called to see if the command may be called, by default always returns True
49        check_any : Optional[List[Callable[[Context], bool]]], optional
50            List of checks, command will execute if any of them return True, by default []
51        cooldown : Optional[int]
52            Time to sleep before another instance of this command can be executed by the same user
53        """
54
55        self.callback = func
56        self.check = check
57        self.check_any = check_any
58        self.cooldown = cooldown
59        self.name = command_name or func.__name__
60
61        self.handler: Callable[
62            [AminoBaseException, Context], Coroutine[Any, Any, T]
63        ] = empty_cb
64        """Handler containing a function to handle exceptions raised
65        
66        Parameters
67        ----------
68        exc : AminoBaseException
69            The exception raised by the command
70        ctx : Context
71            The context of the command
72        """
73        self.calls = {}

Initialises a new UserCommand with a given function to call and a given name

Parameters
  • func (Callable[..., Coroutine[Any, Any, T]]): The function that is called when the command is executed
  • command_name (str, optional): Name of the command, by default the function name
  • check (Optional[Callable[[Context], bool]], optional): Function which is called to see if the command may be called, by default always returns True
  • check_any (Optional[List[Callable[[Context], bool]]], optional): List of checks, command will execute if any of them return True, by default []
  • cooldown (Optional[int]): Time to sleep before another instance of this command can be executed by the same user
handler: Callable[[aminoacid.exceptions.AminoBaseException, aminoacid.abc.Context], Coroutine[Any, Any, ~T]]

Handler containing a function to handle exceptions raised

Parameters
  • exc (AminoBaseException): The exception raised by the command
  • ctx (Context): The context of the command
def error(self):
75    def error(self):
76        """Register a function to be the error handler of this command, calls `self.register_handler()`"""
77
78        def wrap(f: Callable[[AminoBaseException, Context], Coroutine[Any, Any, T]]):
79            @wraps(f)
80            def func(self: UserCommand):
81                self.register_handler(f)
82
83            return func(self)
84
85        return wrap

Register a function to be the error handler of this command, calls self.register_handler()

def register_handler( self, func: Callable[[aminoacid.exceptions.AminoBaseException, aminoacid.abc.Context], Coroutine[Any, Any, ~T]]):
87    def register_handler(
88        self, func: Callable[[AminoBaseException, Context], Coroutine[Any, Any, T]]
89    ):
90        """Register a given function as a handler, this is used by the error decorator
91
92        Parameters
93        ----------
94        func : Callable[[AminoBaseException, Context]]
95            the function to register as the handler, this is called with the exception and the command context
96        """
97        self.handler = func

Register a given function as a handler, this is used by the error decorator

Parameters
  • func (Callable[[AminoBaseException, Context]]): the function to register as the handler, this is called with the exception and the command context
def get_signature(self) -> str:
 99    def get_signature(self) -> str:
100        """Returns the signature of the Command
101
102        Returns
103        -------
104        str
105            Signature of the command
106        """
107
108        params = signature(self.callback).parameters
109        result = list()
110        for name, param in params.items():
111            optional = bool(param.default)
112            annotation: Any = param.annotation
113            origin = getattr(annotation, "__origin__", None)
114            if origin is Union:
115                none_cls = type(None)
116                union_args = annotation.__args__
117                optional = union_args[-1] is none_cls
118                if len(union_args) == 2 and optional:
119                    annotation = union_args[0]
120                    origin = getattr(annotation, "__origin__", None)
121            if origin is Literal:
122                name = "|".join(
123                    f'"{v}"' if isinstance(v, str) else str(v)
124                    for v in annotation.__args__
125                )
126            if optional:
127                result.append(f"[{name}]")
128            elif param.kind == param.VAR_POSITIONAL:
129                result.append(f"[{name}...]")
130            else:
131                result.append(f"<{name}>")
132        return " ".join(result)

Returns the signature of the Command

Returns
  • str: Signature of the command
class HelpCommand(UserCommand):
170class HelpCommand(UserCommand):
171    def __init__(self) -> None:
172        super().__init__(self.help, "help")
173
174    async def help(self, ctx: Context, command: str = ""):
175        if not command:
176            await ctx.send(
177                f"\n".join(
178                    [
179                        ctx.client.prefix + name
180                        for name, _ in ctx.client.__command_map__.items()
181                    ]
182                )
183            )
184        if command not in ctx.client.__command_map__:
185            return ctx.client.logger.exception(CommandNotFound(ctx))
186        await ctx.send(ctx.client.__command_map__[command].get_signature())

Command defined by User

HelpCommand()
171    def __init__(self) -> None:
172        super().__init__(self.help, "help")

Initialises a new UserCommand with a given function to call and a given name

Parameters
  • func (Callable[..., Coroutine[Any, Any, T]]): The function that is called when the command is executed
  • command_name (str, optional): Name of the command, by default the function name
  • check (Optional[Callable[[Context], bool]], optional): Function which is called to see if the command may be called, by default always returns True
  • check_any (Optional[List[Callable[[Context], bool]]], optional): List of checks, command will execute if any of them return True, by default []
  • cooldown (Optional[int]): Time to sleep before another instance of this command can be executed by the same user
async def help(self, ctx: aminoacid.abc.Context, command: str = ''):
174    async def help(self, ctx: Context, command: str = ""):
175        if not command:
176            await ctx.send(
177                f"\n".join(
178                    [
179                        ctx.client.prefix + name
180                        for name, _ in ctx.client.__command_map__.items()
181                    ]
182                )
183            )
184        if command not in ctx.client.__command_map__:
185            return ctx.client.logger.exception(CommandNotFound(ctx))
186        await ctx.send(ctx.client.__command_map__[command].get_signature())