🔖 Release 2.4.1
Some checks failed
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, macos-latest, 3.9) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.12) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, windows-latest, 3.9) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.11) (push) Failing after 2s
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v2, macos-latest, 3.9) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.12) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v2, windows-latest, 3.9) (push) Waiting to run
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.12) (push) Failing after 6m59s
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.10) (push) Failing after 7m7s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.10) (push) Failing after 5m14s
Code Coverage / Test Coverage (pydantic-v1, ubuntu-latest, 3.9) (push) Failing after 5m42s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.11) (push) Failing after 4m51s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.12) (push) Failing after 7m12s
Code Coverage / Test Coverage (pydantic-v2, ubuntu-latest, 3.9) (push) Failing after 6m33s
Pyright Lint / Pyright Lint (pydantic-v1) (push) Failing after 8m12s
Ruff Lint / Ruff Lint (push) Successful in 43s
Pyright Lint / Pyright Lint (pydantic-v2) (push) Failing after 6m56s
Site Deploy / publish (push) Failing after 7m21s

This commit is contained in:
noneflow[bot]
2024-12-25 07:21:05 +00:00
parent 762b2e6ef1
commit 033c90dd74
97 changed files with 19768 additions and 2 deletions

View 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()
```

View File

@ -0,0 +1,632 @@
---
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 内置的配置项列表及含义可以在[内置配置项](#内置配置项)中查看。
:::caution 注意
NoneBot 自 2.2.0 起兼容了 Pydantic v1 与 v2 版本,以下文档中 Pydantic 相关示例均采用 v2 版本用法。
如果在使用商店或其他第三方插件的过程中遇到 Pydantic 相关警告或报错,例如:
```python
pydantic_core._pydantic_core.ValidationError: 1 validation error for Config
Input should be a valid dictionary or instance of Config [type=model_type, input_value=Config(...), input_type=Config]
```
请考虑降级 Pydantic 至 v1 版本:
```bash
pip install --force-reinstall 'pydantic~=1.10'
```
:::
## 配置项的加载
在 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 cmd
set CUSTOM_CONFIG 'config in environment variables'
# windows powershell
$Env: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, field_validator
class Config(BaseModel):
weather_api_key: str
weather_command_priority: int = 10
weather_plugin_enabled: bool = True
@field_validator("weather_command_priority")
@classmethod
def check_priority(cls, v: int) -> int:
if v >= 1:
return v
raise ValueError("weather command priority must 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_plugin_config
from .config import Config
plugin_config = get_plugin_config(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_plugin_config
from .config import Config
plugin_config = get_plugin_config(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 cmd
set DRIVER '~fastapi+~httpx+~websockets'
# windows powershell
$Env: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 cmd
set HOST '127.0.0.1'
# windows powershell
$Env: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 cmd
set PORT '8080'
# windows powershell
$Env: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 cmd
set LOG_LEVEL 'DEBUG'
# windows powershell
$Env: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 cmd
set API_TIMEOUT '10.0'
# windows powershell
$Env: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 cmd
set SUPERUSERS '["123123123"]'
# windows powershell
$Env: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 cmd
set NICKNAME '["bot"]'
# windows powershell
$Env: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 cmd
set COMMAND_START '["/", ""]'
set COMMAND_SEP '[".", " "]'
# windows powershell
$Env:COMMAND_START='["/", ""]'
$Env: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/latest/api/standard_library_types/#datetimetimedelta)。
<Tabs groupId="configMethod">
<TabItem value="dotenv" label="dotenv" default>
```dotenv
SESSION_EXPIRE_TIMEOUT=00:02:00
```
</TabItem>
<TabItem value="env" label="系统环境变量">
```bash
# windows cmd
set SESSION_EXPIRE_TIMEOUT '00:02:00'
# windows powershell
$Env:SESSION_EXPIRE_TIMEOUT='00:02:00'
# linux/macOS
export SESSION_EXPIRE_TIMEOUT='00:02:00'
```
</TabItem>
<TabItem value="init" label="直接传入">
```python title=bot.py
import nonebot
nonebot.init(session_expire_timeout=120)
```
</TabItem>
</Tabs>

View 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())
```

View 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)中介绍。
:::

View File

@ -0,0 +1,117 @@
---
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 {3,9} title=weather/__init__.py
from typing import Tuple
from nonebot.params import Command
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` 进行检查,并返回结果。

View File

@ -0,0 +1,114 @@
---
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 {7,8} title=weather/__init__.py
from nonebot import get_plugin_config
from .config import Config
plugin_config = get_plugin_config(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 {13} title=weather/__init__.py
from nonebot.rule import to_me
from nonebot import get_plugin_config
from .config import Config
plugin_config = get_plugin_config(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)

View File

@ -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())
```

View File

@ -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}"))
```

View File

@ -0,0 +1,11 @@
---
sidebar_position: 99
description: 下一步──进阶!
---
# 下一步
至此,我们已经了解了 NoneBot 的大多数功能用法,相信你已经可以独自写出一个插件了。现在你可以选择:
- 即刻开始插件编写!
- 更深入地了解 NoneBot 的[更多功能和原理](../advanced/plugin-info.md)