Repository: nonebot/nonebot2

Issue:
Feature: 将内置on_*系列函数作为MatcherGroup的方法
**是否在使用中遇到某些问题而需要新的特性？请描述：**

在实际使用中，我需要对nonebot进行一层包装，而MatcherGroup类可以起到对Matcher类似于“工厂函数”的作用
我希望能够将on_*系列函数作为MatcherGroup类的一个方法

**描述你所需要的特性：**

```python
group = MatcherGroup(rule=Rule(some_rule))

matcher = group.on_command("xxx")

@matcher.handle
async def foo():
    ...
```

在库内的on系列函数可以作为一个已经预先实例化的MatcherGroup的方法

```python
_InnerGroup = MatcherGroup()

on_command = _InnerGroup.on_command
```

~希望我描述清楚了~



Discussion:
你的意思是将 `MatcherGroup` 作为类似于 `CommandGroup` 的实现
> 你的意思是将 `MatcherGroup` 作为类似于 `CommandGroup` 的实现

差不多

Resolving patch:
diff --git a/nonebot/__init__.py b/nonebot/__init__.py
--- a/nonebot/__init__.py
+++ b/nonebot/__init__.py
@@ -215,7 +215,7 @@ def run(host: Optional[str] = None,
     get_driver().run(host, port, *args, **kwargs)
 
 
-from nonebot.plugin import on_message, on_notice, on_request, on_metaevent, CommandGroup
+from nonebot.plugin import on_message, on_notice, on_request, on_metaevent, CommandGroup, MatcherGroup
 from nonebot.plugin import on_startswith, on_endswith, on_keyword, on_command, on_regex
 from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins
 from nonebot.plugin import export, require, get_plugin, get_loaded_plugins
diff --git a/nonebot/matcher.py b/nonebot/matcher.py
--- a/nonebot/matcher.py
+++ b/nonebot/matcher.py
@@ -449,201 +449,3 @@ async def run(self, bot: Bot, event: Event, state: dict):
             logger.info(f"Matcher {self} running complete")
             current_bot.reset(b_t)
             current_event.reset(e_t)
-
-
-class MatcherGroup:
-    """事件响应器组合，统一管理。用法同 ``Matcher``"""
-
-    def __init__(self,
-                 type_: str = "",
-                 rule: Optional[Rule] = None,
-                 permission: Optional[Permission] = None,
-                 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):
-        """
-        :说明:
-
-          创建一个事件响应器组合，参数为默认值，与 ``Matcher.new`` 一致
-        """
-        self.matchers: List[Type[Matcher]] = []
-        """
-        :类型: ``List[Type[Matcher]]``
-        :说明: 组内事件响应器列表
-        """
-
-        self.type = type_
-        self.rule = rule or Rule()
-        self.permission = permission or Permission()
-        self.handlers = handlers
-        self.temp = temp
-        self.priority = priority
-        self.block = block
-        self.module = module
-        self.expire_time = expire_time
-
-        self._default_state = default_state
-
-        self._default_parser: Optional[ArgsParser] = None
-
-    def __repr__(self) -> str:
-        return (
-            f"<MatcherGroup from {self.module or 'unknow'}, type={self.type}, "
-            f"priority={self.priority}, temp={self.temp}>")
-
-    def __str__(self) -> str:
-        return self.__repr__()
-
-    def new(self,
-            type_: str = "",
-            rule: Optional[Rule] = None,
-            permission: Optional[Permission] = None,
-            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]:
-        """
-        :说明:
-
-          在组中创建一个新的事件响应器，参数留空则使用组合默认值
-
-        \:\:\:danger 警告
-        如果使用 handlers 参数覆盖组合默认值则该事件响应器不会随组合一起添加新的事件处理函数
-        \:\:\:
-        """
-        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:
-        self._default_parser = func
-        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=str(prompt).format(state))
-                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 send(self, message: Union[str, Message, MessageSegment],
-                   **kwargs):
-        bot = current_bot.get()
-        event = current_event.get()
-        await bot.send(event=event, message=message, **kwargs)
-
-    async def finish(self,
-                     message: Optional[Union[str, Message,
-                                             MessageSegment]] = None,
-                     **kwargs) -> NoReturn:
-        bot = current_bot.get()
-        event = current_event.get()
-        if message:
-            await bot.send(event=event, message=message, **kwargs)
-        raise FinishedException
-
-    async def pause(self,
-                    prompt: Optional[Union[str, Message,
-                                           MessageSegment]] = None,
-                    **kwargs) -> NoReturn:
-        bot = current_bot.get()
-        event = current_event.get()
-        if prompt:
-            await bot.send(event=event, message=prompt, **kwargs)
-        raise PausedException
-
-    async def reject(self,
-                     prompt: Optional[Union[str, Message,
-                                            MessageSegment]] = None,
-                     **kwargs) -> NoReturn:
-        bot = current_bot.get()
-        event = current_event.get()
-        if prompt:
-            await bot.send(event=event, message=prompt, **kwargs)
-        raise RejectedException
diff --git a/nonebot/plugin.py b/nonebot/plugin.py
--- a/nonebot/plugin.py
+++ b/nonebot/plugin.py
@@ -9,6 +9,7 @@
 import sys
 import pkgutil
 import importlib
