diff --git a/nonebot/__init__.py b/nonebot/__init__.py index bd0ee80b..6f93bdae 100644 --- a/nonebot/__init__.py +++ b/nonebot/__init__.py @@ -213,5 +213,5 @@ async def _start_scheduler(): from nonebot.plugin import on_message, on_notice, on_request, on_metaevent -from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex +from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex, CommandGroup from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins, get_loaded_plugins diff --git a/nonebot/matcher.py b/nonebot/matcher.py index fcd70832..76465b0b 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -278,3 +278,172 @@ class Matcher(metaclass=MatcherMeta): logger.info(f"Matcher {self} running complete") current_bot.reset(b_t) current_event.reset(e_t) + + +class MatcherGroup: + + def __init__(self, + type_: str = "", + rule: Rule = Rule(), + permission: Permission = Permission(), + handlers: Optional[list] = None, + temp: bool = False, + priority: int = 1, + block: bool = False, + *, + module: Optional[str] = None, + default_state: Optional[dict] = None, + expire_time: Optional[datetime] = None): + self.matchers: List[Type[Matcher]] = [] + + self.type = type_ + self.rule = rule + self.permission = permission + self.handlers = handlers + self.temp = temp + self.priority = priority + self.block = block + self.module = module + self.default_state = default_state + self.expire_time = expire_time + + def __repr__(self) -> str: + return ( + f"") + + def __str__(self) -> str: + return self.__repr__() + + def new(self, + type_: str = "", + rule: Rule = Rule(), + permission: Permission = Permission(), + handlers: Optional[list] = None, + temp: bool = False, + priority: int = 1, + block: bool = False, + *, + module: Optional[str] = None, + default_state: Optional[dict] = None, + expire_time: Optional[datetime] = None) -> Type[Matcher]: + matcher = Matcher.new(type_=type_ or self.type, + rule=self.rule & rule, + permission=permission or self.permission, + handlers=handlers or self.handlers, + temp=temp or self.temp, + priority=priority or self.priority, + block=block or self.block, + module=module or self.module, + default_state=default_state or self.default_state, + expire_time=expire_time or self.expire_time) + self.matchers.append(matcher) + return matcher + + def args_parser(self, func: ArgsParser) -> ArgsParser: + for matcher in self.matchers: + matcher.args_parser(func) + return func + + def handle(self) -> Callable[[Handler], Handler]: + """直接处理消息事件""" + + def _decorator(func: Handler) -> Handler: + self.handlers.append(func) + return func + + return _decorator + + def receive(self) -> Callable[[Handler], Handler]: + """接收一条新消息并处理""" + + async def _receive(bot: Bot, event: Event, state: dict) -> NoReturn: + raise PausedException + + if self.handlers: + # 已有前置handlers则接受一条新的消息,否则视为接收初始消息 + self.handlers.append(_receive) + + def _decorator(func: Handler) -> Handler: + if not self.handlers or self.handlers[-1] is not func: + self.handlers.append(func) + + return func + + return _decorator + + def got( + self, + key: str, + prompt: Optional[str] = None, + args_parser: Optional[ArgsParser] = None + ) -> Callable[[Handler], Handler]: + + async def _key_getter(bot: Bot, event: Event, state: dict): + state["_current_key"] = key + if key not in state: + if prompt: + await bot.send(event=event, message=prompt) + raise PausedException + else: + state["_skip_key"] = True + + async def _key_parser(bot: Bot, event: Event, state: dict): + if key in state and state.get("_skip_key"): + del state["_skip_key"] + return + parser = args_parser or self._default_parser + if parser: + await parser(bot, event, state) + else: + state[state["_current_key"]] = str(event.message) + + self.handlers.append(_key_getter) + self.handlers.append(_key_parser) + + def _decorator(func: Handler) -> Handler: + if not hasattr(self.handlers[-1], "__wrapped__"): + parser = self.handlers.pop() + + @wraps(func) + async def wrapper(bot: Bot, event: Event, state: dict): + await parser(bot, event, state) + await func(bot, event, state) + if "_current_key" in state: + del state["_current_key"] + + self.handlers.append(wrapper) + + return func + + return _decorator + + async def finish( + self, + prompt: Optional[Union[str, Message, + MessageSegment]] = None) -> NoReturn: + bot: Bot = current_bot.get() + event: Event = current_event.get() + if prompt: + await bot.send(event=event, message=prompt) + raise FinishedException + + async def pause( + self, + prompt: Optional[Union[str, Message, + MessageSegment]] = None) -> NoReturn: + bot: Bot = current_bot.get() + event: Event = current_event.get() + if prompt: + await bot.send(event=event, message=prompt) + raise PausedException + + async def reject( + self, + prompt: Optional[Union[str, Message, + MessageSegment]] = None) -> NoReturn: + bot: Bot = current_bot.get() + event: Event = current_event.get() + if prompt: + await bot.send(event=event, message=prompt) + raise RejectedException diff --git a/nonebot/permission.py b/nonebot/permission.py index 1f43cf38..e194f453 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -14,7 +14,7 @@ import asyncio from nonebot.utils import run_sync -from nonebot.typing import Bot, Event, Union, NoReturn, Callable, Awaitable, PermissionChecker +from nonebot.typing import Bot, Event, Union, NoReturn, Optional, Callable, Awaitable, PermissionChecker class Permission: @@ -53,10 +53,13 @@ class Permission: def __and__(self, other) -> NoReturn: raise RuntimeError("And operation between Permissions is not allowed.") - def __or__(self, other: Union["Permission", - PermissionChecker]) -> "Permission": + def __or__( + self, other: Optional[Union["Permission", + PermissionChecker]]) -> "Permission": checkers = self.checkers.copy() - if isinstance(other, Permission): + if other is None: + return self + elif isinstance(other, Permission): checkers |= other.checkers elif asyncio.iscoroutinefunction(other): checkers.add(other) # type: ignore diff --git a/nonebot/plugin.py b/nonebot/plugin.py index ba526fcf..4936dc8e 100644 --- a/nonebot/plugin.py +++ b/nonebot/plugin.py @@ -9,9 +9,9 @@ from dataclasses import dataclass from importlib._bootstrap import _load from nonebot.log import logger -from nonebot.matcher import Matcher from nonebot.permission import Permission from nonebot.typing import Handler, RuleChecker +from nonebot.matcher import Matcher, MatcherGroup from nonebot.rule import Rule, startswith, endswith, command, regex from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType @@ -27,8 +27,8 @@ class Plugin(object): matcher: Set[Type[Matcher]] -def on(rule: Union[Rule, RuleChecker] = Rule(), - permission: Permission = Permission(), +def on(rule: Optional[Union[Rule, RuleChecker]] = None, + permission: Optional[Permission] = None, *, handlers: Optional[List[Handler]] = None, temp: bool = False, @@ -37,7 +37,7 @@ def on(rule: Union[Rule, RuleChecker] = Rule(), state: Optional[dict] = None) -> Type[Matcher]: matcher = Matcher.new("", Rule() & rule, - permission, + permission or Permission(), temp=temp, priority=priority, block=block, @@ -47,7 +47,7 @@ def on(rule: Union[Rule, RuleChecker] = Rule(), return matcher -def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(), +def on_metaevent(rule: Optional[Union[Rule, RuleChecker]] = None, *, handlers: Optional[List[Handler]] = None, temp: bool = False, @@ -66,8 +66,8 @@ def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(), return matcher -def on_message(rule: Union[Rule, RuleChecker] = Rule(), - permission: Permission = Permission(), +def on_message(rule: Optional[Union[Rule, RuleChecker]] = None, + permission: Optional[Permission] = None, *, handlers: Optional[List[Handler]] = None, temp: bool = False, @@ -76,7 +76,7 @@ def on_message(rule: Union[Rule, RuleChecker] = Rule(), state: Optional[dict] = None) -> Type[Matcher]: matcher = Matcher.new("message", Rule() & rule, - permission, + permission or Permission(), temp=temp, priority=priority, block=block, @@ -86,7 +86,7 @@ def on_message(rule: Union[Rule, RuleChecker] = Rule(), return matcher -def on_notice(rule: Union[Rule, RuleChecker] = Rule(), +def on_notice(rule: Optional[Union[Rule, RuleChecker]] = None, *, handlers: Optional[List[Handler]] = None, temp: bool = False, @@ -105,7 +105,7 @@ def on_notice(rule: Union[Rule, RuleChecker] = Rule(), return matcher -def on_request(rule: Union[Rule, RuleChecker] = Rule(), +def on_request(rule: Optional[Union[Rule, RuleChecker]] = None, *, handlers: Optional[List[Handler]] = None, temp: bool = False, @@ -125,27 +125,23 @@ def on_request(rule: Union[Rule, RuleChecker] = Rule(), def on_startswith(msg: str, - rule: Optional[Union[Rule, RuleChecker]] = None, - permission: Permission = Permission(), + rule: Optional[Optional[Union[Rule, RuleChecker]]] = None, **kwargs) -> Type[Matcher]: - return on_message(startswith(msg) & - rule, permission, **kwargs) if rule else on_message( - startswith(msg), permission, **kwargs) + return on_message(startswith(msg) & rule, **kwargs) if rule else on_message( + startswith(msg), **kwargs) def on_endswith(msg: str, - rule: Optional[Union[Rule, RuleChecker]] = None, - permission: Permission = Permission(), + rule: Optional[Optional[Union[Rule, RuleChecker]]] = None, **kwargs) -> Type[Matcher]: - return on_message(endswith(msg) & - rule, permission, **kwargs) if rule else on_message( - startswith(msg), permission, **kwargs) + return on_message(endswith(msg) & rule, **kwargs) if rule else on_message( + startswith(msg), **kwargs) def on_command(cmd: Union[str, Tuple[str, ...]], + alias: Set[Union[str, Tuple[str, ...]]] = None, rule: Optional[Union[Rule, RuleChecker]] = None, - permission: Permission = Permission(), - **kwargs) -> Type[Matcher]: + **kwargs) -> Union[Type[Matcher], MatcherGroup]: if isinstance(cmd, str): cmd = (cmd,) @@ -157,20 +153,28 @@ def on_command(cmd: Union[str, Tuple[str, ...]], handlers = kwargs.pop("handlers", []) handlers.insert(0, _strip_cmd) - return on_message( - command(cmd) & - rule, permission, handlers=handlers, **kwargs) if rule else on_message( - command(cmd), permission, handlers=handlers, **kwargs) + if alias: + alias = set(map(lambda x: (x,) if isinstance(x, str) else x, alias)) + group = MatcherGroup("message", + Rule() & rule, + handlers=handlers, + **kwargs) + for cmd_ in [cmd, *alias]: + group.new(rule=command(cmd_)) + return group + else: + return on_message(command(cmd) & rule, handlers=handlers, ** + kwargs) if rule else on_message( + command(cmd), handlers=handlers, **kwargs) def on_regex(pattern: str, flags: Union[int, re.RegexFlag] = 0, rule: Optional[Rule] = None, - permission: Permission = Permission(), **kwargs) -> Type[Matcher]: return on_message(regex(pattern, flags) & - rule, permission, **kwargs) if rule else on_message( - regex(pattern, flags), permission, **kwargs) + rule, **kwargs) if rule else on_message( + regex(pattern, flags), **kwargs) def load_plugin(module_path: str) -> Optional[Plugin]: @@ -234,3 +238,21 @@ def load_builtin_plugins(): def get_loaded_plugins() -> Set[Plugin]: return set(plugins.values()) + + +class CommandGroup: + + def __init__(self, name: Union[str, Tuple[str, ...]], **kwargs): + self.basename = (name,) if isinstance(name, str) else name + if "aliases" in kwargs: + del kwargs["aliases"] + self.base_kwargs = kwargs + + def command(self, name: Union[str, Tuple[str, ...]], + **kwargs) -> Union[Type[Matcher], MatcherGroup]: + sub_name = (name,) if isinstance(name, str) else name + name = self.basename + sub_name + + final_kwargs = self.base_kwargs.copy() + final_kwargs.update(kwargs) + return on_command(name, **final_kwargs) diff --git a/nonebot/rule.py b/nonebot/rule.py index 0b1b5cc0..7d57b33e 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -20,7 +20,7 @@ from pygtrie import CharTrie from nonebot import get_driver from nonebot.log import logger from nonebot.utils import run_sync -from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, Callable, Awaitable, RuleChecker +from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, Optional, Callable, Awaitable, RuleChecker class Rule: @@ -68,9 +68,11 @@ class Rule: *map(lambda c: c(bot, event, state), self.checkers)) return all(results) - def __and__(self, other: Union["Rule", RuleChecker]) -> "Rule": + def __and__(self, other: Optional[Union["Rule", RuleChecker]]) -> "Rule": checkers = self.checkers.copy() - if isinstance(other, Rule): + if other is None: + return self + elif isinstance(other, Rule): checkers |= other.checkers elif asyncio.iscoroutinefunction(other): checkers.add(other) # type: ignore