Co-authored-by: Ju4tCode <42488585+yanyongyu@users.noreply.github.com>
11 KiB
id | sidebar_position | description | slug | options | ||||||
---|---|---|---|---|---|---|---|---|---|---|
index | 0 | 深入了解 NoneBot2 运行机制 | /advanced/ |
|
深入
:::danger 警告 进阶部分尚未更新完成 :::
它如何工作?
如同概览所言:
NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的事件进行解析和处理,并以插件化的形式,按优先级分发给事件所对应的事件响应器,来完成具体的功能。
NoneBot2 是一个可以对机器人上报的事件进行处理并完成具体功能的机器人框架,在这里,我们将简要讲述它的工作内容。
便捷起见,以下内容对 NoneBot2 会被称为 NoneBot,与 NoneBot2 交互的机器人实现会被称为协议端。
在实际应用中,NoneBot 会充当一个高性能,轻量级的 Python 微服务框架。协议端可以通过 http、websocket 等方式与之通信,这个通信往往是双向的:一方面,协议端可以上报数据给 NoneBot,NoneBot 会处理数据并返回响应给协议端;另一方面,NoneBot 可以主动推送数据给协议端。而 NoneBot 便是围绕双向通信进行工作的。
在开始工作之前,NoneBot 需要进行准备工作:
- 运行
nonebot.init
初始化函数,它会读取配置文件,并初始化 NoneBot 和后端驱动Driver
对象。 - 注册协议适配器
Adapter
。 - 加载插件。
准备工作完成后,NoneBot 会利用 uvicorn 启动,并运行 on_startup
钩子函数。
随后,倘若一个协议端与 NoneBot 进行了连接,NoneBot 的后端驱动 Driver
就会将数据交给 Adapter
,然后会实例化 Bot
,NoneBot 便会利用 Bot
开始工作,它的工作内容分为两个方面:
-
事件处理,
Bot
会将协议端上报的数据转化为Event
(事件),之后 NoneBot 会根据一套既定流程来处理事件。 -
调用
API
,在事件处理的过程中,NoneBot 可以通过Bot
调用协议端指定的API
来获取更多数据,或者反馈响应给协议端;NoneBot 也可以通过调用API
向协议端主动请求数据或者主动推送数据。
在指南模块,我们已经叙述了如何配置 NoneBot、如何注册协议适配器以及如何加载插件,这里便不再赘述。
下面,我们将对事件处理,调用 API 进行说明。
事件处理
我们可以先看事件处理的流程图:
在流程图里,我们可以看到,NoneBot 会有三个阶段来处理事件:
- Driver 接收上报数据
- Adapter 处理原始数据
- NoneBot 处理 Event
我们将顺序说明这三个阶段。其中,会将第三个阶段拆分成概念解释,处理 Event,特殊异常处理三个部分来说明。
Driver 接收上报数据
- 协议端会通过 websocket 或 http 等方式与 NoneBot 的后端驱动
Driver
连接,协议端上报数据后,Driver
会将原始数据交给Adapter
处理。
:::warning
连接之前必须要注册 Adapter
:::
Adapter 处理原始数据
Adapter
检查授权许可,并获取self-id
作为唯一识别 id 。
:::tip
如果协议端通过 websocket 上报数据,这个步骤只会在建立连接时进行,并在之后运行 on_bot_connect
钩子函数;通过 http 方式连接时,会在协议端每次上报数据时都进行这个步骤。
:::
:::warning
self-id
是帐号的唯一识别 ID ,这意味着不能出现相同的 self-id
。
:::
-
根据
self-id
实例化Adapter
相应的Bot
。 -
根据
Event Model
将原始数据转化为 NoneBot 可以处理的Event
对象。
:::tip
Adapter
在转换数据格式的同时可以进行一系列的特殊操作,例如 OneBot 适配器会对 reply 信息进行提取。
:::
Bot
和Event
交由 NoneBot 进一步处理。
NoneBot 处理 Event
在讲述这个阶段之前,我们需要先对几个概念进行解释。
概念解释
- hook ,或者说钩子函数,它们可以在 NoneBot 处理
Event
的不同时刻进行拦截,修改或者扩展,在 NoneBot 中,事件钩子函数分为事件预处理 hook
、运行预处理 hook
、运行后处理 hook
和事件后处理 hook
。
:::tip
关于 hook
的更多信息,可以查阅这里。
:::
-
Matcher 与 matcher,在指南中,我们讲述了如何注册事件响应器,这里的事件响应器或者说
Matcher
并不是一个具体的实例instance
,而是一个具有特定属性的类class
。只有当Matcher
响应事件时,才会实例化为具体的instance
,也就是matcher
。matcher
可以认为是 NoneBot 处理Event
的基本单位,运行matcher
是 NoneBot 工作的主要内容。 -
handler,或者说事件处理函数,它们可以认为是 NoneBot 处理
Event
的最小单位。在不考虑hook
的情况下,运行 matcher 就是顺序运行 matcher.handlers,这句话换种表达方式就是,handler
只有添加到matcher.handlers
时,才可以参与到 NoneBot 的工作中来。
:::tip
如何让 handler
添加到 matcher.handlers
?
一方面,我们可以参照这里利用装饰器来添加;另一方面,我们在用 on()
或者 on_*()
注册事件响应器时,可以添加 handlers=[handler1, handler2, ...]
这样的关键词参数来添加。
:::
处理 Event
- 执行事件预处理 hook, NoneBot 接收到
Event
后,会传入到事件预处理 hook
中进行处理。
:::warning
需要注意的是,执行多个 事件预处理 hook
时并无顺序可言,它们是并发运行的。这个原则同样适用于其他的 hook
。
:::
-
按优先级升序选出同一优先级的 Matcher,NoneBot 提供了一个全局字典
matchers
,这个字典的key
是优先级priority
,value
是一个list
,里面存放着同一优先级的Matcher
。在注册Matcher
时,它和优先级priority
会添加到里面。在执行
事件预处理 hook
后,NoneBot 会对matchers
的key
升序排序并选择出当前最小优先级的Matcher
。 -
根据 Matcher 定义的 Rule、Permission 判断是否运行,在选出
Matcher
后,NoneBot 会将bot
,Event
传入到Matcher.check_rule
和Matcher.check_perm
两个函数中,两个函数分别对 Matcher 定义的Rule
、Permission
进行 check,当 check 通过后,这个Matcher
就会响应事件。当同一个优先级的所有Matcher
均没有响应时,NoneBot 会返回到上一个步骤,选择出下一优先级的Matcher
。 -
实例化 matcher 并执行运行预处理 hook,当
Matcher
响应事件后,它便会实例化为matcher
,并执行运行预处理 hook
。 -
顺序运行 matcher 的所有 handlers,
运行预处理 hook
执行完毕后,便会运行matcher
,也就是顺序运行它的handlers
。
:::tip
matcher
运行 handlers
的顺序是:先运行该 matcher
的类 Matcher
注册时添加的 handlers
(如果有的话),再按照装饰器装饰顺序运行装饰的 handlers
。
:::
-
执行运行后处理 hook,
matcher
的handlers
运行完毕后,会执行运行后处理 hook
。 -
判断是否停止事件传播,NoneBot 会根据当前优先级所有
matcher
的block
参数或者StopPropagation
异常判断是否停止传播Event
,如果事件没有停止传播,NoneBot 便会返回到第 2 步, 选择出下一优先级的Matcher
。 -
执行事件后处理 hook,在
Event
停止传播或执行完所有响应的Matcher
后,NoneBot 会执行事件后处理 hook
。当
事件后处理 hook
执行完毕后,当前Event
的处理周期就顺利结束了。
特殊异常处理
在这个阶段,NoneBot 规定了几个特殊的异常,当 NoneBot 捕获到它们时,会用特定的行为来处理它们。
-
IgnoredException
这个异常可以在
事件预处理 hook
和运行预处理 hook
抛出。当
事件预处理 hook
抛出它时,NoneBot 会忽略当前的Event
,不进行处理。当
运行预处理 hook
抛出它时,NoneBot 会忽略当前的matcher
,结束当前matcher
的运行。
:::warning
当 hook
需要抛出这个异常时,要写明原因。
:::
-
PausedException
这个异常可以在
handler
中由Matcher.pause
抛出。当 NoneBot 捕获到它时,会停止运行当前
handler
并结束当前matcher
的运行,并将后续的handler
交给一个临时Matcher
来响应当前交互用户的下一个消息事件,当临时Matcher
响应时,临时Matcher
会运行后续的handler
。 -
RejectedException
这个异常可以在
handler
中由Matcher.reject
抛出。当 NoneBot 捕获到它时,会停止运行当前
handler
并结束当前matcher
的运行,并将当前 handler 和后续handler
交给一个临时Matcher
来响应当前交互用户的下一个消息事件,当临时Matcher
响应时,临时Matcher
会运行当前handler
和后续的handler
。 -
FinishedException
这个异常可以在
handler
中由Matcher.finish
抛出。当 NoneBot 捕获到它时,会停止运行当前
handler
并结束当前matcher
的运行。 -
StopPropagation
这个异常一般会在执行
运行后处理 hook
后抛出。当 NoneBot 捕获到它时, 会停止传播当前
Event
,不再寻找下一优先级的Matcher
,直接执行事件后处理 hook
。
调用 API
NoneBot 可以通过 bot
来调用 API
,API
可以向协议端发送数据,也可以向协议端请求更多的数据。
NoneBot 调用 API
会有如下过程:
-
调用
calling_api_hook
预处理钩子。 -
adapter
将信息处理为原始数据,并转交driver
,driver
交给协议端处理。 -
driver
接收协议端的结果,交给adapter
处理之后将结果反馈给 NoneBot 。 -
调用
called_api_hook
后处理钩子。
在调用 API
时同样规定了特殊的异常,叫做 MockApiException
。该异常会由预处理钩子和后处理钩子触发,当预处理钩子触发时,NoneBot 会跳过之后的调用过程,直接执行后处理钩子。
:::tip
不同 adapter
规定了不同的 API,对应的 API 列表请参照协议规范。
:::
一般来说,我们可以用 bot.*
来调用 API
(*是 API
的 action
或者 endpoint
)。
对于发送消息而言,一方面可以调用既有的 API
;另一方面 NoneBot 实现了两个便捷方法,bot.send(event, message, **kwargs)
方法和可以在 handler
中使用的 Matcher.send(message, **kwargs)
方法,来向事件主体发送消息。