mirror of
https://github.com/nonebot/nonebot2.git
synced 2025-07-28 16:51:26 +00:00
🔖 Release 2.1.2
This commit is contained in:
131
website/versioned_docs/version-2.1.2/appendices/api-calling.mdx
Normal file
131
website/versioned_docs/version-2.1.2/appendices/api-calling.mdx
Normal file
@ -0,0 +1,131 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
description: 使用平台接口,完成更多功能
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 50
|
||||
---
|
||||
|
||||
# 使用平台接口
|
||||
|
||||
import Messenger from "@/components/Messenger";
|
||||
|
||||
在 NoneBot 中,除了使用事件响应器操作发送文本消息外,我们还可以直接通过使用协议适配器提供的方法来使用平台特定的接口,完成发送特殊消息、获取信息等其他平台提供的功能。同时,在部分无法使用事件响应器的情况中,例如[定时任务](../best-practice/scheduler.md),我们也可以使用平台接口来完成需要的功能。
|
||||
|
||||
## 发送平台特殊消息
|
||||
|
||||
在之前的章节中,我们介绍了如何向用户发送文本消息以及[如何处理平台消息](../tutorial/message.md),现在我们来向用户发送平台特殊消息。
|
||||
|
||||
:::caution 注意
|
||||
在以下的示例中,我们将使用 `Console` 协议适配器来演示如何发送平台消息。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。
|
||||
:::
|
||||
|
||||
```python {4,7-17} title=weather/__init__.py
|
||||
import inspect
|
||||
from nonebot.adapters.console import MessageSegment
|
||||
|
||||
@weather.got("location", prompt=MessageSegment.emoji("question") + "请输入地名")
|
||||
async def got_location(location: str = ArgPlainText()):
|
||||
result = await weather.send(
|
||||
MessageSegment.markdown(
|
||||
inspect.cleandoc(
|
||||
f"""
|
||||
# {location}
|
||||
|
||||
- 今天
|
||||
|
||||
⛅ 多云 20℃~24℃
|
||||
"""
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
<Messenger
|
||||
msgs={[
|
||||
{ position: "right", msg: "/天气" },
|
||||
{ position: "left", msg: "❓请输入地名" },
|
||||
{ position: "right", msg: "北京" },
|
||||
{
|
||||
position: "left",
|
||||
monospace: true,
|
||||
msg: "┏━━━━━━━━━━━━━━━━┓\n┃ 北京 ┃\n┗━━━━━━━━━━━━━━━━┛\n• 今天\n⛅ 多云 20℃~24℃",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
在上面的示例中,我们使用了 `Console` 协议适配器提供的 `MessageSegment` 类来发送平台特定的消息 `emoji` 和 `markdown`。这两种消息可以显示在终端中,但是无法在其他平台上使用。在事件响应器操作中,我们可以使用 `str`、消息序列、消息段、消息模板四种类型来发送消息,但其中只有 `str` 和[纯文本形式的消息模板类型](../tutorial/message.md#使用消息模板)消息可以在所有平台上使用。
|
||||
|
||||
`send` 事件响应器操作实际上是由协议适配器通过调用平台 API 来实现的,通常会将 API 调用的结果作为返回值返回。
|
||||
|
||||
## 调用平台 API
|
||||
|
||||
在 NoneBot 中,我们可以通过 `Bot` 对象来调用协议适配器支持的平台 API,来完成更多的功能。
|
||||
|
||||
### 获取 Bot
|
||||
|
||||
在调用平台 API 之前,我们首先要获得 Bot 对象。有两种方式可以获得 Bot 对象。
|
||||
|
||||
在事件处理流程的上下文中,我们可以直接使用依赖注入 Bot 来获取:
|
||||
|
||||
```python {1,4} title=weather/__init__.py
|
||||
from nonebot.adapters import Bot
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def got_location(bot: Bot, location: str = ArgPlainText()):
|
||||
...
|
||||
```
|
||||
|
||||
依赖注入会确保你获得的 Bot 对象与类型注解的 Bot 类型一致。也就是说,如果你使用的是 Bot 基类,将会允许任何平台的 Bot 对象;如果你使用的是平台特定的 Bot 类型,将会只允许该平台的 Bot 对象,其他类型的 Bot 将会跳过这个事件处理函数。更多详情请参考[事件处理重载](./overload.md)。
|
||||
|
||||
在其他情况下,我们可以通过 NoneBot 提供的方法来获取 Bot 对象,这些方法将会在[使用适配器](../advanced/adapter.md#获取-bot-对象)中详细介绍:
|
||||
|
||||
```python {4,6}
|
||||
from nonebot import get_bot
|
||||
|
||||
# 获取当前所有 Bot 中的第一个
|
||||
bot = get_bot()
|
||||
# 获取指定 ID 的 Bot
|
||||
bot = get_bot("bot_id")
|
||||
```
|
||||
|
||||
### 调用 API
|
||||
|
||||
在获得 Bot 对象后,我们可以通过 Bot 的实例方法来调用平台 API:
|
||||
|
||||
```python {2,5}
|
||||
# 通过 bot.api_name(**kwargs) 的方法调用 API
|
||||
result = await bot.get_user_info(user_id=12345678)
|
||||
|
||||
# 通过 bot.call_api(api_name, **kwargs) 的方法调用 API
|
||||
result = await bot.call_api("get_user_info", user_id=12345678)
|
||||
```
|
||||
|
||||
:::caution 注意
|
||||
实际可以使用的 API 以及参数取决于平台提供的接口以及协议适配器的实现,请参考协议适配器以及平台文档。
|
||||
:::
|
||||
|
||||
在了解了如何调用 API 后,我们可以来改进 `weather` 插件,使得消息发送后,调用 `Console` 接口响铃提醒机器人用户:
|
||||
|
||||
```python {4,18} title=weather/__init__.py
|
||||
from nonebot.adapters.console import Bot, MessageSegment
|
||||
|
||||
@weather.got("location", prompt=MessageSegment.emoji("question") + "请输入地名")
|
||||
async def got_location(bot: Bot, location: str = ArgPlainText()):
|
||||
await weather.send(
|
||||
MessageSegment.markdown(
|
||||
inspect.cleandoc(
|
||||
f"""
|
||||
# {location}
|
||||
|
||||
- 今天
|
||||
|
||||
⛅ 多云 20℃~24℃
|
||||
"""
|
||||
)
|
||||
)
|
||||
)
|
||||
await bot.bell()
|
||||
```
|
591
website/versioned_docs/version-2.1.2/appendices/config.mdx
Normal file
591
website/versioned_docs/version-2.1.2/appendices/config.mdx
Normal file
@ -0,0 +1,591 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: 读取用户配置来控制插件行为
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 10
|
||||
---
|
||||
|
||||
# 配置
|
||||
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
配置是项目中非常重要的一部分,为了方便我们控制机器人的行为,NoneBot 提供了一套配置系统。下面我们将会补充[指南](../quick-start.mdx)中的天气插件,使其能够读取用户配置。在这之前,我们需要先了解一下配置系统,如果你已经了解了 NoneBot 中的配置方法,可以跳转到[编写插件配置](#插件配置)。
|
||||
|
||||
NoneBot 使用 [`pydantic`](https://docs.pydantic.dev/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取 dotenv 配置文件以及环境变量,从而控制机器人行为。配置文件需要符合 dotenv 格式,复杂数据类型需使用 JSON 格式或 [pydantic 支持格式](https://docs.pydantic.dev/usage/types/)填写。
|
||||
|
||||
NoneBot 内置的配置项列表及含义可以在[内置配置项](#内置配置项)中查看。
|
||||
|
||||
## 配置项的加载
|
||||
|
||||
在 NoneBot 中,我们可以把配置途径分为 **直接传入**、**系统环境变量**、**dotenv 配置文件** 三种,其加载优先级依次由高到低。
|
||||
|
||||
### 直接传入
|
||||
|
||||
在 NoneBot 初始化的过程中,可以通过 `nonebot.init()` 传入任意合法的 Python 变量,也可以在初始化完成后直接赋值。
|
||||
|
||||
通常,在初始化前的传参会在机器人的入口文件(如 `bot.py`)中进行,而初始化后的赋值可以在任何地方进行。
|
||||
|
||||
```python {4,8,9} title=bot.py
|
||||
import nonebot
|
||||
|
||||
# 初始化时
|
||||
nonebot.init(custom_config1="config on init")
|
||||
|
||||
# 初始化后
|
||||
config = nonebot.get_driver().config
|
||||
config.custom_config1 = "changed after init"
|
||||
config.custom_config2 = "new config after init"
|
||||
```
|
||||
|
||||
### 系统环境变量
|
||||
|
||||
在 dotenv 配置文件中定义的配置项,也会在环境变量中进行寻找。如果在环境变量中发现同名配置项(大小写不敏感),将会覆盖 dotenv 中所填值。
|
||||
|
||||
例如,在 dotenv 配置文件中存在配置项 `custom_config`:
|
||||
|
||||
```dotenv
|
||||
CUSTOM_CONFIG=config in dotenv
|
||||
```
|
||||
|
||||
同时,设置环境变量:
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set CUSTOM_CONFIG "config in environment variables"
|
||||
# linux/macOS
|
||||
export CUSTOM_CONFIG="config in environment variables"
|
||||
```
|
||||
|
||||
那最终 NoneBot 所读取的内容为环境变量中的内容,即 `config in environment variables`。
|
||||
|
||||
:::caution 注意
|
||||
NoneBot 不会自发读取未被定义的配置项的环境变量,如果需要读取某一环境变量需要在 dotenv 配置文件中进行声明。
|
||||
:::
|
||||
|
||||
### dotenv 配置文件
|
||||
|
||||
dotenv 是一种便捷的跨平台配置通用模式,也是我们推荐的配置方式。
|
||||
|
||||
NoneBot 在启动时将会从系统环境变量或者 `.env` 文件中寻找配置项 `ENVIRONMENT` (大小写不敏感),默认值为 `prod`。这将决定 NoneBot 后续进一步加载环境配置的文件路径 `.env.{ENVIRONMENT}`。
|
||||
|
||||
#### 配置项解析
|
||||
|
||||
dotenv 文件中的配置值使用 JSON 进行解析。如果配置项值无法被解析,将作为**字符串**处理。例如:
|
||||
|
||||
```dotenv
|
||||
STRING_CONFIG=some string
|
||||
LIST_CONFIG=[1, 2, 3]
|
||||
DICT_CONFIG={"key": "value"}
|
||||
MULTILINE_CONFIG='
|
||||
[
|
||||
{
|
||||
"item_key": "item_value"
|
||||
}
|
||||
]
|
||||
'
|
||||
EMPTY_CONFIG=
|
||||
NULL_CONFIG
|
||||
```
|
||||
|
||||
将被解析为:
|
||||
|
||||
```python
|
||||
dotenv_config = {
|
||||
"string_config": "some string",
|
||||
"list_config": [1, 2, 3],
|
||||
"dict_config": {"key": "value"},
|
||||
"multiline_config": [{"item_key": "item_value"}],
|
||||
"empty_config": "",
|
||||
"null_config": None
|
||||
}
|
||||
```
|
||||
|
||||
特别的,NoneBot 支持使用 `env_nested_delimiter` 配置嵌套字典,在层与层之间使用 `__` 分隔即可:
|
||||
|
||||
```dotenv
|
||||
DICT={"k1": "v1", "k2": null}
|
||||
DICT__K2=v2
|
||||
DICT__K3=v3
|
||||
DICT__INNER__K4=v4
|
||||
```
|
||||
|
||||
将被解析为:
|
||||
|
||||
```python
|
||||
dotenv_config = {
|
||||
"dict": {
|
||||
"k1": "v1",
|
||||
"k2": "v2",
|
||||
"k3": "v3",
|
||||
"inner": {
|
||||
"k4": "v4"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### .env 文件
|
||||
|
||||
`.env` 文件是基础配置文件,该文件中的配置项在不同环境下都会被加载,但会被 `.env.{ENVIRONMENT}` 文件中的配置所**覆盖**。
|
||||
|
||||
我们可以在 `.env` 文件中写入当前的环境信息:
|
||||
|
||||
```dotenv
|
||||
ENVIRONMENT=dev
|
||||
COMMON_CONFIG=common config # 这个配置项在任何环境中都会被加载
|
||||
```
|
||||
|
||||
这样,我们在启动 NoneBot 时就会从 `.env.dev` 文件中加载剩余配置项。
|
||||
|
||||
:::tip 提示
|
||||
在生产环境中,可以通过设置环境变量 `ENVIRONMENT=prod` 来确保 NoneBot 读取正确的环境配置。
|
||||
:::
|
||||
|
||||
#### .env.{ENVIRONMENT} 文件
|
||||
|
||||
`.env.{ENVIRONMENT}` 文件类似于预设,可以让我们在多套不同的配置方案中灵活切换,默认 NoneBot 会读取 `.env.prod` 配置。如果你使用了 `nb-cli` 创建 `simple` 项目,那么将含有两套预设配置:`.env.dev` 和 `.env.prod`。
|
||||
|
||||
在 NoneBot 初始化时,可以指定加载某个环境配置文件:
|
||||
|
||||
```python
|
||||
nonebot.init(_env_file=".env.dev")
|
||||
```
|
||||
|
||||
这将忽略在 `.env` 文件或环境变量中指定的 `ENVIRONMENT` 配置项。
|
||||
|
||||
## 读取配置项
|
||||
|
||||
NoneBot 的全局配置对象可以通过 `driver` 获取,如:
|
||||
|
||||
```python
|
||||
import nonebot
|
||||
|
||||
config = nonebot.get_driver().config
|
||||
```
|
||||
|
||||
如果我们需要获取某个配置项,可以直接通过 `config` 对象的属性访问:
|
||||
|
||||
```python
|
||||
superusers = config.superusers
|
||||
```
|
||||
|
||||
如果配置项不存在,将会抛出异常。
|
||||
|
||||
## 插件配置
|
||||
|
||||
在一个涉及大量配置项的项目中,通过直接读取配置项的方式显然并不高效。同时,由于额外的全局配置项没有预先定义,开发时编辑器将无法提示字段与类型,并且运行时没有对配置项直接进行合法性检查。那么就需要一种方式来规范定义插件配置项。
|
||||
|
||||
在 NoneBot 中,我们使用强大高效的 `pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在 `weather` 插件目录中新建 `config.py` 来定义一个模型:
|
||||
|
||||
```python title=weather/config.py
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
class Config(BaseModel):
|
||||
weather_api_key: str
|
||||
weather_command_priority: int = 10
|
||||
weather_plugin_enabled: bool = True
|
||||
|
||||
@validator("weather_command_priority")
|
||||
def check_priority(cls, v):
|
||||
if isinstance(v, int) and v >= 1:
|
||||
return v
|
||||
raise ValueError("weather command priority must be an integer and greater than 1")
|
||||
```
|
||||
|
||||
在 `config.py` 中,我们定义了一个 `Config` 类,它继承自 `pydantic.BaseModel`,并定义了一些配置项。在 `Config` 类中,我们还定义了一个 `check_priority` 方法,它用于检查 `weather_command_priority` 配置项的合法性。更多关于 `pydantic` 的编写方式,可以参考 [pydantic 官方文档](https://docs.pydantic.dev/)。
|
||||
|
||||
在定义好配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型并使用:
|
||||
|
||||
```python {5,11} title=weather/__init__.py
|
||||
from nonebot import get_driver
|
||||
|
||||
from .config import Config
|
||||
|
||||
plugin_config = Config.parse_obj(get_driver().config)
|
||||
|
||||
weather = on_command(
|
||||
"天气",
|
||||
rule=to_me(),
|
||||
aliases={"weather", "查天气"},
|
||||
priority=plugin_config.weather_command_priority,
|
||||
block=True,
|
||||
)
|
||||
```
|
||||
|
||||
然后,我们便可以从 `plugin_config` 中读取配置了,例如 `plugin_config.weather_api_key`。
|
||||
|
||||
这种方式可以简洁、高效地读取配置项,同时也可以设置默认值或者在运行时对配置项进行合法性检查,防止由于配置项导致的插件出错等情况出现。
|
||||
|
||||
:::tip 提示
|
||||
发布插件应该为自身的事件响应器提供可配置的优先级,以便插件使用者可以自定义多个插件间的响应顺序。
|
||||
:::
|
||||
|
||||
由于插件配置项是从全局配置中读取的,通常我们需要在配置项名称前面添加前缀名,以防止配置项冲突。例如在上方的示例中,我们就添加了配置项前缀 `weather_`。但是这样会导致在使用配置项时过长的变量名,因此我们可以使用 `pydantic` 的 `alias` 或者通过配置 scope 来简化配置项名称。这里我们以 scope 配置为例:
|
||||
|
||||
```python title=weather/config.py
|
||||
from pydantic import BaseModel
|
||||
|
||||
class ScopedConfig(BaseModel):
|
||||
api_key: str
|
||||
command_priority: int = 10
|
||||
plugin_enabled: bool = True
|
||||
|
||||
class Config(BaseModel):
|
||||
weather: ScopedConfig
|
||||
```
|
||||
|
||||
```python title=weather/__init__.py
|
||||
from nonebot import get_driver
|
||||
|
||||
from .config import Config
|
||||
|
||||
plugin_config = Config.parse_obj(get_driver().config).weather
|
||||
```
|
||||
|
||||
这样我们就可以省略插件配置项名称中的前缀 `weather_` 了。但需要注意的是,如果我们使用了 scope 配置,那么在配置文件中也需要使用 [`env_nested_delimiter` 格式](#配置项解析),例如:
|
||||
|
||||
```dotenv
|
||||
WEATHER__API_KEY=123456
|
||||
WEATHER__COMMAND_PRIORITY=10
|
||||
```
|
||||
|
||||
## 内置配置项
|
||||
|
||||
配置项 API 文档可以前往 [Config 类](../api/config.md#Config)查看。
|
||||
|
||||
### Driver
|
||||
|
||||
- **类型**: `str`
|
||||
- **默认值**: `"~fastapi"`
|
||||
|
||||
NoneBot 运行所使用的驱动器。具体配置方法可以参考[安装驱动器](../tutorial/store.mdx#安装驱动器)和[选择驱动器](../advanced/driver.md)。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
DRIVER=~fastapi+~httpx+~websockets
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set DRIVER '~fastapi+~httpx+~websockets'
|
||||
# linux/macOS
|
||||
export DRIVER='~fastapi+~httpx+~websockets'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(driver="~fastapi+~httpx+~websockets")
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Host
|
||||
|
||||
- **类型**: `IPvAnyAddress`
|
||||
- **默认值**: `127.0.0.1`
|
||||
|
||||
当 NoneBot 作为服务端时,监听的 IP / 主机名。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
HOST=127.0.0.1
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set HOST '127.0.0.1'
|
||||
# linux/macOS
|
||||
export HOST='127.0.0.1'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(host="127.0.0.1")
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Port
|
||||
|
||||
- **类型**: `int` (1 ~ 65535)
|
||||
- **默认值**: `8080`
|
||||
|
||||
当 NoneBot 作为服务端时,监听的端口。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
PORT=8080
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set PORT '8080'
|
||||
# linux/macOS
|
||||
export PORT='8080'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(port=8080)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Log Level
|
||||
|
||||
- **类型**: `int | str`
|
||||
- **默认值**: `INFO`
|
||||
|
||||
NoneBot 日志输出等级,可以为 `int` 类型等级或等级名称。具体等级对照表参考 [loguru 日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
|
||||
|
||||
:::tip 提示
|
||||
日志等级名称应为大写,如 `INFO`。
|
||||
:::
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
LOG_LEVEL=DEBUG
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set LOG_LEVEL 'DEBUG'
|
||||
# linux/macOS
|
||||
export LOG_LEVEL='DEBUG'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(log_level="DEBUG")
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### API Timeout
|
||||
|
||||
- **类型**: `float | None`
|
||||
- **默认值**: `30.0`
|
||||
|
||||
调用平台接口的超时时间,单位为秒。`None` 表示不设置超时时间。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
API_TIMEOUT=10.0
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set API_TIMEOUT '10.0'
|
||||
# linux/macOS
|
||||
export API_TIMEOUT='10.0'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(api_timeout=10.0)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### SuperUsers
|
||||
|
||||
- **类型**: `set[str]`
|
||||
- **默认值**: `set()`
|
||||
|
||||
机器人超级用户,可以使用权限 [`SUPERUSER`](../api/permission.md#SUPERUSER)。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
SUPERUSERS=["123123123"]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set SUPERUSERS '["123123123"]'
|
||||
# linux/macOS
|
||||
export SUPERUSERS='["123123123"]'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(superusers={"123123123"})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Nickname
|
||||
|
||||
- **类型**: `set[str]`
|
||||
- **默认值**: `set()`
|
||||
|
||||
机器人昵称,通常协议适配器会根据用户是否 @bot 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
NICKNAME=["bot"]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set NICKNAME '["bot"]'
|
||||
# linux/macOS
|
||||
export NICKNAME='["bot"]'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(nickname={"bot"})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Command Start 和 Command Separator
|
||||
|
||||
- **类型**: `set[str]`
|
||||
- **默认值**:
|
||||
- Command Start: `{"/"}`
|
||||
- Command Separator: `{"."}`
|
||||
|
||||
命令消息的起始符和分隔符。用于 [`command`](../advanced/matcher.md#command) 规则。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
COMMAND_START=["/", ""]
|
||||
COMMAND_SEP=[".", " "]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set COMMAND_START '["/", ""]'
|
||||
set COMMAND_SEP '[".", " "]'
|
||||
# linux/macOS
|
||||
export COMMAND_START='["/", ""]'
|
||||
export COMMAND_SEP='[".", " "]'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(command_start={"/", ""}, command_sep={".", " "})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Session Expire Timeout
|
||||
|
||||
- **类型**: `timedelta`
|
||||
- **默认值**: `timedelta(minutes=2)`
|
||||
|
||||
用户会话超时时间,配置格式参考 [Datetime Types](https://docs.pydantic.dev/usage/types/#datetime-types),可以为单位为秒的 `int | float` 等。
|
||||
|
||||
<Tabs groupId="configMethod">
|
||||
<TabItem value="dotenv" label="dotenv" default>
|
||||
|
||||
```dotenv
|
||||
SESSION_EXPIRE_TIMEOUT=120
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="env" label="系统环境变量">
|
||||
|
||||
```bash
|
||||
# windows
|
||||
set SESSION_EXPIRE_TIMEOUT '120'
|
||||
# linux/macOS
|
||||
export SESSION_EXPIRE_TIMEOUT='120'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="init" label="直接传入">
|
||||
|
||||
```python title=bot.py
|
||||
import nonebot
|
||||
|
||||
nonebot.init(session_expire_timeout=120)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
102
website/versioned_docs/version-2.1.2/appendices/log.md
Normal file
102
website/versioned_docs/version-2.1.2/appendices/log.md
Normal file
@ -0,0 +1,102 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
description: 记录与控制日志
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 70
|
||||
---
|
||||
|
||||
# 日志
|
||||
|
||||
无论是在开发还是在生产环境中,日志都是一个重要的功能,可以帮助我们了解运行状况、排查问题等。虽然我们可以使用 `print` 来将需要的信息输出到控制台,但是这种方式难以控制,而且不利于日志的归档、分析等。NoneBot 使用优秀的 [Loguru](https://loguru.readthedocs.io/) 库来进行日志记录。
|
||||
|
||||
## 记录日志
|
||||
|
||||
我们可以从 NoneBot 中导入 `logger` 对象,然后使用 `logger` 对象的方法来记录日志。
|
||||
|
||||
```python
|
||||
from nonebot import logger
|
||||
|
||||
logger.trace("This is a trace message")
|
||||
logger.debug("This is a debug message")
|
||||
logger.info("This is an info message")
|
||||
logger.success("This is a success message")
|
||||
logger.warning("This is a warning message")
|
||||
logger.error("This is an error message")
|
||||
logger.critical("This is a critical message")
|
||||
```
|
||||
|
||||
我们仅需一行代码即可记录对应级别的日志。日志可以通过配置 [`LOG_LEVEL` 配置项](./config.mdx#log-level)来过滤输出等级,控制台中仅会输出大于等于 `LOG_LEVEL` 的日志。默认的 `LOG_LEVEL` 为 `INFO`,即只会输出 `INFO`、`SUCCESS`、`WARNING`、`ERROR`、`CRITICAL` 级别的日志。
|
||||
|
||||
如果需要记录 `Exception traceback` 日志,可以向 `logger` 添加 `exception` 选项:
|
||||
|
||||
```python {4}
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError:
|
||||
logger.opt(exception=True).error("ZeroDivisionError")
|
||||
```
|
||||
|
||||
如果需要输出彩色日志,可以向 `logger` 添加 `colors` 选项:
|
||||
|
||||
```python
|
||||
logger.opt(colors=True).warning("We got a <red>BIG</red> problem")
|
||||
```
|
||||
|
||||
更多日志记录方法请参考 [Loguru 文档](https://loguru.readthedocs.io/)。
|
||||
|
||||
## 自定义日志输出
|
||||
|
||||
NoneBot 在启动时会添加一个默认的日志处理器,该处理器会将日志输出到**stdout**,并且根据 `LOG_LEVEL` 配置项过滤日志等级。
|
||||
|
||||
默认的日志格式为:
|
||||
|
||||
```text
|
||||
<g>{time:MM-DD HH:mm:ss}</g> [<lvl>{level}</lvl>] <c><u>{name}</u></c> | {message}
|
||||
```
|
||||
|
||||
我们可以从 `nonebot.log` 模块导入以使用 NoneBot 的默认格式和过滤器:
|
||||
|
||||
```python
|
||||
from nonebot.log import default_format, default_filter
|
||||
```
|
||||
|
||||
如果需要自定义日志格式,我们需要移除 NoneBot 默认的日志处理器并添加新的日志处理器。例如,在机器人入口文件中 `nonebot.init` 之前添加以下内容:
|
||||
|
||||
```python title=bot.py
|
||||
from nonebot.log import logger_id
|
||||
|
||||
# 移除 NoneBot 默认的日志处理器
|
||||
logger.remove(logger_id)
|
||||
# 添加新的日志处理器
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=True,
|
||||
format="<g>{time:MM-DD HH:mm:ss}</g> [<lvl>{level}</lvl>] <c><u>{name}</u></c> | {message}",
|
||||
filter=default_filter
|
||||
)
|
||||
```
|
||||
|
||||
如果想要输出日志到文件,我们可以使用 `logger.add` 方法添加文件处理器:
|
||||
|
||||
```python title=bot.py
|
||||
logger.add("error.log", level="ERROR", format=default_format, rotation="1 week")
|
||||
```
|
||||
|
||||
更多日志处理器的使用方法请参考 [Loguru 文档](https://loguru.readthedocs.io/)。
|
||||
|
||||
## 重定向 logging 日志
|
||||
|
||||
`logging` 是 Python 标准库中的日志模块,NoneBot 提供了一个 logging handler 用于将 `logging` 日志重定向到 `loguru` 处理。
|
||||
|
||||
```python
|
||||
from nonebot.log import LoguruHandler
|
||||
|
||||
# root logger 添加 LoguruHandler
|
||||
logging.basicConfig(handlers=[LoguruHandler()])
|
||||
# 或者为其他 logging.Logger 添加 LoguruHandler
|
||||
logger.addHandler(LoguruHandler())
|
||||
```
|
74
website/versioned_docs/version-2.1.2/appendices/overload.md
Normal file
74
website/versioned_docs/version-2.1.2/appendices/overload.md
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
description: 根据事件类型进行不同的处理
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 80
|
||||
---
|
||||
|
||||
# 事件类型与重载
|
||||
|
||||
在之前的示例中,我们已经了解了如何[获取事件信息](../tutorial/event-data.mdx)以及[使用平台接口](./api-calling.mdx)。但是,事件信息通常不仅仅包含消息这一个内容,还有其他平台提供的信息,例如消息发送时间、消息发送者等等。同时,在使用平台接口时,我们需要确保使用的**平台接口**与所要发送的**平台类型**一致,对不同类型的事件需要做出不同的处理。在本章节中,我们将介绍如何获取事件更多的信息以及根据事件类型进行不同的处理。
|
||||
|
||||
## 事件类型
|
||||
|
||||
在 NoneBot 中,事件均是 `nonebot.adapters.Event` 基类的子类型,基类对一些必要的属性进行了抽象,子类型则根据不同的平台进行了实现。在[自定义权限](./permission.mdx#自定义权限)一节中,我们就使用了 `Event` 的抽象方法 `get_user_id` 来获取事件发送者 ID,这个方法由协议适配器进行了实现,返回机器人用户对应的平台 ID。更多的基类抽象方法可以在[使用适配器](../advanced/adapter.md#获取事件通用信息)中查看。
|
||||
|
||||
既然事件是基类的子类型,我们实际可以获得的信息通常多于基类抽象方法所提供的。如果我们不满足于基类能获得的信息,我们可以小小的修改一下事件处理函数的事件参数类型注解,使其变为子类型,这样我们就可以通过协议适配器定义的子类型来获取更多的信息。我们以 `Console` 协议适配器为例:
|
||||
|
||||
```python {4} title=weather/__init__.py
|
||||
from nonebot.adapters.console import MessageEvent
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def got_location(event: MessageEvent, location: str = ArgPlainText()):
|
||||
await weather.finish(f"{event.time.strftime('%Y-%m-%d')} {location} 的天气是...")
|
||||
```
|
||||
|
||||
在上面的代码中,我们获取了 `Console` 协议适配器的消息事件提供的发送时间 `time` 属性。
|
||||
|
||||
:::caution 注意
|
||||
如果**基类**就能满足你的需求,那么就**不要修改**事件参数类型注解,这样可以使你的代码更加**通用**,可以在更多平台上运行。如何根据不同平台事件类型进行不同的处理,我们将在[重载](#重载)一节中介绍。
|
||||
:::
|
||||
|
||||
## 重载
|
||||
|
||||
我们在编写机器人时,常常会遇到这样一个问题:如何对私聊和群聊消息进行不同的处理?如何对不同平台的事件进行不同的处理?针对这些问题,NoneBot 提供了一个便捷而高效的解决方案 ── 重载。简单来说,依赖函数会根据其参数的类型注解来决定是否执行,忽略不符合其参数类型注解的情况。这样,我们就可以通过修改事件参数类型注解来实现对不同事件的处理,或者修改 `Bot` 参数类型注解来实现使用不同平台的接口。我们以 `OneBot` 协议适配器为例:
|
||||
|
||||
```python {4,8}
|
||||
from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent
|
||||
|
||||
@matcher.handle()
|
||||
async def handle_private(event: PrivateMessageEvent):
|
||||
await matcher.finish("私聊消息")
|
||||
|
||||
@matcher.handle()
|
||||
async def handle_group(event: GroupMessageEvent):
|
||||
await matcher.finish("群聊消息")
|
||||
```
|
||||
|
||||
这样,机器人用户就会在私聊和群聊中分别收到不同的回复。同样的,我们也可以通过修改 `Bot` 参数类型注解来实现使用不同平台的接口:
|
||||
|
||||
```python
|
||||
from nonebot.adapters.console import Bot as ConsoleBot
|
||||
from nonebot.adapters.onebot.v11 import Bot as OneBot
|
||||
|
||||
@matcher.handle()
|
||||
async def handle_console(bot: ConsoleBot):
|
||||
await bot.bell()
|
||||
|
||||
@matcher.handle()
|
||||
async def handle_onebot(bot: OneBot):
|
||||
await bot.send_group_message(group_id=123123, message="OneBot")
|
||||
```
|
||||
|
||||
:::caution 注意
|
||||
重载机制对所有的参数类型注解都有效,因此,依赖注入也可以使用这个特性来对不同的返回值进行处理。
|
||||
|
||||
但 Bot、Event 和 Matcher 三者的参数类型注解具有最高检查优先级,如果三者任一类型注解不匹配,那么其他依赖注入将不会执行(如:`Depends`)。
|
||||
:::
|
||||
|
||||
:::tip 提示
|
||||
如何更好地编写一个跨平台的插件,我们将在[最佳实践](../best-practice/multi-adapter.mdx)中介绍。
|
||||
:::
|
116
website/versioned_docs/version-2.1.2/appendices/permission.mdx
Normal file
116
website/versioned_docs/version-2.1.2/appendices/permission.mdx
Normal file
@ -0,0 +1,116 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
description: 控制事件响应器的权限
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 60
|
||||
---
|
||||
|
||||
# 权限控制
|
||||
|
||||
import Messenger from "@site/src/components/Messenger";
|
||||
|
||||
**权限控制**是机器人在实际应用中需要解决的重点问题之一,NoneBot 提供了灵活的权限控制机制 —— `Permission`。
|
||||
|
||||
类似于响应规则 `Rule`,`Permission` 是由非负整数个 `PermissionChecker` 所共同组成的**用于筛选事件**的对象。但需要特别说明的是,权限和响应规则有如下区别:
|
||||
|
||||
1. 权限检查**先于**响应规则检查
|
||||
2. `Permission` 只需**其中一个** `PermissionChecker` 返回 `True` 时就会检查通过
|
||||
3. 权限检查进行时,上下文中并不存在会话状态 `state`
|
||||
4. `Rule` 仅在**初次触发**事件响应器时进行检查,在余下的会话中并不会限制事件;而 `Permission` 会**持续生效**,在连续对话中一直对事件主体加以限制。
|
||||
|
||||
## 基础使用
|
||||
|
||||
通常情况下,`Permission` 更侧重于对于**触发事件的机器人用户**的筛选,例如由 NoneBot 自身提供的 `SUPERUSER` 权限,便是筛选出会话发起者是否为超级用户。它可以对输入的用户进行鉴别,如果符合要求则会被认为通过并返回 `True`,反之则返回 `False`。
|
||||
|
||||
简单来说,`Permission` 是一个用于筛选出符合要求的用户的机制,可以通过 `Permission` 精确的控制响应对象的覆盖范围,从而拒绝掉我们所不希望的事件。
|
||||
|
||||
例如,我们可以在 `weather` 插件中添加一个超级用户可用的指令:
|
||||
|
||||
```python {2,8} title=weather/__init__.py
|
||||
from typing import Tuple
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
manage = on_command(
|
||||
("天气", "启用"),
|
||||
rule=to_me(),
|
||||
aliases={("天气", "禁用")},
|
||||
permission=SUPERUSER,
|
||||
)
|
||||
|
||||
@manage.handle()
|
||||
async def control(cmd: Tuple[str, str] = Command()):
|
||||
_, action = cmd
|
||||
if action == "启用":
|
||||
plugin_config.weather_plugin_enabled = True
|
||||
elif action == "禁用":
|
||||
plugin_config.weather_plugin_enabled = False
|
||||
await manage.finish(f"天气插件已{action}")
|
||||
```
|
||||
|
||||
如上方示例所示,在注册事件响应器时,我们设置了 `permission` 参数,那么这个事件处理器在触发事件前的检查阶段会对用户身份进行验证,如果不符合我们设置的条件(此处即为**超级用户**)则不会响应。此时,我们向机器人发送 `/天气.禁用` 指令,机器人不会有任何响应,因为我们还不是机器人的超级管理员。我们在 dotenv 文件中设置了 `SUPERUSERS` 配置项之后,机器人就会响应我们的指令了。
|
||||
|
||||
```dotenv title=.env
|
||||
SUPERUSERS=["console_user"]
|
||||
```
|
||||
|
||||
<Messenger
|
||||
msgs={[
|
||||
{ position: "right", msg: "/天气.禁用" },
|
||||
{ position: "left", msg: "天气插件已禁用" },
|
||||
{ position: "right", msg: "/天气.启用" },
|
||||
{ position: "left", msg: "天气插件已启用" },
|
||||
]}
|
||||
/>
|
||||
|
||||
## 自定义权限
|
||||
|
||||
与事件响应规则类似,`PermissionChecker` 也是一个返回值为 `bool` 类型的依赖函数,即 `PermissionChecker` 支持依赖注入。例如,我们可以限制用户的指令调用次数:
|
||||
|
||||
```python title=weather/__init__.py
|
||||
from nonebot.adapters import Event
|
||||
|
||||
fake_db: Dict[str, int] = {}
|
||||
|
||||
async def limit_permission(event: Event):
|
||||
count = fake_db.setdefault(event.get_user_id(), 100)
|
||||
if count > 0:
|
||||
fake_db[event.get_user_id()] -= 1
|
||||
return True
|
||||
return False
|
||||
|
||||
weather = on_command("天气", permission=limit_permission)
|
||||
```
|
||||
|
||||
## 权限组合
|
||||
|
||||
权限之间可以通过 `|` 运算符进行组合,使得任意一个权限检查返回 `True` 时通过。例如:
|
||||
|
||||
```python {4-6}
|
||||
perm1 = Permission(foo_checker)
|
||||
perm2 = Permission(bar_checker)
|
||||
|
||||
perm = perm1 | perm2
|
||||
perm = perm1 | bar_checker
|
||||
perm = foo_checker | perm2
|
||||
```
|
||||
|
||||
同样的,我们也无需担心组合了一个 `None` 值,`Permission` 会自动忽略 `None` 值。
|
||||
|
||||
```python
|
||||
assert (perm | None) is perm
|
||||
```
|
||||
|
||||
## 主动使用权限
|
||||
|
||||
除了在事件响应器中使用权限外,我们也可以主动使用权限来判断事件是否符合条件。例如:
|
||||
|
||||
```python {3}
|
||||
perm = Permission(some_checker)
|
||||
|
||||
result: bool = await perm(bot, event)
|
||||
```
|
||||
|
||||
我们只需要传入 `Bot` 实例、事件,`Permission` 会并发调用所有 `PermissionChecker` 进行检查,并返回结果。
|
107
website/versioned_docs/version-2.1.2/appendices/rule.md
Normal file
107
website/versioned_docs/version-2.1.2/appendices/rule.md
Normal file
@ -0,0 +1,107 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: 自定义响应规则
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 20
|
||||
---
|
||||
|
||||
# 响应规则
|
||||
|
||||
机器人在实际应用中,往往会接收到多种多样的事件类型,NoneBot 通过响应规则来控制事件的处理。
|
||||
|
||||
在[指南](../tutorial/matcher.md#为事件响应器添加参数)中,我们为 `weather` 命令添加了一个 `rule=to_me()` 参数,这个参数就是一个响应规则,确保只有在私聊或者 `@bot` 时才会响应。
|
||||
|
||||
响应规则是一个 `Rule` 对象,它由一系列的 `RuleChecker` 函数组成,每个 `RuleChecker` 函数都会检查事件是否符合条件,如果所有的检查都通过,则事件会被处理。
|
||||
|
||||
## RuleChecker
|
||||
|
||||
`RuleChecker` 是一个返回值为 `bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。我们可以根据上一节中添加的[配置项](./config.mdx#插件配置),在 `weather` 插件目录中编写一个响应规则:
|
||||
|
||||
```python {3,4} title=weather/__init__.py
|
||||
plugin_config = Config.parse_obj(get_driver().config)
|
||||
|
||||
async def is_enable() -> bool:
|
||||
return plugin_config.weather_plugin_enabled
|
||||
|
||||
weather = on_command("天气", rule=is_enable)
|
||||
```
|
||||
|
||||
在上面的代码中,我们定义了一个函数 `is_enable`,它会检查配置项 `weather_plugin_enabled` 是否为 `True`。这个函数 `is_enable` 即为一个 `RuleChecker`。
|
||||
|
||||
## Rule
|
||||
|
||||
`Rule` 是若干个 `RuleChecker` 的集合,它会并发调用每个 `RuleChecker`,只有当所有 `RuleChecker` 检查通过时匹配成功。例如:我们可以组合两个 `RuleChecker`,一个用于检查插件是否启用,一个用于检查用户是否在黑名单中:
|
||||
|
||||
```python {10}
|
||||
from nonebot.rule import Rule
|
||||
from nonebot.adapters import Event
|
||||
|
||||
async def is_enable() -> bool:
|
||||
return plugin_config.weather_plugin_enabled
|
||||
|
||||
async def is_blacklisted(event: Event) -> bool:
|
||||
return event.get_user_id() not in BLACKLIST
|
||||
|
||||
rule = Rule(is_enable, is_blacklisted)
|
||||
|
||||
weather = on_command("天气", rule=rule)
|
||||
```
|
||||
|
||||
## 合并响应规则
|
||||
|
||||
在定义响应规则时,我们可以将规则进行细分,来更好地复用规则。而在使用时,我们需要合并多个规则。除了使用 `Rule` 对象来组合多个 `RuleChecker` 外,我们还可以对 `Rule` 对象进行合并。在原 `weather` 插件中,我们可以将 `rule=to_me()` 与 `rule=is_enable` 使用 `&` 运算符合并:
|
||||
|
||||
```python {10} title=weather/__init__.py
|
||||
from nonebot.rule import to_me
|
||||
|
||||
plugin_config = Config.parse_obj(get_driver().config)
|
||||
|
||||
async def is_enable() -> bool:
|
||||
return plugin_config.weather_plugin_enabled
|
||||
|
||||
weather = on_command(
|
||||
"天气",
|
||||
rule=to_me() & is_enable,
|
||||
aliases={"weather", "查天气"},
|
||||
priority=plugin_config.weather_command_priority
|
||||
block=True,
|
||||
)
|
||||
```
|
||||
|
||||
这样,`weather` 命令就只会在插件启用且在私聊或者 `@bot` 时才会响应。
|
||||
|
||||
合并响应规则可以有多种形式,例如:
|
||||
|
||||
```python {4-6}
|
||||
rule1 = Rule(foo_checker)
|
||||
rule2 = Rule(bar_checker)
|
||||
|
||||
rule = rule1 & rule2
|
||||
rule = rule1 & bar_checker
|
||||
rule = foo_checker & rule2
|
||||
```
|
||||
|
||||
同时,我们也无需担心合并了一个 `None` 值,`Rule` 会忽略 `None` 值。
|
||||
|
||||
```python
|
||||
assert (rule & None) is rule
|
||||
```
|
||||
|
||||
## 主动使用响应规则
|
||||
|
||||
除了在事件响应器中使用响应规则外,我们也可以主动使用响应规则来判断事件是否符合条件。例如:
|
||||
|
||||
```python {3}
|
||||
rule = Rule(some_checker)
|
||||
|
||||
result: bool = await rule(bot, event, state)
|
||||
```
|
||||
|
||||
我们只需要传入 `Bot` 对象、事件和会话状态,`Rule` 会并发调用所有 `RuleChecker` 进行检查,并返回结果。
|
||||
|
||||
## 内置响应规则
|
||||
|
||||
NoneBot 内置了一些常用的响应规则,可以直接通过事件响应器辅助函数或者自行合并其他规则使用。内置响应规则列表可以参考[事件响应器进阶](../advanced/matcher.md)
|
@ -0,0 +1,397 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: 更灵活的会话控制
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 30
|
||||
---
|
||||
|
||||
# 会话控制
|
||||
|
||||
import Messenger from "@site/src/components/Messenger";
|
||||
|
||||
在[指南](../tutorial/event-data.mdx#使用依赖注入)的 `weather` 插件中,我们使用依赖注入获取了机器人用户发送的地名参数,并根据地名参数进行相应的回复。但是,一问一答的对话模式仅仅适用于简单的对话场景,如果我们想要实现更复杂的对话模式,就需要使用会话控制。
|
||||
|
||||
## 询问并获取用户输入
|
||||
|
||||
在 `weather` 插件中,我们对于用户未输入地名参数的情况直接回复了 `请输入地名` 并结束了事件流程。但是,这样用户体验并不好,需要重新输入指令和地名参数才能获取天气回复。我们现在来实现询问并获取用户地名参数的功能。
|
||||
|
||||
### 询问用户
|
||||
|
||||
我们可以使用事件响应器操作中的 `got` 装饰器来表示当前事件处理流程需要询问并获取用户输入的消息:
|
||||
|
||||
```python {6} title=weather/__init__.py
|
||||
@weather.handle()
|
||||
async def handle_function(args: Message = CommandArg()):
|
||||
if location := args.extract_plain_text():
|
||||
await weather.finish(f"今天{location}的天气是...")
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def got_location():
|
||||
...
|
||||
```
|
||||
|
||||
在上面的代码中,我们使用 `got` 事件响应器操作来向用户发送 `prompt` 消息,并等待用户的回复。用户的回复消息将会被作为 `location` 参数存储于事件响应器状态中。
|
||||
|
||||
:::tip 提示
|
||||
事件处理函数根据定义的顺序依次执行。
|
||||
:::
|
||||
|
||||
### 获取用户输入
|
||||
|
||||
在询问以及用户回复之后,我们就可以获取到我们需要的 `location` 参数了。我们使用 `ArgPlainText` 依赖注入来获取参数纯文本信息:
|
||||
|
||||
```python {9} title=weather/__init__.py
|
||||
from nonebot.params import ArgPlainText
|
||||
|
||||
@weather.handle()
|
||||
async def handle_function(args: Message = CommandArg()):
|
||||
if location := args.extract_plain_text():
|
||||
await weather.finish(f"今天{location}的天气是...")
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def got_location(location: str = ArgPlainText()):
|
||||
await weather.finish(f"今天{location}的天气是...")
|
||||
```
|
||||
|
||||
<Messenger
|
||||
msgs={[
|
||||
{ position: "right", msg: "/天气" },
|
||||
{ position: "left", msg: "请输入地名" },
|
||||
{ position: "right", msg: "北京" },
|
||||
{ position: "left", msg: "今天北京的天气是..." },
|
||||
]}
|
||||
/>
|
||||
|
||||
在上面的代码中,我们在 `got_location` 函数中定义了一个依赖注入参数 `location`,他的值将会是用户回复的消息纯文本信息。获取到用户输入的地名参数后,我们就可以进行天气查询并回复了。
|
||||
|
||||
:::tip 提示
|
||||
如果想要获取用户回复的消息对象 `Message` ,可以使用 `Arg` 依赖注入。
|
||||
:::
|
||||
|
||||
### 跳过询问
|
||||
|
||||
在上面的代码中,如果用户在输入天气指令时,同时提供了地名参数,我们直接回复了天气信息,这部分的逻辑是和询问用户地名参数之后的逻辑一致的。如果在复杂的业务场景下,我们希望这部分代码应该复用以减少代码冗余。我们可以使用事件响应器操作中的 `set_arg` 来主动设置一个参数:
|
||||
|
||||
```python {4,6} title=weather/__init__.py
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@weather.handle()
|
||||
async def handle_function(matcher: Matcher, args: Message = CommandArg()):
|
||||
if args.extract_plain_text():
|
||||
matcher.set_arg("location", args)
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def got_location(location: str = ArgPlainText()):
|
||||
await weather.finish(f"今天{location}的天气是...")
|
||||
```
|
||||
|
||||
请注意,设置参数需要使用依赖注入来获取 `Matcher` 实例以确保上下文正确,且参数值应为 `Message` 对象。
|
||||
|
||||
在 `location` 参数被设置之后,`got` 事件响应器操作将不再会询问并等待用户的回复,而是直接进入 `got_location` 函数。
|
||||
|
||||
## 请求重新输入
|
||||
|
||||
在实际的业务场景中,用户的输入很有可能并非是我们所期望的,而结束事件处理流程让用户重新发送指令也不是一个好的体验。这时我们可以使用 `reject` 事件响应器操作来请求用户重新输入:
|
||||
|
||||
```python {8,9} title=weather/__init__.py
|
||||
@weather.handle()
|
||||
async def handle_function(matcher: Matcher, args: Message = CommandArg()):
|
||||
if args.extract_plain_text():
|
||||
matcher.set_arg("location", args)
|
||||
|
||||
@weather.got("location", prompt="请输入地名")
|
||||
async def got_location(location: str = ArgPlainText()):
|
||||
if location not in ["北京", "上海", "广州", "深圳"]:
|
||||
await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
|
||||
await weather.finish(f"今天{location}的天气是...")
|
||||
```
|
||||
|
||||
<Messenger
|
||||
msgs={[
|
||||
{ position: "right", msg: "/天气" },
|
||||
{ position: "left", msg: "请输入地名" },
|
||||
{ position: "right", msg: "南京" },
|
||||
{ position: "left", msg: "你想查询的城市 南京 暂不支持,请重新输入!" },
|
||||
{ position: "right", msg: "北京" },
|
||||
{ position: "left", msg: "今天北京的天气是..." },
|
||||
]}
|
||||
/>
|
||||
|
||||
在上面的代码中,我们在 `got_location` 函数中判断用户输入的地名是否在支持的城市列表中,如果不在,则使用 `reject` 事件响应器操作。操作将会向用户发送 `reject` 参数中的消息,并等待用户回复后,重新执行 `got_location` 函数。通过 `got` 和 `reject` 事件响应器操作,我们实现了类似于**循环**的执行方式。
|
||||
|
||||
`reject` 事件响应器操作与 `finish` 类似,NoneBot 会在向机器人用户发送消息内容后抛出 `RejectedException` 异常来暂停事件响应流程以等待用户输入。也就是说,在 `reject` 被执行后,后续的程序同样是不会被执行的。
|
||||
|
||||
## 更多事件响应器操作
|
||||
|
||||
在之前的章节中,我们已经大致了解了五个事件响应器操作:`handle`、`got`、`finish`、`send` 和 `reject`。现在我们来完整地介绍一下这些操作。
|
||||
|
||||
事件响应器操作可以分为两大类:**交互操作**和**流程控制操作**。我们可以通过交互操作来与用户进行交互,而流程控制操作则可以用来控制事件处理流程的执行。
|
||||
|
||||
:::tip 提示
|
||||
事件处理流程按照事件处理函数添加顺序执行,已经结束的事件处理函数不可能被恢复执行。
|
||||
:::
|
||||
|
||||
### handle
|
||||
|
||||
`handle` 事件响应器操作是一个装饰器,用于向事件处理流程添加一个事件处理函数。
|
||||
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def handle_func():
|
||||
...
|
||||
```
|
||||
|
||||
`handle` 装饰器支持嵌套操作,即一个事件处理函数可以被添加多次:
|
||||
|
||||
```python
|
||||
@matcher.handle()
|
||||
@matcher.handle()
|
||||
async def handle_func():
|
||||
# 这个函数会被执行两次
|
||||
...
|
||||
```
|
||||
|
||||
### got
|
||||
|
||||
`got` 事件响应器操作也是一个装饰器,它会在当前装饰的事件处理函数运行之前,中断当前事件处理流程,等待接收一个新的事件。它可以通过 `prompt` 参数来向用户发送询问消息,然后等待用户的回复消息,贴近对话形式会话。
|
||||
|
||||
`got` 装饰器接受一个参数 `key` 和一个可选参数 `prompt`。当会话状态中不存在 `key` 对应的消息时,会向用户发送 `prompt` 参数的消息,并等待用户回复。`prompt` 参数的类型和 [`send`](#send) 事件响应器操作的参数类型一致。
|
||||
|
||||
在事件处理函数中,可以通过依赖注入的方式来获取接收到的消息,参考:[`Arg`](../advanced/dependency.mdx#arg)、[`ArgStr`](../advanced/dependency.mdx#argstr)、[`ArgPlainText`](../advanced/dependency.mdx#argplaintext)。
|
||||
|
||||
```python
|
||||
@matcher.got("key", prompt="请输入...")
|
||||
async def got_func(key: Message = Arg()):
|
||||
...
|
||||
```
|
||||
|
||||
`got` 装饰器支持与 `got` 和 `receive` 装饰器嵌套操作,即一个事件处理函数可以在接收多个事件或消息后执行:
|
||||
|
||||
```python
|
||||
@matcher.got("key1", prompt="请输入key1...")
|
||||
@matcher.got("key2", prompt="请输入key2...")
|
||||
@matcher.receive("key3")
|
||||
async def got_func(key1: Message = Arg(), key2: Message = Arg(), key3: Event = Received("key3")):
|
||||
...
|
||||
```
|
||||
|
||||
### receive
|
||||
|
||||
`receive` 事件响应器操作也是一个装饰器,它会在当前装饰的事件处理函数运行之前,中断当前事件处理流程,等待接收一个新的事件。与 `got` 不同的是,`receive` 不会向用户发送询问消息,并且等待一个用户事件。可以接收的事件类型取决于[会话更新](../advanced/session-updating.md)。
|
||||
|
||||
`receive` 装饰器接受一个可选参数 id,用于标识当前需要接收的事件,如果不指定,则默认为空 `""`。
|
||||
|
||||
在事件处理函数中,可以通过依赖注入的方式来获取接收到的事件,参考:[`Received`](../advanced/dependency.mdx#received)、[`LastReceived`](../advanced/dependency.mdx#lastreceived)。
|
||||
|
||||
```python
|
||||
@matcher.receive("id")
|
||||
async def receive_func(event: Event = Received("id")):
|
||||
...
|
||||
```
|
||||
|
||||
`receive` 装饰器支持与 `got` 和 `receive` 装饰器嵌套操作,即一个事件处理函数可以在接收多个事件或消息后执行:
|
||||
|
||||
```python
|
||||
@matcher.receive("key1")
|
||||
@matcher.got("key2", prompt="请输入key2...")
|
||||
@matcher.got("key3", prompt="请输入key3...")
|
||||
async def receive_func(key1: Event = Received("key1"), key2: Message = Arg(), key3: Message = Arg()):
|
||||
...
|
||||
```
|
||||
|
||||
### send
|
||||
|
||||
`send` 事件响应器操作用于向用户回复一条消息。协议适配器会根据当前 event 选择回复的途径。
|
||||
|
||||
`send` 操作接受一个参数 message 和其他任何协议适配器接受的参数。message 参数类型可以是字符串、消息序列、消息段或者消息模板。消息模板将会使用会话状态字典进行渲染后发送。
|
||||
|
||||
这个操作等同于使用 `bot.send(event, message, **kwargs)`,但不需要自行传入 `event`。
|
||||
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def _():
|
||||
await matcher.send("Hello world!")
|
||||
```
|
||||
|
||||
### finish
|
||||
|
||||
向用户回复一条消息(可选),并立即结束**整个处理流程**。
|
||||
|
||||
参数与 [`send`](#send) 相同。
|
||||
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def _():
|
||||
await matcher.finish("Hello world!")
|
||||
# 下面的代码不会被执行
|
||||
```
|
||||
|
||||
### pause
|
||||
|
||||
向用户回复一条消息(可选),立即结束**当前**事件处理函数,等待接收一个新的事件后进入**下一个**事件处理函数。
|
||||
|
||||
参数与 [`send`](#send) 相同。
|
||||
|
||||
```python
|
||||
@matcher.handle()
|
||||
async def _():
|
||||
if need_confirm:
|
||||
await matcher.pause("请在两分钟内确认执行")
|
||||
|
||||
@matcher.handle()
|
||||
async def _():
|
||||
...
|
||||
```
|
||||
|
||||
### reject
|
||||
|
||||
向用户回复一条消息(可选),立即结束**当前**事件处理函数,等待接收一个新的事件后再次执行**当前**事件处理函数。
|
||||
|
||||
`reject` 可以用于拒绝当前 `receive` 接收的事件或 `got` 接收的参数。通常在用户回复不符合格式或标准需要重新输入,或者用于循环进行用户交互。
|
||||
|
||||
参数与 [`send`](#send) 相同。
|
||||
|
||||
```python
|
||||
@matcher.got("arg")
|
||||
async def _(arg: str = ArgPlainText()):
|
||||
if not is_valid(arg):
|
||||
await matcher.reject("Invalid arg!")
|
||||
```
|
||||
|
||||
### reject_arg
|
||||
|
||||
向用户回复一条消息(可选),立即结束**当前**事件处理函数,等待接收一个新的消息后再次执行**当前**事件处理函数。
|
||||
|
||||
`reject_arg` 用于拒绝指定 `got` 接收的参数,通常在嵌套装饰器时使用。
|
||||
|
||||
`reject_arg` 操作接受一个 key 参数以及可选的 prompt 参数。prompt 参数与 [`send`](#send) 相同。
|
||||
|
||||
```python
|
||||
@matcher.got("a")
|
||||
@matcher.got("b")
|
||||
async def _(a: str = ArgPlainText(), b: str = ArgPlainText()):
|
||||
if a not in b:
|
||||
await matcher.reject_arg("a", "Invalid a!")
|
||||
```
|
||||
|
||||
### reject_receive
|
||||
|
||||
向用户回复一条消息(可选),立即结束**当前**事件处理函数,等待接收一个新的事件后再次执行**当前**事件处理函数。
|
||||
|
||||
`reject_receive` 用于拒绝指定 `receive` 接收的事件,通常在嵌套装饰器时使用。
|
||||
|
||||
`reject_receive` 操作接受一个可选的 id 参数以及可选的 prompt 参数。id 参数默认为空 `""`,prompt 参数与 [`send`](#send) 相同。
|
||||
|
||||
```python
|
||||
@matcher.receive("a")
|
||||
@matcher.receive("b")
|
||||
async def _(a: Event = Received("a"), b: Event = Received("b")):
|
||||
if a.get_user_id() != b.get_user_id():
|
||||
await matcher.reject_receive("a")
|
||||
```
|
||||
|
||||
### skip
|
||||
|
||||
立即结束当前事件处理函数,进入下一个事件处理函数。
|
||||
|
||||
通常在依赖注入中使用,用于跳过当前事件处理函数的执行。
|
||||
|
||||
```python
|
||||
from nonebot.params import Depends
|
||||
|
||||
async def dependency():
|
||||
matcher.skip()
|
||||
|
||||
@matcher.handle()
|
||||
async def _(check=Depends(dependency)):
|
||||
# 这个函数不会被执行
|
||||
```
|
||||
|
||||
### stop_propagation
|
||||
|
||||
阻止事件向更低优先级的事件响应器传播。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@foo.handle()
|
||||
async def _(matcher: Matcher):
|
||||
matcher.stop_propagation()
|
||||
```
|
||||
|
||||
:::caution 注意
|
||||
`stop_propagation` 操作是实例方法,需要先通过依赖注入获取事件响应器实例再进行调用。
|
||||
:::
|
||||
|
||||
### get_arg
|
||||
|
||||
获取一个 `got` 接收的参数。
|
||||
|
||||
`get_arg` 操作接受一个 key 参数和一个可选的 default 参数。当参数不存在时,将返回 default 或 `None`。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
key = matcher.get_arg("key", default=None)
|
||||
```
|
||||
|
||||
### set_arg
|
||||
|
||||
设置 / 覆盖一个 `got` 接收的参数。
|
||||
|
||||
`set_arg` 操作接受一个 key 参数和一个 value 参数。请注意,value 参数必须是消息序列对象,如需存储其他数据请使用[会话状态](./session-state.md)。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
matcher.set_arg("key", Message("value"))
|
||||
```
|
||||
|
||||
### get_receive
|
||||
|
||||
获取一个 `receive` 接收的事件。
|
||||
|
||||
`get_receive` 操作接受一个 id 参数和一个可选的 default 参数。当事件不存在时,将返回 default 或 `None`。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
event = matcher.get_receive("id", default=None)
|
||||
```
|
||||
|
||||
### get_last_receive
|
||||
|
||||
获取最近的一个 `receive` 接收的事件。
|
||||
|
||||
`get_last_receive` 操作接受一个可选的 default 参数。当事件不存在时,将返回 default 或 `None`。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
event = matcher.get_last_receive(default=None)
|
||||
```
|
||||
|
||||
### set_receive
|
||||
|
||||
设置 / 覆盖一个 `receive` 接收的事件。
|
||||
|
||||
`set_receive` 操作接受一个 id 参数和一个 event 参数。请注意,event 参数必须是事件对象,如需存储其他数据请使用[会话状态](./session-state.md)。
|
||||
|
||||
```python
|
||||
from nonebot.matcher import Matcher
|
||||
|
||||
@matcher.handle()
|
||||
async def _(matcher: Matcher):
|
||||
matcher.set_receive("key", Event())
|
||||
```
|
@ -0,0 +1,59 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: 会话状态信息
|
||||
|
||||
options:
|
||||
menu:
|
||||
- category: appendices
|
||||
weight: 40
|
||||
---
|
||||
|
||||
# 会话状态
|
||||
|
||||
在事件处理流程中,和用户交互的过程即是会话。在会话中,我们可能需要记录一些信息,例如用户的重试次数等等,以便在会话中的不同阶段进行判断和处理。这些信息都可以存储于会话状态中。
|
||||
|
||||
NoneBot 中的会话状态是一个字典,可以通过类型 `T_State` 来获取。字典内可以存储任意类型的数据,但是要注意的是,NoneBot 本身会在会话状态中存储一些信息,因此不要使用 [NoneBot 使用的键名](../api/consts.md)。
|
||||
|
||||
```python
|
||||
from nonebot.typing import T_State
|
||||
|
||||
@matcher.got("key", prompt="请输入密码")
|
||||
async def _(state: T_State, key: str = ArgPlainText()):
|
||||
if key != "some password":
|
||||
try_count = state.get("try_count", 1)
|
||||
if try_count >= 3:
|
||||
await matcher.finish("密码错误次数过多")
|
||||
else:
|
||||
state["try_count"] = try_count + 1
|
||||
await matcher.reject("密码错误,请重新输入")
|
||||
await matcher.finish("密码正确")
|
||||
```
|
||||
|
||||
会话状态的生命周期与事件处理流程相同,在期间的任何一个事件处理函数都可以进行读写。
|
||||
|
||||
```python
|
||||
from nonebot.typing import T_State
|
||||
|
||||
@matcher.handle()
|
||||
async def _(state: T_State):
|
||||
state["key"] = "value"
|
||||
|
||||
@matcher.handle()
|
||||
async def _(state: T_State):
|
||||
await matcher.finish(state["key"])
|
||||
```
|
||||
|
||||
会话状态还可以用于发送动态消息,消息模板在发送时会使用会话状态字典进行渲染。消息模板的使用方法已经在[消息处理](../tutorial/message.md#使用消息模板)中介绍过,这里不再赘述。
|
||||
|
||||
```python
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import MessageTemplate
|
||||
|
||||
@matcher.handle()
|
||||
async def _(state: T_State):
|
||||
state["username"] = "user"
|
||||
|
||||
@matcher.got("password", prompt=MessageTemplate("请输入 {username} 的密码"))
|
||||
async def _():
|
||||
await matcher.finish(MessageTemplate("密码为 {password}"))
|
||||
```
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
sidebar_position: 99
|
||||
description: 下一步──进阶!
|
||||
---
|
||||
|
||||
# 下一步
|
||||
|
||||
至此,我们已经了解了 NoneBot 的大多数功能用法,相信你已经可以独自写出一个插件了。现在你可以选择:
|
||||
|
||||
- 即刻开始插件编写!
|
||||
- 更深入地了解 NoneBot 的[更多功能和原理](../advanced/plugin-info.md)!
|
Reference in New Issue
Block a user