+from datetime import datetime
 from dataclasses import dataclass
 from importlib._bootstrap import _load
 from contextvars import Context, ContextVar, copy_context
@@ -18,7 +19,7 @@
 from nonebot.permission import Permission
 from nonebot.typing import Handler, RuleChecker
 from nonebot.rule import Rule, startswith, endswith, keyword, command, regex
-from nonebot.typing import Any, Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
+from nonebot.typing import Any, Set, List, Dict, Type, Tuple, Union, Optional, ArgsParser, ModuleType
 
 plugins: Dict[str, "Plugin"] = {}
 """
@@ -485,6 +486,314 @@ def command(self, cmd: Union[str, Tuple[str, ...]],
         return on_command(cmd, **final_kwargs)
 
 
+class MatcherGroup:
+    """事件响应器组合，统一管理。为 ``Matcher`` 创建提供默认属性。"""
+
+    def __init__(self, **kwargs):
+        """
+        :说明:
+
+          创建一个事件响应器组合，参数为默认值，与 ``on`` 一致
+        """
+        self.matchers: List[Type[Matcher]] = []
+        """
+        :类型: ``List[Type[Matcher]]``
+        :说明: 组内事件响应器列表
+        """
+        self.base_kwargs: Dict[str, Any] = kwargs
+        """
+        - **类型**: ``Dict[str, Any]``
+        - **说明**: 其他传递给 ``on`` 的参数默认值
+        """
+
+    def on(self, **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个基础事件响应器，可自定义类型。
+
+        :参数:
+
+          * ``type: str``: 事件响应器类型
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``permission: Optional[Permission]``: 事件响应权限
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        final_kwargs = self.base_kwargs.copy()
+        final_kwargs.update(kwargs)
+        matcher = Matcher.new(**final_kwargs)
+        self.matchers.append(matcher)
+        _tmp_matchers.get().add(matcher)
+        return matcher
+
+    def on_metaevent(self, **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个元事件响应器。
+
+        :参数:
+
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        final_kwargs = self.base_kwargs.copy()
+        final_kwargs.update(kwargs)
+        final_kwargs.pop("type", None)
+        matcher = Matcher.new("meta_event", **final_kwargs)
+        self.matchers.append(matcher)
+        _tmp_matchers.get().add(matcher)
+        return matcher
+
+    def on_message(self, **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个消息事件响应器。
+
+        :参数:
+
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``permission: Optional[Permission]``: 事件响应权限
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        final_kwargs = self.base_kwargs.copy()
+        final_kwargs.update(kwargs)
+        final_kwargs.pop("type", None)
+        matcher = Matcher.new("message", **final_kwargs)
+        self.matchers.append(matcher)
+        _tmp_matchers.get().add(matcher)
+        return matcher
+
+    def on_notice(self, **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个通知事件响应器。
+
+        :参数:
+
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        final_kwargs = self.base_kwargs.copy()
+        final_kwargs.update(kwargs)
+        final_kwargs.pop("type", None)
+        matcher = Matcher.new("notice", **final_kwargs)
+        self.matchers.append(matcher)
+        _tmp_matchers.get().add(matcher)
+        return matcher
+
+    def on_request(self, **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个请求事件响应器。
+
+        :参数:
+
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        final_kwargs = self.base_kwargs.copy()
+        final_kwargs.update(kwargs)
+        final_kwargs.pop("type", None)
+        matcher = Matcher.new("request", **final_kwargs)
+        self.matchers.append(matcher)
+        _tmp_matchers.get().add(matcher)
+        return matcher
+
+    def on_startswith(self,
+                      msg: str,
+                      rule: Optional[Optional[Union[Rule, RuleChecker]]] = None,
+                      **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个消息事件响应器，并且当消息的**文本部分**以指定内容开头时响应。
+
+        :参数:
+
+          * ``msg: str``: 指定消息开头内容
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``permission: Optional[Permission]``: 事件响应权限
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        return self.on_message(rule=startswith(msg) & rule, **kwargs)
+
+    def on_endswith(self,
+                    msg: str,
+                    rule: Optional[Optional[Union[Rule, RuleChecker]]] = None,
+                    **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个消息事件响应器，并且当消息的**文本部分**以指定内容结尾时响应。
+
+        :参数:
+
+          * ``msg: str``: 指定消息结尾内容
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``permission: Optional[Permission]``: 事件响应权限
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        return self.on_message(rule=endswith(msg) & rule, **kwargs)
+
+    def on_keyword(self,
+                   keywords: Set[str],
+                   rule: Optional[Union[Rule, RuleChecker]] = None,
+                   **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个消息事件响应器，并且当消息纯文本部分包含关键词时响应。
+
+        :参数:
+
+          * ``keywords: Set[str]``: 关键词列表
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``permission: Optional[Permission]``: 事件响应权限
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        return self.on_message(rule=keyword(*keywords) & rule, **kwargs)
+
+    def on_command(self,
+                   cmd: Union[str, Tuple[str, ...]],
+                   rule: Optional[Union[Rule, RuleChecker]] = None,
+                   aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
+                   **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个消息事件响应器，并且当消息以指定命令开头时响应。
+
+          命令匹配规则参考: `命令形式匹配 <rule.html#command-command>`_
+
+        :参数:
+
+          * ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
+          * ``permission: Optional[Permission]``: 事件响应权限
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+
+        async def _strip_cmd(bot, event, state: dict):
+            message = event.message
+            event.message = message.__class__(
+                str(message)[len(state["_prefix"]["raw_command"]):].strip())
+
+        handlers = kwargs.pop("handlers", [])
+        handlers.insert(0, _strip_cmd)
+
+        commands = set([cmd]) | (aliases or set())
+        return self.on_message(rule=command(*commands) & rule,
+                               handlers=handlers,
+                               **kwargs)
+
+    def on_regex(self,
+                 pattern: str,
+                 flags: Union[int, re.RegexFlag] = 0,
+                 rule: Optional[Rule] = None,
+                 **kwargs) -> Type[Matcher]:
+        """
+        :说明:
+
+          注册一个消息事件响应器，并且当消息匹配正则表达式时响应。
+
+          命令匹配规则参考: `正则匹配 <rule.html#regex-regex-flags-0>`_
+
+        :参数:
+
+          * ``pattern: str``: 正则表达式
+          * ``flags: Union[int, re.RegexFlag]``: 正则匹配标志
+          * ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
+          * ``permission: Optional[Permission]``: 事件响应权限
+          * ``handlers: Optional[List[Handler]]``: 事件处理函数列表
+          * ``temp: bool``: 是否为临时事件响应器（仅执行一次）
+          * ``priority: int``: 事件响应器优先级
+          * ``block: bool``: 是否阻止事件向更低优先级传递
+          * ``state: Optional[dict]``: 默认的 state
+
+        :返回:
+
+          - ``Type[Matcher]``
+        """
+        return self.on_message(rule=regex(pattern, flags) & rule, **kwargs)
+
+
 def load_plugin(module_path: str) -> Optional[Plugin]:
     """
     :说明:


