mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-07-27 16:21:28 +00:00
🏗️ change doc theme
This commit is contained in:
188
website/docs/advanced/README.md
Normal file
188
website/docs/advanced/README.md
Normal file
@ -0,0 +1,188 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
id: advanced
|
||||
slug: /advanced
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 10
|
||||
catogory: advanced
|
||||
---
|
||||
|
||||
# 深入
|
||||
|
||||
## 它如何工作?
|
||||
|
||||
如同[概览](../guide/README.md)所言:
|
||||
|
||||
> NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的事件进行解析和处理,并以插件化的形式,按优先级分发给事件所对应的事件响应器,来完成具体的功能。
|
||||
|
||||
`Nonebot2` 是一个可以对机器人上报的事件进行处理并完成具体功能的机器人框架,在这里,我们将简要讲述它的工作内容。
|
||||
|
||||
**便捷起见,以下内容对 `Nonebot2` 会被称为 `nonebot`,与 `Nonebot2` 交互的机器人实现会被称为 `协议端`**。
|
||||
|
||||
在实际应用中,`nonebot` 会充当一个高性能,轻量级的 Python 微服务框架。协议端可以通过 `http`, `websocket` 等方式与之通信,这个通信往往是双向的:一方面,协议端可以上报数据给 `nonebot`,`nonebot` 会处理数据并返回响应给协议端;另一方面,`nonebot` 可以主动推送数据给协议端。而 `nonebot` 便是围绕双向通信进行工作的。
|
||||
|
||||
在开始工作之前,`nonebot` 需要进行准备工作:
|
||||
|
||||
1. **运行 `nonebot.init` 初始化函数**,它会读取配置文件,并初始化 `nonebot` 和后端驱动 `driver` 对象。
|
||||
2. **注册协议适配器 `adapter`** 。
|
||||
3. **加载插件**。
|
||||
|
||||
准备工作完成后,`nonebot` 会利用 `uvicorn` 启动,并运行 `on_startup` 钩子函数。
|
||||
|
||||
随后,倘若一个协议端与 `nonebot` 进行了连接,`nonebot` 的后端驱动 `driver` 就会将 `adapter` 实例化为 `bot`,`nonebot` 便会利用 `bot` 开始工作,它的工作内容分为两个方面:
|
||||
|
||||
1. **事件处理**,`bot` 会将协议端上报的数据转化为 `事件`(`Event`),之后 `nonebot` 会根据一套既定流程来处理 `事件`。
|
||||
|
||||
2. **调用 `API`**, 在**事件处理**的过程中,`nonebot` 可以通过 `bot` 调用协议端指定的 `API` 来获取更多数据,或者反馈响应给协议端; `nonebot` 也可以通过调用 `API` 向协议端主动请求数据或者主动推送数据。
|
||||
|
||||
在**指南**模块, 我们已经叙述了[如何配置 nonebot](../guide/basic-configuration.md), [如何注册协议适配器](../guide/getting-started.md),[如何加载插件](../guide/loading-a-plugin.md), 在这里便不再赘述。
|
||||
|
||||
下面,我们将对**事件处理**, **调用 API**进行说明。
|
||||
|
||||
## 事件处理
|
||||
|
||||
我们可以先看事件处理的流程图:
|
||||
|
||||

