# 深入
# 它如何工作?
如同概览所言:
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
和配置文件的内容来进行鉴权,从而获得这个连接的唯一识别 idself-id
,随后adapter
就会利用self-id
实例化为bot
对象。TIP
需要注意的是,如果协议端通过
websocket
与nonebot
连接,这个步骤只会在建立连接时进行,并在之后运行on_bot_connect
钩子函数;通过http
方式连接时,会在协议端每次上报数据时都进行这个步骤。WARNING
连接之前必须要注册
adapter
WARNING
self-id
是帐号的唯一识别 ID,这意味着不能出现相同的self-id
。driver
会将接收到的数据转交给bot
对象进一步处理。
# adapter 处理原始数据
bot
会利用事先定义好的Event Model
对上报的数据进行分析处理,将数据转化为nonebot
可以处理的Event
对象。TIP
adapter
在转换数据格式的同时可以进行一系列的特殊操作,例如CQHTTP
会对reply
信息进行提取。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
会运行后续的 handlers。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
可以向协议端发送数据,也可以向协议端请求更多的数据。
TIP
不同 adapter
规定了不同的 API,对应的 API 列表请参照协议规范。
一般来说,我们可以用 bot.*
来调用 API
(*是 API
的 action
或者 endpoint
)。
对于发送消息而言,一方面可以调用既有的 API;另一方面 nonebot
实现了两个便捷方法,bot.send(event, message, **kwargs)
方法和可以在 handler
中使用的 Matcher.send(message, **kwargs)
方法,来向事件主体发送消息。
定时任务 →