Test patch:
diff --git a/tests/test_plugins/test_group/__init__.py b/tests/test_plugins/test_group/__init__.py
--- a/tests/test_plugins/test_group/__init__.py
+++ b/tests/test_plugins/test_group/__init__.py
@@ -1,6 +1,7 @@
 from nonebot.rule import to_me
-from nonebot import CommandGroup
+from nonebot import CommandGroup, MatcherGroup
 
-test = CommandGroup("test", rule=to_me())
+cmd = CommandGroup("test", rule=to_me())
+match = MatcherGroup(priority=2)
 
-from . import commands
+from . import commands, matches
diff --git a/tests/test_plugins/test_group/commands.py b/tests/test_plugins/test_group/commands.py
--- a/tests/test_plugins/test_group/commands.py
+++ b/tests/test_plugins/test_group/commands.py
@@ -1,9 +1,9 @@
 from nonebot.typing import Bot, Event
 from nonebot.permission import GROUP_OWNER
 
-from . import test
+from . import cmd
 
-test_1 = test.command("1", aliases={"test"}, permission=GROUP_OWNER)
+test_1 = cmd.command("1", aliases={"test"}, permission=GROUP_OWNER)
 
 
 @test_1.handle()
diff --git a/tests/test_plugins/test_group/matches.py b/tests/test_plugins/test_group/matches.py
new file mode 100644
--- /dev/null
+++ b/tests/test_plugins/test_group/matches.py
@@ -0,0 +1,15 @@
+from nonebot.typing import Bot, Event
+
+from . import match
+
+
+async def heartbeat(bot: Bot, event: Event, state: dict) -> bool:
+    return event.detail_type == "heartbeat"
+
+
+test = match.on_metaevent(rule=heartbeat)
+
+
+@test.receive()
+async def handle_heartbeat(bot: Bot, event: Event, state: dict):
+    print("[i] Heartbeat")