|
||||
|
||||
在流程图里,我们可以看到,`nonebot` 会有三个阶段来处理事件:
|
||||
|
||||
1. **driver 处理上报数据**
|
||||
2. **adapter 处理原始数据**
|
||||
3. **nonebot 处理 Event**
|
||||
|
||||
我们将顺序说明这三个阶段。其中,会将第三个阶段拆分成**概念解释**,**处理 Event**,**特殊异常处理**三个部分来说明。
|
||||
|
||||
### driver 处理上报数据
|
||||
|
||||
1. 协议端会通过 `websocket` 或者 `http` 等方式与 `nonebot` 的后端驱动 `driver` 连接,`driver` 会根据之前注册的 `adapter` 和配置文件的内容来进行鉴权,从而获得这个连接的唯一识别 id `self-id`,随后 `adapter` 就会利用 `self-id` 实例化为 `bot` 对象。
|
||||
|
||||
::: tip
|
||||
需要注意的是,如果协议端通过 `websocket` 与 `nonebot` 连接,这个步骤只会在建立连接时进行,并在之后运行 `on_bot_connect` 钩子函数;通过 `http` 方式连接时,会在协议端每次上报数据时都进行这个步骤。
|
||||
:::
|
||||
|
||||
::: warning
|
||||
连接之前必须要注册 `adapter`
|
||||
:::
|
||||
|
||||
::: warning
|
||||
`self-id` 是帐号的唯一识别 ID,这意味着不能出现相同的 `self-id`。
|
||||
:::
|
||||
|
||||
2. `driver` 会将接收到的数据转交给 `bot` 对象进一步处理。
|
||||
|
||||
### adapter 处理原始数据
|
||||
|
||||
1. `bot` 会利用事先定义好的 `Event Model` 对上报的数据进行分析处理,将数据转化为 `nonebot` 可以处理的 `Event` 对象。
|
||||
|
||||
::: tip
|
||||
`adapter` 在转换数据格式的同时可以进行一系列的特殊操作,例如 `CQHTTP` 会对 `reply` 信息进行提取。
|
||||
:::
|
||||
|
||||
2. `Event` 会传入 `nonebot` 做进一步处理。
|
||||
|
||||
### nonebot 处理 Event
|
||||
|
||||
在讲述这个阶段之前,我们需要先对几个概念进行解释。
|
||||
|
||||
#### 概念解释
|
||||
|
||||
1. **hook**,或者说**钩子函数**,它们可以在 `nonebot` 处理 `Event` 的不同时刻进行拦截,修改或者扩展,在 `nonebot` 中,钩子函数分为 `事件预处理hook`,`运行预处理hook`,`运行后处理hook` 和 `事件后处理hook`。
|
||||
|
||||
::: tip
|
||||
关于`hook`的更多信息,可以查阅[这里](./runtime-hook.md)
|
||||
:::
|
||||
|
||||
2. **Matcher**与**matcher**,在**指南**中,我们讲述了[如何注册事件响应器](../guide/creating-a-matcher),这里的事件响应器或者说 `Matcher` 并不是一个具体的实例 `instance`,而是一个具有特定属性的类 `class`。只有当 `Matcher` **响应事件**时,才会实例化为具体的 `instance`,也就是 `matcher`。`matcher` 可以认为是 `nonebot` 处理 `Event` 的基本单位,运行 `matcher` 是`nonebot`工作的主要内容。
|
||||
|
||||
3. **handler**,或者说**事件处理函数**, 它们可以认为是 `nonebot` 处理 `Event` 的最小单位。在不考虑 `hook` 的情况下,**运行 matcher 就是顺序运行 matcher.handlers**,这句话换种表达方式就是,`handler` 只有添加到 `matcher.handlers` 时,才可以参与到 `nonebot` 的工作中来。
|
||||
|
||||
::: tip
|
||||
如何让 `handler` 添加到 `matcher.handlers`?
|
||||
|
||||
一方面,我们可以参照[这里](../guide/creating-a-handler)利用装饰器来添加;另一方面,我们在用 `on()` 或者 `on_*()` 注册事件响应器时,可以添加 `handlers=[handler1, handler2, ...]` 这样的关键词参数来添加。
|
||||
:::
|
||||
|
||||
#### 处理 Event
|
||||
|
||||
1. **执行事件预处理 hook**, `nonebot` 接收到 `Event` 后,会传入到 `事件预处理hook` 中进行处理。
|
||||
|
||||
::: warning
|
||||
需要注意的是,执行多个 `事件预处理hook` 时并无顺序可言,它们是**并行运行**的。这个原则同样适用于其他的 `hook`。
|
||||
:::
|
||||
|
||||
2. **按优先级升序选出同一优先级的 Matcher**,`nonebot` 提供了一个全局字典 `matchers`,这个字典的 `key` 是优先级 `priority`,`value` 是一个 `list`,里面存放着同一优先级的 `Matcher`。在注册 `Matcher` 时,它和优先级 `priority` 会添加到里面。
|
||||
|
||||
在执行 `事件预处理hook` 后,`nonebot` 会对 `matchers` 的 `key` 升序排序并选择出当前最小优先级的 `Matcher`。
|
||||
|
||||
3. **根据 Matcher 定义的 Rule, Permission 判断是否运行**,在选出 `Matcher` 后,`nonebot` 会将 `bot`,`Event` 传入到 `Matcher.check_rule` 和 `Matcher.check_perm` 两个函数中,两个函数分别对 Matcher 定义的 Rule, Permission 进行 check,当 check 通过后,这个 `Matcher` 就会响应事件。但是当同一个优先级的所有 `Matcher` 均没有响应时,`nonebot` 会返回到上一个步骤,选择出下一优先级的 `Matcher`。
|
||||
|
||||
4. **实例化 matcher 并执行运行预处理 hook**,当 `Matcher` 响应事件后,它便会实例化为 `matcher`,并执行 `运行预处理hook`。
|
||||
|
||||
5. **顺序运行 matcher 的所有 handlers**,`运行预处理hook` 执行完毕后,便会运行 `matcher`,也就是**顺序运行**它的 `handlers`。
|
||||
|
||||
::: tip
|
||||
`matcher` 运行 `handlers` 的顺序是: 先运行该 `matcher` 的类 `Matcher` 注册时添加的 `handlers`(如果有的话),再按照装饰器装饰顺序运行装饰的 `handlers`。
|
||||
:::
|
||||
|
||||
6. **执行运行后处理 hook**,`matcher` 的 `handlers` 运行完毕后,会执行 `运行后处理hook`。
|
||||
|
||||
7. **判断是否停止事件传播**,`nonebot` 会根据当前优先级所有 `matcher` 的 `block` 参数或者 `StopPropagation` 异常判断是否停止传播 `Event`,如果事件没有停止传播,`nonebot` 便会返回到第 2 步, 选择出下一优先级的 `Matcher`。
|
||||
|
||||
8. **执行事件后处理 hook**,在 `Event` 停止传播或执行完所有响应的 `Matcher` 后,`nonebot` 会执行 `事件后处理hook`。
|
||||
|
||||
当 `事件后处理hook` 执行完毕后,当前`Event`的处理周期就顺利结束了。
|
||||
|
||||
#### 特殊异常处理
|
||||
|
||||
在这个阶段,`nonebot` 规定了几个特殊的异常,当 `nonebot` 捕获到它们时,会用特定的行为来处理它们。
|
||||
|
||||
1. **IgnoredException**
|
||||
|
||||
这个异常可以在 `事件预处理hook` 和 `运行预处理hook` 抛出。
|
||||
|
||||
当 `事件预处理hook` 抛出它时,`nonebot` 会忽略当前的 `Event`,不进行处理。
|
||||
|
||||
当 `运行预处理hook` 抛出它时,`nonebot` 会忽略当前的 `matcher`,结束当前 `matcher` 的运行。
|
||||
|
||||
::: warning
|
||||
当 `hook` 需要抛出这个异常时,要写明原因。
|
||||
:::
|
||||
|
||||
2. **PausedException**
|
||||
|
||||
这个异常可以在 `handler` 中由 `Matcher.pause` 抛出。
|
||||
|
||||
当 `nonebot` 捕获到它时,会停止运行当前 `handler` 并结束当前 `matcher` 的运行,并将后续的 `handler` 交给一个临时 `Matcher` 来响应当前交互用户的下一个消息事件,当临时 `Matcher` 响应时,临时 `Matcher` 会运行后续的 handlers。
|
||||
|
||||
3. **RejectedException**
|
||||
|
||||
这个异常可以在 `handler` 中由 `Matcher.reject` 抛出。
|
||||
|
||||
当 `nonebot` 捕获到它时,会停止运行当前 `handler` 并结束当前 `matcher` 的运行,并将当前 handler 和后续 `handler` 交给一个临时 `Matcher` 来响应当前交互用户的下一个消息事件,当临时 `Matcher` 响应时,临时 `Matcher` 会运行当前 `handler` 和后续的 `handler`。
|
||||
|
||||
4. **FinishedException**
|
||||
|
||||
这个异常可以在 `handler` 中由 `Matcher.finish` 抛出。
|
||||
|
||||
当 `nonebot` 捕获到它时,会停止运行当前 `handler` 并结束当前 `matcher` 的运行。
|
||||
|
||||
5. **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)` 方法,来向事件主体发送消息。
|
4
website/docs/advanced/developer/_category_.json
Normal file
4
website/docs/advanced/developer/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "发布",
|
||||
"position": 1
|
||||
}
|
BIN
website/docs/advanced/developer/images/plugin_store_publish.png
Normal file
BIN
website/docs/advanced/developer/images/plugin_store_publish.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 92 KiB |
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
58
website/docs/advanced/developer/publish-plugin.md
Normal file
58
website/docs/advanced/developer/publish-plugin.md
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 110
|
||||
catogory: advanced
|
||||
---
|
||||
|
||||
# 发布插件
|
||||
|
||||
## 前注
|
||||
|
||||
本章节仅包含插件发布流程指导,插件开发请查阅 **[创建插件](../guide/creating-a-plugin.md)** 章节与 **[Plugin API 文档](../api/plugin.md)** 。
|
||||
|
||||
## 插件发布流程
|
||||
|
||||
### 发布到 PyPI
|
||||
|
||||
您可以选择任意自己喜欢的方式将您的插件发布到 **[PyPI](https://pypi.org/)** ,如使用 **[setuptools](https://pypi.org/project/setuptools/)** 或 **[poetry](https://pypi.org/project/poetry/)** 进行 PyPI 发布。
|
||||
|
||||
发布时,请您为自己的插件取一个清晰易懂的名字。通常而言,一款 NoneBot2 插件名称使用 `nonebot-plugin-` 作为前缀(如`nonebot-plugin-foo`),以 `nonebot_plugin_` 作为导入名的前缀(如`nonebot_plugin_foo`),这并非是强制规范, 而是为了防止与其他 PyPI 包产生冲突, 所以我们推荐您在没有特殊需求的情况下这样做。
|
||||
|
||||
发布后,请确保您的插件已能公开的从 PyPI 访问到,请检查您的插件在 PyPI 的地址:`https://pypi.org/project/<您的Nonebot2插件项目名>`
|
||||
|
||||
### 托管您的插件源代码
|
||||
|
||||
将插件源代码及相关构建文件(如`pyproject.toml`或`setup.py`等与 PyPI 包构建相关的文件)托管在公开代码仓。
|
||||
|
||||
请确保您的代码仓地址能够被正确的访问,检查您的插件在代码仓的地址,如 `https://github.com/<您的Github用户名>/<您的插件Github项目名>` 。
|
||||
|
||||
### 申请发布到 Nonebot2 插件商店
|
||||
|
||||
完成在 PyPI 的插件发布流程与源代码托管流程后,请您前往 **[NoneBot2 商店](https://v2.nonebot.dev/store.html)** 页面,切换到插件页签,点击 **发布插件** 按钮。
|
||||
|
||||

|
||||
|
||||
如图所示,在弹出的插件信息提交表单内,填入您所要发布的相应插件信息。
|
||||
|
||||

|
||||
|
||||
完成填写后,请点击 **发布** 按钮,这将自动在 **[NoneBot2](https://github.com/nonebot/nonebot2)** 代码仓内创建发布您的插件的对应 Issue。
|
||||
|
||||
### 等待插件发布处理
|
||||
|
||||
您的插件发布 Issue 创建后,将会经过*Nonebot2 Publish Bot*的检查,以确保插件信息正确无误。
|
||||
|
||||
之后,NoneBot2 的维护者们将会对插件进行进一步的检查,以确保用户能够正常安装并使用该插件。
|
||||
|
||||
完成这些步骤后,您的插件将会被合并到 **[NoneBot2 商店](https://v2.nonebot.dev/store.html)** ,而您也将成为 **[NoneBot2 贡献者](https://github.com/nonebot/nonebot2/graphs/contributors)** 的一员。
|
||||
|
||||
## 完成
|
||||
|
||||
恭喜您,经过上述的发布流程,您的插件已经成功发布到 Nonebot2 商店了。
|
||||
|
||||
此时,您可以在 **[NoneBot2 商店](https://v2.nonebot.dev/store.html)** 的插件页签查找到您的插件。同时,欢迎您成为 **[NoneBot2 贡献者](https://github.com/nonebot/nonebot2/graphs/contributors)** !
|
||||
|
||||
**Congratulations!**
|
126
website/docs/advanced/export-and-require.md
Normal file
126
website/docs/advanced/export-and-require.md
Normal file
@ -0,0 +1,126 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 50
|
||||
catogory: advanced
|
||||
---
|
||||
|
||||
# 跨插件访问
|
||||
|
||||
由于 `nonebot2` 独特的插件加载机制,在使用 python 原有的 import 机制来进行插件之间的访问时,很可能会有奇怪的或者意料以外的情况发生。为了避免这种情况的发生,您可以有两种方法来实现跨插件访问:
|
||||
|
||||
1. 将插件间的要使用的公共代码剥离出来,作为公共文件或者文件夹,提供给插件加以调用。
|
||||
2. 使用 `nonebot2` 提供的 `export` 和 `require` 机制,来实现插件间的互相调用。
|
||||
|
||||
第一种方法比较容易理解和实现,这里不再赘述,但需要注意的是,请不要将公共文件或者公共文件夹作为**插件**被 `nonebot2` 加载。
|
||||
|
||||
下面将介绍第二种方法—— `export` 和 `require` 机制:
|
||||
|
||||
## 使用 export 和 require
|
||||
|
||||
现在,假定有两个插件 `pluginA` 和 `pluginB`,需要在 `pluginB` 中调用 `pluginA` 中的一个变量 `varA` 和一个函数 `funcA`。
|
||||
|
||||
在上面的条件中涉及到了两种操作:一种是在 `pluginA` 的 `导出对象` 操作;而另一种是在 `pluginB` 的 `导入对象` 操作。在 `nonebot2` 中,`导出对象` 的操作用 `export` 机制来实现,`导入对象` 的操作用 `require` 机制来实现。下面,我们将逐一进行介绍。
|
||||
|
||||
:::warning 警告
|
||||
|
||||
使用这个方法进行跨插件访问时,**需要先加载`导出对象`的插件,再加载`导入对象`的插件。**
|
||||
|
||||
:::
|
||||
|
||||
### 使用 export
|
||||
|
||||
在 `pluginA` 中,我们调用 `export` 机制 `导出对象`。
|
||||
|
||||
在 `export` 机制调用前,我们需要保证导出的对象已经被定义,比如:
|
||||
|
||||
```python
|
||||
varA = "varA"
|
||||
|
||||
|
||||
def funcA():
|
||||
return "funcA"
|
||||
```
|
||||
|
||||
在确保定义之后,我们可以从 `nonebot.plugin` 导入 `export()` 方法, `export()` 方法会返回一个特殊的字典 `export`:
|
||||
|
||||
```python
|
||||
from nonebot.plugin import export
|
||||
|
||||
export=export()
|
||||
```
|
||||
|
||||
这个字典可以用来装载导出的对象,它的 key 是对象导出后的命名,value 是对象本身,我们可以直接创建新的 `key` - `value` 对导出对象:
|
||||
|
||||
```python
|
||||
export.vA = varA
|
||||
export.fA = funcA
|
||||
```
|
||||
|
||||
除此之外,也支持 `嵌套` 导出对象:
|
||||
|
||||
```python
|
||||
export.sub.vA = varA
|
||||
export.sub.fA = funcA
|
||||
```
|
||||
|
||||
特别地,对于 `函数对象` 而言,`export` 支持用 `装饰器` 的方法来导出,因此,我们可以这样定义 `funcA`:
|
||||
|
||||
```python
|
||||
@export.sub
|
||||
def funcA():
|
||||
return "funcA"
|
||||
```
|
||||
|
||||
或者:
|
||||
|
||||
```python
|
||||
@export
|
||||
def funcA():
|
||||
return "funcA"
|
||||
```
|
||||
|
||||
通过 `装饰器` 的方法导出函数时,命名固定为函数的命名,也就是说,上面的两个例子等同于:
|
||||
|
||||
```python
|
||||
export.sub.funcA = funcA
|
||||
|
||||
export.funcA = funcA
|
||||
```
|
||||
|
||||
这样,我们就成功导出 `varA` 和 `funcA` 对象了。
|
||||
|
||||
下面我们将介绍如何在 `pluginB` 中导入这些对象。
|
||||
|
||||
### 使用 require
|
||||
|
||||
在 `pluginB` 中,我们调用 `require` 机制 `导入对象`。
|
||||
|
||||
:::warning 警告
|
||||
|
||||
在导入来自其他插件的对象时, 请确保导出该对象的插件在引用该对象的插件之前加载。如果该插件并未被加载,则会尝试加载,加载失败则会返回 `None`。
|
||||
|
||||
:::
|
||||
|
||||
我们可以从 `nonebot.plugin` 中导入 `require()` 方法:
|
||||
|
||||
```python
|
||||
from nonebot.plugin import require
|
||||
```
|
||||
|
||||
`require()` 方法的参数是插件名, 它会返回在指定插件中,用 `export()` 方法创建的字典。
|
||||
|
||||
```python
|
||||
require_A = require('pluginA')
|
||||
```
|
||||
|
||||
在之前,这个字典已经存入了 `'vA'` - `varA`, `'fA'` - `funcA` 或 `'funcA'` - `funcA` 这样的 `key` - `value` 对。因此在这里我们直接用 `属性` 的方法来获取导入对象:
|
||||
|
||||
```python
|
||||
varA = require_A.vA
|
||||
funcA = require_A.fA or require_A.funcA
|
||||
```
|
||||
|
||||
这样,我们就在 `pluginB` 中成功导入了 `varA` 和 `funcA` 对象了。
|
73
website/docs/advanced/overloaded-handlers.md
Normal file
73
website/docs/advanced/overloaded-handlers.md
Normal file
@ -0,0 +1,73 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 60
|
||||
catogory: advanced
|
||||
---
|
||||
|
||||
# 事件处理函数重载
|
||||
|
||||
当我们在编写 `nonebot2` 应用时,常常会遇到这样一个问题:该怎么让同一类型的不同事件执行不同的响应逻辑?又或者如何让不同的 `adapter` 针对同一类型的事件作出不同响应?
|
||||
|
||||
针对这个问题, `nonebot2` 提供一个便捷而高效的解决方案:事件处理函数重载机制。简单地说,`handler` (事件处理函数) 会根据其参数的 `type hints` ([PEP484 类型标注](https://www.python.org/dev/peps/pep-0484/)) 来对相对应的 `adapter` 和 `Event` 进行响应,并且会忽略不符合其参数类型标注的情况。
|
||||
|
||||
必须要注意的是,该机制利用了 `inspect` 标准库获取到了事件处理函数的 `singnature` (签名) ,进一步获取到参数名称和类型标注。故而,我们在编写 `handler` 时,参数的名称和类型标注必须要符合 `T_Handler` 规定,详情可以参看 **指南** 中的[事件处理](../guide/creating-a-handler)。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
如果想了解更多关于 `inspect` 标准库的信息,可以查看[官方文档](https://docs.python.org/zh-cn/3.9/library/inspect.html)。
|
||||
|
||||
:::
|
||||
|
||||
下面,我们会以 `CQHTTP` 中的 `群聊消息事件` 和 `私聊消息事件` 为例,对该机制的应用进行简单的介绍。
|
||||
|
||||
## 一个例子
|
||||
|
||||
首先,我们需要导入需要的方法、类型。
|
||||
|
||||
```python
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, PrivateMessageEvent
|
||||
```
|
||||
|
||||
之后,我们可以注册一个 `Matcher` 来响应 `消息事件` 。
|
||||
|
||||
```python
|
||||
matcher = on_command("testoverload")
|
||||
```
|
||||
|
||||
最后, 我们编写不同的 `handler` 并编写不同的类型标注来实现事件处理函数重载:
|
||||
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent):
|
||||
await matcher.send("群聊消息事件响应成功!")
|
||||
|
||||
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot, event: PrivateMessageEvent):
|
||||
await matcher.send("私聊消息事件响应成功!")
|
||||
```
|
||||
|
||||
此时,我们可以在群聊或私聊中对我们的机器人发送 `testoverload` ,它会在不同的场景做出不同的应答。
|
||||
|
||||
这样一个简单的事件处理函数重载就完成了。
|
||||
|
||||
## 进阶
|
||||
|
||||
事件处理函数重载机制同样支持被 `matcher.got` 等装饰器装饰的函数。 例如:
|
||||
|
||||
```python
|
||||
@matcher.got("key1", prompt="群事件提问")
|
||||
async def _(bot: Bot, event: GroupMessageEvent):
|
||||
await matcher.send("群聊消息事件响应成功!")
|
||||
|
||||
|
||||
@matcher.got("key2", prompt="私聊事件提问")
|
||||
async def _(bot: Bot, event: PrivateMessageEvent):
|
||||
await matcher.send("私聊消息事件响应成功!")
|
||||
```
|
||||
|
||||
只有触发事件符合的函数才会触发装饰器。
|
99
website/docs/advanced/permission.md
Normal file
99
website/docs/advanced/permission.md
Normal file
@ -0,0 +1,99 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 30
|
||||
catogory: advanced
|
||||
---
|
||||
|
||||
# 权限控制
|
||||
|
||||
**权限控制**是机器人在实际应用中需要解决的重点问题之一,`Nonebot` 提供了十分完善且灵活的权限控制机制—— `Permission` 机制。接下来我们将对这个机制进行简单的说明。
|
||||
|
||||
## 应用
|
||||
|
||||
如同 `Rule` 一样, `Permission` 可以在[注册事件响应器](../guide/creating-a-matcher)时添加 `permission` 参数来加以应用,这样 `Nonebot` 会在事件响应时检测事件主体的权限。下面我们以 `SUPERUSER` 为例,对该机制的应用做一下介绍。
|
||||
|
||||
```python
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot import on_command
|
||||
|
||||
matcher = on_command("测试超管", permission=SUPERUSER)
|
||||
|
||||
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot):
|
||||
await matcher.send("超管命令测试成功")
|
||||
|
||||
|
||||
@matcher.got("key1", "超管提问")
|
||||
async def _(bot: Bot, event: Event):
|
||||
await matcher.send("超管命令got成功")
|
||||
```
|
||||
|
||||
在这段代码中,我们事件响应器指定了 `SUPERUSER` 这样一个权限,那么机器人只会响应超级管理员的 `测试超管` 命令,并且会响应该超级管理员的连续对话。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
在这里需要强调的是,`Permission` 与 `Rule` 的表现并不相同, `Rule` 只会在初次响应时生效,在余下的对话中并没有限制事件;但是 `Permission` 会持续生效,在连续对话中会一直对事件主体加以限制。
|
||||
|
||||
:::
|
||||
|
||||
## 进阶
|
||||
|
||||
`Permission` 除了可以在注册事件响应器时加以应用,还可以在编写事件处理函数 `handler` 时主动调用,我们可以利用这个特性在一个 `handler` 里对不同权限的事件主体进行区别响应,下面我们以 `CQHTTP` 中的 `GROUP_ADMIN` (普通管理员非群主)和 `GROUP_OWNER` 为例,说明下怎么进行主动调用。
|
||||
|
||||
```python
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot
|
||||
from nonebot.adapters.cqhttp import GroupMessageEvent
|
||||
from nonebot.adapters.cqhttp import GROUP_ADMIN, GROUP_OWNER
|
||||
|
||||
matcher = on_command("测试权限")
|
||||
|
||||
@matcher.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent):
|
||||
if await GROUP_ADMIN(bot, event):
|
||||
await matcher.send("管理员测试成功")
|
||||
elif await GROUP_OWNER(bot, event):
|
||||
await matcher.send("群主测试成功")
|
||||
else:
|
||||
await matcher.send("群员测试成功")
|
||||
|
||||
```
|
||||
|
||||
在这段代码里,我们并没有对命令的权限指定,这个命令会响应所有在群聊中的 `测试权限` 命令,但是在 `handler` 里,我们对两个 `Permission` 进行主动调用,从而可以对不同的角色进行不同的响应。
|
||||
|
||||
## 自定义
|
||||
|
||||
如同 `Rule` 一样, `Permission` 也是由非负数个 `PermissionChecker` 组成的,但只需其中一个返回 `True` 时就会匹配成功。下面则是 `PermissionChecker` 和 `Permission` 示例:
|
||||
|
||||
```python
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.permission import Permission
|
||||
|
||||
async def async_checker(bot: Bot, event: Event) -> bool:
|
||||
return True
|
||||
|
||||
def sync_checker(bot: Bot, event: Event) -> bool:
|
||||
return True
|
||||
|
||||
def check(arg1, arg2):
|
||||
|
||||
async def _checker(bot: Bot, event: Event) -> bool:
|
||||
return bool(arg1 + arg2)
|
||||
|
||||
return Permission(_checker)
|
||||
```
|
||||
|
||||
`Permission` 和 `PermissionChecker` 之间可以使用 `或 |` 互相组合:
|
||||
|
||||
```python
|
||||
from nonebot.permission import Permission
|
||||
|
||||
Permission(async_checker1) | sync_checker | async_checker2
|
||||
```
|
||||
|
||||
同样地,如果想用 `Permission(*checkers)` 包裹构造 `Permission` ,函数必须是异步的;但是在利用 `或 |` 符号连接构造时, `Nonebot` 会自动包裹同步函数为异步函数。
|
172
website/docs/advanced/runtime-hook.md
Normal file
172
website/docs/advanced/runtime-hook.md
Normal file
@ -0,0 +1,172 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 40
|
||||
catogory: advanced
|
||||
---
|
||||
|
||||
# 钩子函数
|
||||
|
||||
[`钩子编程`](https://zh.wikipedia.org/wiki/%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B)
|
||||
|
||||
> 钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。
|
||||
|
||||
在 `nonebot2` 中有一系列预定义的钩子函数,分为两类:`全局钩子函数` 和 `事件钩子函数` ,这些钩子函数可以用装饰器的形式来使用。
|
||||
|
||||
## 全局钩子函数
|
||||
|
||||
全局钩子函数是指 `nonebot2` 针对其本身运行过程的钩子函数。
|
||||
|
||||
这些钩子函数是由其后端驱动 `driver`来运行的,故需要先获得全局 `driver` 对象:
|
||||
|
||||
```python
|
||||
from nonebot import get_driver
|
||||
|
||||
|
||||
driver=get_driver()
|
||||
```
|
||||
|
||||
共分为六种函数:
|
||||
|
||||
### 启动准备
|
||||
|
||||
这个钩子函数会在 `nonebot2` 启动时运行。
|
||||
|
||||
```python
|
||||
@driver.on_startup
|
||||
async def do_something():
|
||||
pass
|
||||
```
|
||||
|
||||
### 终止处理
|
||||
|
||||
这个钩子函数会在 `nonebot2` 终止时运行。
|
||||
|
||||
```python
|
||||
@driver.on_shutdown
|
||||
async def do_something():
|
||||
pass
|
||||
```
|
||||
|
||||
### bot 连接处理
|
||||
|
||||
这个钩子函数会在 `bot` 通过 `websocket` 连接到 `nonebot2` 时运行。
|
||||
|
||||
```python
|
||||
@driver.on_bot_connect
|
||||
async def do_something(bot: Bot):
|
||||
pass
|
||||
```
|
||||
|
||||
### bot 断开处理
|
||||
|
||||
这个钩子函数会在 `bot` 断开与 `nonebot2` 的 `websocket` 连接时运行。
|
||||
|
||||
```python
|
||||
@driver.on_bot_disconnect
|
||||
async def do_something(bot: Bot):
|
||||
pass
|
||||
```
|
||||
|
||||
### bot api 调用钩子
|
||||
|
||||
这个钩子函数会在 `Bot` 调用 API 时运行。
|
||||
|
||||
```python
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
@Bot.on_calling_api
|
||||
async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
pass
|
||||
```
|
||||
|
||||
### bot api 调用后钩子
|
||||
|
||||
这个钩子函数会在 `Bot` 调用 API 后运行。
|
||||
|
||||
```python
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
@Bot.on_called_api
|
||||
async def handle_api_result(bot: Bot, exception: Optional[Exception], api: str, data: Dict[str, Any], result: Any):
|
||||
pass
|
||||
```
|
||||
|
||||
## 事件处理钩子
|
||||
|
||||
这些钩子函数指的是影响 `nonebot2` 进行 `事件处理` 的函数。
|
||||
|
||||
:::tip 提示
|
||||
|
||||
关于 `事件处理` 的流程,可以在[这里](./README.md)查阅。
|
||||
|
||||
:::
|
||||
|
||||
:::warning 注意
|
||||
|
||||
1.在事件处理钩子函数中,与 `matcher` 运行状态相关的函数将不可用,如 `matcher.finish()`
|
||||
|
||||
2.如果需要在事件处理钩子函数中打断整个对话的执行,请参考以下范例:
|
||||
|
||||
```python
|
||||
from nonebot.exception import IgnoredException
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def do_something(bot: Bot, event: Event, state: T_State):
|
||||
raise IgnoredException("reason")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
共分为四种函数:
|
||||
|
||||
### 事件预处理
|
||||
|
||||
这个钩子函数会在 `Event` 上报到 `nonebot2` 时运行
|
||||
|
||||
```python
|
||||
from nonebot.message import event_preprocessor
|
||||
|
||||
@event_preprocessor
|
||||
async def do_something(bot: Bot, event: Event, state: T_State):
|
||||
pass
|
||||
```
|
||||
|
||||
### 事件后处理
|
||||
|
||||
这个钩子函数会在 `nonebot2` 处理 `Event` 后运行
|
||||
|
||||
```python
|
||||
from nonebot.message import event_postprocessor
|
||||
|
||||
@event_postprocessor
|
||||
async def do_something(bot: Bot, event: Event, state: T_State):
|
||||
pass
|
||||
```
|
||||
|
||||
### 运行预处理
|
||||
|
||||
这个钩子函数会在 `nonebot2`运行 `matcher` 前运行。
|
||||
|
||||
```python
|
||||
from nonebot.message import run_preprocessor
|
||||
|
||||
@run_preprocessor
|
||||
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
|
||||
pass
|
||||
```
|
||||
|
||||
### 运行后处理
|
||||
|
||||
这个钩子函数会在 `nonebot2`运行 `matcher` 后运行。
|
||||
|
||||
```python
|
||||
from nonebot.message import run_postprocessor
|
||||
|
||||
@run_postprocessor
|
||||
async def do_something(matcher: Matcher, exception: Optional[Exception], bot: Bot, event: Event, state: T_State):
|
||||
pass
|
||||
```
|
144
website/docs/advanced/scheduler.md
Normal file
144
website/docs/advanced/scheduler.md
Normal file
@ -0,0 +1,144 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
|
||||
options:
|
||||
menu:
|
||||
weight: 20
|
||||
catogory: advanced
|
||||
---
|
||||
|
||||
# 定时任务
|
||||
|
||||
[`APScheduler`](https://apscheduler.readthedocs.io/en/3.x/) —— Advanced Python Scheduler
|
||||
|
||||
> Advanced Python Scheduler (APScheduler) is a Python library that lets you schedule your Python code to be executed later, either just once or periodically. You can add new jobs or remove old ones on the fly as you please. If you store your jobs in a database, they will also survive scheduler restarts and maintain their state. When the scheduler is restarted, it will then run all the jobs it should have run while it was offline.
|
||||
|
||||
## 从 NoneBot v1 迁移
|
||||
|
||||
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [商店](https://v2.nonebot.dev/store.html) 中找到它。
|
||||
|
||||
相比于 `nonebot` v1,`nonebot` v2 只需要安装插件并修改 `scheduler` 的导入方式即可完成迁移。
|
||||
|
||||
## 安装插件
|
||||
|
||||
### 通过 nb-cli
|
||||
|
||||
如正在使用 `nb-cli` 构建项目,你可以从插件市场复制安装命令或手动输入以下命令以添加 `nonebot_plugin_apscheduler`。
|
||||
|
||||
```bash
|
||||
nb plugin install nonebot_plugin_apscheduler
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
`nb-cli` 默认通过 `pypi` 安装,你可以添加命令参数 `-i [mirror]` 或 `--index [mirror]` 以使用镜像源安装。
|
||||
:::
|
||||
|
||||
### 通过 poetry
|
||||
|
||||
执行以下命令以添加 `nonebot_plugin_apscheduler`
|
||||
|
||||
```bash
|
||||
poetry add nonebot-plugin-apscheduler
|
||||
```
|
||||
|
||||
:::tip 提示
|
||||
由于稍后我们将使用 `nonebot.require()` 方法进行导入,所以无需额外的 `nonebot.load_plugin()`
|
||||
:::
|
||||
|
||||
## 快速上手
|
||||
|
||||
1. 在需要设置定时任务的插件中,通过 `nonebot.require` 从 `nonebot_plugin_apscheduler` 导入 `scheduler` 对象
|
||||
|
||||
2. 在该对象的基础上,根据 `APScheduler` 的使用方法进一步配置定时任务
|
||||
|
||||
将上述步骤归纳为最小实现的代码如下:
|
||||
|
||||
```python
|
||||
from nonebot import require
|
||||
|
||||
scheduler = require("nonebot_plugin_apscheduler").scheduler
|
||||
|
||||
@scheduler.scheduled_job("cron", hour="*/2", id="xxx", args=[1], kwargs={"arg2": 2})
|
||||
async def run_every_2_hour(arg1, arg2):
|
||||
pass
|
||||
|
||||
scheduler.add_job(run_every_day_from_program_start, "interval", days=1, id="xxx")
|
||||
```
|
||||
|
||||
## 分步进行
|
||||
|
||||
### 导入 scheduler 对象
|
||||
|
||||
为了使插件能够实现定时任务,需要先将 `scheduler` 对象导入插件。
|
||||
|
||||
`nonebot2` 提供了 `nonebot.require` 方法来实现导入其他插件的内容,此处我们使用这个方法来导入 `scheduler` 对象。
|
||||
|
||||
`nonebot` 使用的 `scheduler` 对象为 `AsyncScheduler` 。
|
||||
|
||||
> 使用该方法传入的插件本身也需要有对应实现,关于该方法的更多介绍可以参阅 [这里](./export-and-require.md)
|
||||
|
||||
```python
|
||||
from nonebot import require
|
||||
|
||||
scheduler = require("nonebot_plugin_apscheduler").scheduler
|
||||
```
|
||||
|
||||
### 编写定时任务
|
||||
|
||||
由于本部分为标准的通过 `APScheduler` 配置定时任务,有关指南请参阅 [APScheduler 官方文档](https://apscheduler.readthedocs.io/en/3.x/userguide.html#adding-jobs)。
|
||||
|
||||
### 配置插件选项
|
||||
|
||||
根据项目的 `.env` 文件设置,向 `.env.*` 或 `bot.py` 文件添加 `nonebot_plugin_apscheduler` 的可选配置项
|
||||
|
||||
:::warning 注意
|
||||
`.env.*` 文件的编写应遵循 nonebot2 对 `.env.*` 文件的编写要求
|
||||
:::
|
||||
|
||||
#### `apscheduler_autostart`
|
||||
|
||||
类型:`bool`
|
||||
|
||||
默认值:`True`
|
||||
|
||||
是否自动启动 `APScheduler`。
|
||||
|
||||
对于大多数情况,我们需要在 `nonebot2` 项目被启动时启动定时任务,则此处设为 `true`
|
||||
|
||||
##### 在 `.env` 中添加
|
||||
|
||||
```bash
|
||||
APSCHEDULER_AUTOSTART=true
|
||||
```
|
||||
|
||||
##### 在 `bot.py` 中添加
|
||||
|
||||
```python
|
||||
nonebot.init(apscheduler_autostart=True)
|
||||
```
|
||||
|
||||
#### `apscheduler_config`
|
||||
|
||||
类型:`dict`
|
||||
|
||||
默认值:`{"apscheduler.timezone": "Asia/Shanghai"}`
|
||||
|
||||
`APScheduler` 相关配置。修改/增加其中配置项需要确保 `prefix: apscheduler`。
|
||||
|
||||
对于 `APScheduler` 的相关配置,请参阅 [scheduler-config](https://apscheduler.readthedocs.io/en/3.x/userguide.html#scheduler-config) 和 [BaseScheduler](https://apscheduler.readthedocs.io/en/3.x/modules/schedulers/base.html#apscheduler.schedulers.base.BaseScheduler)
|
||||
|
||||
> 官方文档在绝大多数时候能提供最准确和最具时效性的指南
|
||||
|
||||
##### 在 `.env` 中添加
|
||||
|
||||
```bash
|
||||
APSCHEDULER_CONFIG={"apscheduler.timezone": "Asia/Shanghai"}
|
||||
```
|
||||
|
||||
##### 在 `bot.py` 中添加
|
||||
|
||||
```python
|
||||
nonebot.init(apscheduler_config={
|
||||
"apscheduler.timezone": "Asia/Shanghai"
|
||||
})
|
||||
```
|
Reference in New Issue
Block a user