事件处理器 (EventHandlers)
EventHandlers
模块是 MuRainBot2 (MRB2) 框架响应各类事件的基石。它不仅提供了强大的事件过滤和分发能力,还通过依赖注入和可扩展的规则系统,为插件开发者构建复杂交互逻辑提供了坚实的基础。
推荐用法
对于需要解析带参数的、有固定前缀的命令(例如 /weather beijing
),我们强烈推荐使用更高级的 CommandManager
。它构建在 EventHandlers
之上,提供了更简洁、更强大的命令定义和参数解析功能。
EventHandlers
现在主要用于处理以下场景:
- 非命令类事件:如监听群成员增加/减少通知 (
NoticeEvent
)、好友请求 (RequestEvent
)等。 - 无固定前缀或格式的关键词触发:例如,监听消息中是否包含“你好”,而不关心它出现在消息的哪个位置。
- 需要高度自定义底层匹配逻辑的复杂场景。
核心架构:Rule -> Matcher -> Handler
MRB2 的事件处理遵循一个清晰的链式模型:
事件 (Event): 当 MRB2 收到数据时,会将其解析为标准化的事件对象,如
GroupMessageEvent
。这是数据流的起点。匹配器 (Matcher): 通过
on_event()
创建。它像一个过滤器组,首先根据事件类型进行粗筛,然后使用一组初始规则 (Rule) 进行精筛。只有通过所有筛选的事件,才能进入该Matcher
。处理器 (Handler): 使用
@matcher.register_handler()
注册在Matcher
上的函数。它是事件处理的终点,也可以有自己的附加规则。只有通过了Matcher
和Handler
两层规则的事件,才会最终触发处理器函数的执行。
依赖注入 (Dependency Injection):优雅地获取上下文
依赖注入是 EventHandlers
的一个核心特性,它允许你的处理器函数仅仅通过声明参数,就能自动获取所需的上下文信息,而无需在函数体内手动调用各种管理器。
可注入的参数
EventHandlers
支持注入以下三种核心的上下文对象:
event
(事件对象):- 如何声明:
def my_handler(event: EventClassifier.MessageEvent): ...
- 作用: 这是最基础的注入,每个处理器函数的第一个参数必须是事件对象。框架会自动将触发该处理器的事件实例传递给它。类型注解有助于静态分析和代码补全。
- 如何声明:
state
,user_state
,group_state
(状态对象):- 如何声明:
def my_handler(event, state: dict): ...
def my_handler(event, user_state: dict): ...
def my_handler(event, group_state: dict): ...
- 作用: 从
StateManager
中获取临时的、与当前交互相关的内存状态。state
: 最常用的状态。对于群消息,它对应g<group_id>_u<user_id>
的状态;对于私聊,对应u<user_id>
的状态。user_state
: 明确获取当前用户的状态 (u<user_id>
)。group_state
: 明确获取当前群组的状态 (g<group_id>
)。
- 数据结构: 注入的状态是一个字典,其中
data
键对应的值才是你真正用来读写的状态字典。pythondef handler(event, user_state: dict): # user_state['data'] 是一个可变字典,直接修改它即可保存状态 count = user_state["data"].get("visit_count", 0) + 1 user_state["data"]["visit_count"] = count
警告:内存存储,重启丢失
StateManager
中的所有数据都存储在内存中!当机器人程序重启或关闭时,所有状态数据都会丢失。它只适用于存储临时的会话状态。
- 如何声明:
由
Rule
注入的自定义参数:- 如何声明: 取决于你自定义的
Rule
。 - 作用: 这是
EventHandlers
最灵活的部分。你的自定义Rule
可以在match()
方法中返回一个元组(True, {'my_param': some_value})
。框架会自动收集这些由规则生成的键值对,并通过依赖注入传递给处理器函数。
- 如何声明: 取决于你自定义的
自定义 Rule
:打造你的专属匹配逻辑
当内置规则无法满足你复杂的需求时,你可以通过继承 EventHandlers.Rule
来创建自己的规则。
自定义 Rule
的关键在于实现 match()
方法。
match()
方法详解
from typing import Any, Tuple
class MyCustomRule(EventHandlers.Rule):
def match(self, event: EventClassifier.Event) -> bool | Tuple[bool, dict[str, Any]]:
# ... 你的逻辑 ...
- 输入:
event
,即当前正在被检查的事件对象。 - 输出:
bool
:- 返回
True
表示匹配成功。 - 返回
False
表示匹配失败。
- 返回
tuple[bool, dict]
:- 这是一个更强大的返回形式,用于在匹配成功的同时,向处理器注入自定义参数。
- 返回
(True, {'param_name': value})
。当此规则被用于激活一个处理器时,框架会尝试将value
注入到处理器函数中名为param_name
的参数上。 - 如果匹配失败,应返回
False
或(False, None)
。
示例 1:使用 FuncRule
判断群主
对于一些简单的逻辑判断,FuncRule
是最快捷的方式。例如,判断消息是否由群主发送。
from murainbot import EventHandlers, EventClassifier
def is_group_owner(event: EventClassifier.Event) -> bool:
# 最佳实践:通过检查事件的属性来判断其上下文,而不是它的类。
# 这让 Rule 更加通用和健壮。
if event.get("message_type") != "group":
return False
# 安全地获取 sender 信息
sender_info = event.get("sender")
if not isinstance(sender_info, dict):
return False
# 返回角色检查结果
return sender_info.get("role") == "owner"
# 将函数包装成 Rule
owner_rule = EventHandlers.FuncRule(is_group_owner)
# 在 Matcher 中使用
# 这个 Matcher 可以安全地监听 MessageEvent,因为我们的 Rule 内部做了正确的检查
owner_matcher = EventHandlers.on_event(
EventClassifier.MessageEvent,
rules=[owner_rule]
)
最佳实践
为什么检查 event.get("message_type")
而不是 isinstance(event, GroupMessageEvent)
?
因为这让你的 Rule
更加通用和健壮。一个 Rule
如果写成这样,那么无论 Matcher
是通过 on_event(GroupMessageEvent)
还是更宽泛的 on_event(MessageEvent)
注册的,它都能正确工作。它关注的是事件的数据内容(它是不是来自一个群),而不是事件的具体类实现,这是一种更可靠的设计模式。
示例 2:自定义 Rule
提取特定格式消息
当你的规则不仅需要判断,还需要提取数据并注入时,自定义 Rule
就派上了用场。假设我们想创建一个规则,专门匹配并提取形如 “抽奖: <奖品名称>”
的消息。
import re
from murainbot import EventHandlers, QQRichText, EventClassifier
from typing import Any, Tuple
class DrawPrizeRule(EventHandlers.Rule):
# 定义一个正则表达式来匹配格式
PATTERN = re.compile(r"^抽奖[::]\s*(?P<prize>.+)")
def match(self, event: EventClassifier.Event) -> Tuple[bool, dict[str, Any] | None]:
# 1. 确保我们处理的是消息事件
if not isinstance(event, EventClassifier.MessageEvent):
return False, None
# 2. 获取消息的纯文本内容
text = event.raw_message.strip()
# 3. 使用正则表达式进行匹配
match_obj = self.PATTERN.match(text)
# 4. 如果匹配成功
if match_obj:
# 提取命名捕获组 'prize' 的内容
prize_name = match_obj.group('prize').strip()
# 5. 返回成功标志和要注入的参数字典
# 我们将提取出的奖品名称注入到名为 'prize' 的参数中
return True, {"prize": prize_name}
# 6. 如果不匹配,返回失败
return False, None
# --- 在插件中使用这个自定义 Rule ---
# 创建规则实例
draw_rule = DrawPrizeRule()
# 创建监听器,使用我们的自定义规则
draw_matcher = EventHandlers.on_event(
EventClassifier.MessageEvent,
rules=[draw_rule]
)
# 注册处理器,并通过函数签名请求注入 'prize' 参数
@draw_matcher.register_handler()
def handle_draw(event: EventClassifier.MessageEvent, prize: str):
# 依赖注入的魔力:'prize' 参数已经被自动填充
user_id = event.user_id
response_text = f"收到!用户 {user_id} 发起了关于「{prize}」的抽奖!"
event.reply(response_text)
在这个例子中,DrawPrizeRule
不仅判断了消息格式是否正确,还承担了数据提取的工作,并通过依赖注入将提取出的数据直接传递给处理器,极大地简化了处理器的内部逻辑。
规则的组合:&
与 |
Rule
对象支持使用 &
(逻辑与) 和 |
(逻辑或) 运算符进行组合,以创建更复杂的逻辑链。
from murainbot.EventHandlers import to_me
# 假设已经定义好了 is_group_owner 函数和 owner_rule
owner_rule = EventHandlers.FuncRule(is_group_owner)
# 组合规则:必须是@我的,并且是群主发送的
final_rule = to_me & owner_rule
# 使用组合规则创建 Matcher
owner_command_matcher = EventHandlers.on_event(
EventClassifier.MessageEvent,
rules=[final_rule]
)
rule1 & rule2
等效于AllRule(rule1, rule2)
。rule1 | rule2
等效于AnyRule(rule1, rule2)
。- 当你在
rules
列表中放入多个Rule
时,它们之间默认就是&
(与) 关系。
通过深入理解依赖注入和自定义 Rule
,你可以将 EventHandlers
的能力发挥到极致,构建出逻辑清晰、高度解耦且易于维护的强大插件。