🔖 Release 2.0.0-beta.2

This commit is contained in:
github-actions
2022-02-14 16:27:11 +00:00
parent 14703fce8d
commit cee96d8ab6
72 changed files with 9781 additions and 0 deletions

View File

@ -0,0 +1,42 @@
---
sidebar_position: 10
description: 扩展自定义服务端 API
---
# 添加自定义 API
由于 NoneBot2 可以使用 `ReverseDriver` (即服务端框架)来进行驱动,因此可以将 NoneBot2 来作为一个服务端程序来提供 API 接口等功能。
在扩展 API 之前,你首先需要确保 NoneBot2 使用的是 `ReverseDriver`,详情可以参考 [选择驱动器](./choose-driver.md)。下面我们以 FastAPI 驱动器为例,来演示如何添加自定义 API。
## 获取 APP 实例
在定义 API 接口之前,需要先获取到驱动器框架的 APP 实例。
```python {4}
import nonebot
from fastapi import FastAPI
app: FastAPI = nonebot.get_app()
@app.get("/api")
async def custom_api():
return {"message": "Hello, world!"}
```
## 添加接口
在获取到当前驱动器的 APP 实例后,即可以直接使用驱动器框架提供的方法来添加 API 接口。
在下面的代码中,我们添加了一个 `GET` 类型的 `/api` 接口,具体方法参考 [FastAPI 文档](https://fastapi.tiangolo.com/)。
```python {6-8}
import nonebot
from fastapi import FastAPI
app: FastAPI = nonebot.get_app()
@app.get("/api")
async def custom_api():
return {"message": "Hello, world!"}
```

View File

@ -0,0 +1,29 @@
---
sidebar_position: 8
description: 调用机器人平台 API完成更多的功能
options:
menu:
weight: 29
category: guide
---
# 调用平台 API
在使用机器人功能时,除了发送消息以外,还可能需要调用机器人平台的 API 来完成更多的功能。
NoneBot 提供了两种方式来调用机器人平台 API两种方式都需要首先获得 Bot 实例,然后调用相应的方法。
例如,如果需要调用机器人平台的 `get_user_info` API可以这样做
```python
from nonebot import get_bot
bot = get_bot("bot_id")
result = await bot.get_user_info(user_id=12345678)
await bot.call_api("get_user_info", user_id=12345678)
```
:::tip 提示
API 由平台提供,请参考平台文档。
:::

View File

@ -0,0 +1,239 @@
---
sidebar_position: 5
description: 各驱动器的功能与区别
options:
menu:
weight: 22
category: guide
---
# 选择驱动器
:::warning 注意
驱动器的选择通常与你所使用的协议适配器相关,如果你不知道该选择哪个驱动器,可以先阅读你想要使用的协议适配器文档说明。
:::
:::tip 提示
如何**安装**驱动器请参考[安装驱动器](../start/install-driver.mdx)。
如何**使用**驱动器请参考[配置](./configuration.md#driver)。
:::
## 驱动器的类型
驱动器的类型有两种:
- `ForwardDriver`:即客户端类型驱动器,多用于使用 HTTP 轮询WebSocket 连接服务器的情形。
- `ReverseDriver`:即服务端类型驱动器,多用于使用 WebHook 情形。
其中 `ReverseDriver` 可以配合 `ForwardDriver` 一起使用,即可以同时使用客户端功能和服务端功能。
## 驱动器的功能
在 NoneBot 中,驱动器主要负责数据的收发,不对数据进行处理。通常,驱动器会实现以下功能:
### ForwardDriver
1. 异步发送 HTTP 请求,自定义 `HTTP Method``URL``Header``Body``Cookie``Proxy``Timeout` 等。
2. 异步建立 WebSocket 连接上下文,自定义 `WebSocket URL``Header``Cookie``Proxy``Timeout` 等。
### ReverseDriver
1. 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
2. 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
3. 用户可以将 Driver 作为服务端使用,自行添加任何服务端相关功能。
## 内置驱动器
### FastAPI默认
类型:`ReverseDriver`
> FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
FastAPI 是一个易上手、高性能的异步 Web 框架,具有极佳的编写体验,可以挂载其他 ASGI、WSGI 应用。
FastAPI[文档](https://fastapi.tiangolo.com/)、[仓库](https://github.com/tiangolo/fastapi)
驱动器:[API](../api/drivers/fastapi.md)、[源码](https://github.com/nonebot/nonebot2/blob/master/nonebot/drivers/fastapi.py)
```env
DRIVER=~fastapi
```
#### FastAPI 配置项
##### `fastapi_openapi_url`
类型:`Optional[str]`
默认值:`None`
说明:`FastAPI` 提供的 `OpenAPI` JSON 定义地址,如果为 `None`,则不提供 `OpenAPI` JSON 定义。
##### `fastapi_docs_url`
类型:`Optional[str]`
默认值:`None`
说明:`FastAPI` 提供的 `Swagger` 文档地址,如果为 `None`,则不提供 `Swagger` 文档。
##### `fastapi_redoc_url`
类型:`Optional[str]`
默认值:`None`
说明:`FastAPI` 提供的 `ReDoc` 文档地址,如果为 `None`,则不提供 `ReDoc` 文档。
##### `fastapi_include_adapter_schema`
类型:`bool`
默认值:`True`
说明:`FastAPI` 提供的 `OpenAPI` JSON 定义中是否包含适配器路由的 `Schema`
##### `fastapi_reload`
类型:`bool`
默认值:`False`
说明:是否开启 `uvicorn``reload` 功能,需要提供 asgi 应用路径。
```python title=bot.py
app = nonebot.get_asgi()
nonebot.run(app="bot:app")
```
##### `fastapi_reload_dirs`
类型:`Optional[List[str]]`
默认值:`None`
说明:重载监控文件夹列表,默认为 uvicorn 默认值
##### `fastapi_reload_delay`
类型:`Optional[float]`
默认值:`None`
说明:重载延迟,默认为 uvicorn 默认值
##### `fastapi_reload_includes`
类型:`Optional[List[str]]`
默认值:`None`
说明:要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
##### `fastapi_reload_excludes`
类型:`Optional[List[str]]`
默认值:`None`
说明:不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
### Quart
类型:`ReverseDriver`
> Quart is an asyncio reimplementation of the popular Flask microframework API.
Quart 是一个类 Flask 的异步版本,拥有与 Flask 非常相似的接口和使用方法。
Quart[文档](https://pgjones.gitlab.io/quart/)、[仓库](https://gitlab.com/pgjones/quart)
驱动器:[API](../api/drivers/quart.md)、[源码](https://github.com/nonebot/nonebot2/blob/master/nonebot/drivers/quart.py)
```env
DRIVER=~quart
```
#### Quart 配置项
##### `quart_reload`
类型:`bool`
默认值:`False`
说明:是否开启 `uvicorn` 的 `reload` 功能,需要提供 asgi 应用路径。
```python title=bot.py
app = nonebot.get_asgi()
nonebot.run(app="bot:app")
```
##### `quart_reload_dirs`
类型:`Optional[List[str]]`
默认值:`None`
说明:重载监控文件夹列表,默认为 uvicorn 默认值
##### `quart_reload_delay`
类型:`Optional[float]`
默认值:`None`
说明:重载延迟,默认为 uvicorn 默认值
##### `quart_reload_includes`
类型:`Optional[List[str]]`
默认值:`None`
说明:要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
##### `quart_reload_excludes`
类型:`Optional[List[str]]`
默认值:`None`
说明:不要监听的文件列表,支持 glob pattern默认为 uvicorn 默认值
### HTTPX
类型:`ForwardDriver`
:::warning 注意
本驱动器仅支持 HTTP 请求,不支持 WebSocket 请求。
:::
> HTTPX is a fully featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.
HTTPX[文档](https://www.python-httpx.org/)、[仓库](https://github.com/encode/httpx/)
驱动器:[API](../api/drivers/httpx.md)、[源码](https://github.com/nonebot/nonebot2/blob/master/nonebot/drivers/httpx.py)
```env
DRIVER=~httpx
```
:::important 注意
本驱动器支持 `Mixin`
:::
### websockets
类型:`ForwardDriver`
:::warning 注意
本驱动器仅支持 WebSocket 请求,不支持 HTTP 请求。
:::
> websockets is a library for building WebSocket servers and clients in Python with a focus on correctness, simplicity, robustness, and performance.
websockets[文档](https://websockets.readthedocs.io/en/stable/)、[仓库](https://github.com/aaugustin/websockets)
驱动器:[API](../api/drivers/websockets.md)、[源码](https://github.com/nonebot/nonebot2/blob/master/nonebot/drivers/websockets.py)
```env
DRIVER=~websockets
```
:::important 注意
本驱动器支持 `Mixin`
:::
### AIOHTTP
类型:`ForwardDriver`
> Asynchronous HTTP Client/Server for asyncio and Python.
AIOHTTP[文档](https://docs.aiohttp.org/en/stable/)、[仓库](https://github.com/aio-libs/aiohttp)
驱动器:[API](../api/drivers/aiohttp.md)、[源码](https://github.com/nonebot/nonebot2/blob/master/nonebot/drivers/aiohttp.py)
```env
DRIVER=~aiohttp
```
:::important 注意
本驱动器支持 `Mixin`
:::

View File

@ -0,0 +1,240 @@
---
sidebar_position: 1
description: 项目配置方式与配置项
options:
menu:
weight: 21
category: guide
---
# 配置
在上一章节中,我们创建了默认的项目结构,其中 `.env``.env.*` 均为项目的配置文件,下面将介绍几种 NoneBot 配置方式以及配置项。
:::danger 警告
请勿将敏感信息写入配置文件并提交至开源仓库!
:::
## 配置方式
### .env 文件
NoneBot 在启动时将会从系统环境变量或者 `.env` 文件中寻找变量 `ENVIRONMENT`(大小写不敏感),默认值为 `prod`
这将引导 NoneBot 从系统环境变量或者 `.env.{ENVIRONMENT}` 文件中进一步加载具体配置。
`.env` 文件是基础环境配置文件,该文件中的配置项在不同环境下都会被加载,但会被 `.env.{ENVIRONMENT}` 文件中的配置所覆盖。
NoneBot 使用 [Pydantic](https://pydantic-docs.helpmanual.io/) 进行配置处理,并对 Pydantic 的行为做出了更改,详见下方说明。
现在,我们在 `.env` 文件中写入当前环境信息:
```bash
# .env
ENVIRONMENT=dev
CUSTOM_CONFIG=common config # 这个配置项在任何环境中都会被加载
```
如你所想,之后 NoneBot 就会从 `.env.dev` 文件中加载环境变量。
:::important 参考文档
`.env` 相关文件的加载使用 `dotenv` 语法,请参考 [`dotenv` 文档](https://saurabh-kumar.com/python-dotenv/)
:::
:::warning 提示
由于 Pydantic 使用 JSON 解析配置项,请确保配置项值为 JSON 格式的数据。如:
```bash
list=["123456789", "987654321", 1]
test={"hello": "world"}
```
如果配置项值解析失败将作为**字符串**处理。
特别的,如果配置项**为空**,则会从**系统环境变量**中获取值,如果不存在则为空字符串。
:::
### .env.\* 文件
NoneBot 默认会从 `.env.{ENVIRONMENT}` 文件加载配置,但是可以在 NoneBot 初始化时指定加载某个环境配置文件:`nonebot.init(_env_file=".env.dev")`,这将忽略你在 `.env` 中设置的 `ENVIRONMENT`
配置语法与 `.env` 文件相同。
示例及说明:
```bash
HOST=0.0.0.0 # 配置 NoneBot2 监听的 IP/主机名
PORT=8080 # 配置 NoneBot2 监听的端口
SUPERUSERS=["123456789", "987654321"] # 配置 NoneBot 超级用户
NICKNAME=["awesome", "bot"] # 配置机器人的昵称
COMMAND_START=["/", ""] # 配置命令起始字符
COMMAND_SEP=["."] # 配置命令分割字符
# Custom Configs
CUSTOM_CONFIG1="config in env file"
CUSTOM_CONFIG2= # 留空则从系统环境变量读取,如不存在则为空字符串
```
详细的配置项可以参考[配置项](#详细配置项)。
### 系统环境变量
如果在系统环境变量中定义了配置,则一样会被读取。
### bot.py 文件
配置项也可以在 NoneBot 初始化时传入。此处可以传入任意合法 Python 变量。当然也可以在初始化完成后修改或新增。
示例:
```python
# bot.py
import nonebot
nonebot.init(custom_config3="config on init")
config = nonebot.get_driver().config
config.custom_config3 = "changed after init"
config.custom_config4 = "new config after init"
```
## 配置优先级
`bot.py` 文件(`nonebot.init`> 系统环境变量 > `.env``.env.*` 文件
## 读取配置项
配置项可以通过三种类型的对象获取:`driver``adapter``bot`
```python
import nonebot
# driver
nonebot.get_driver().config.custom_config
# bot
nonebot.get_bot().config.custom_config
# adapter
nonebot.get_driver()._adapters["adapter_name"].config.custom_config
```
## 详细配置项
配置项的 API 文档可以前往 [Class Config](../api/config.md#class-config) 查看。
### Driver
- **类型**: `str`
- **默认值**: `"~fastapi"`
NoneBot2 运行所使用的驱动器。主要分为 `ForwardDriver``ReverseDriver` 即客户端和服务端两类。
配置格式采用特殊语法:`<module>[:<Driver>][+<module>[:<Mixin>]]*`
其中 `<module>` 为驱动器模块名,可以使用 `~` 作为 `nonebot.drivers.` 的简写;`<Driver>` 为驱动器类名,默认为 `Driver``<Mixin>` 为驱动器混入的类名,默认为 `Mixin`
NoneBot2 内置了几个常用驱动器,包括了各类常用功能,常见驱动器配置如下:
```env
DRIVER=~fastapi
DRIVER=~httpx+~websockets
DRIVER=~fastapi+~httpx+~websockets
DRIVER=~fastapi+~aiohttp
```
各驱动器的功能与区别请参考[选择驱动器](./choose-driver.md)。
### Host
- **类型**: `IPvAnyAddress`
- **默认值**: `127.0.0.1`
使用 `ReversedDriver`NoneBot2 监听的 IP/主机名。
```env
HOST=127.0.0.1
```
### Port
- **类型**: `int`
- **默认值**: `8080`
使用 `ReversedDriver`NoneBot2 监听的端口。
```env
PORT=8080
```
### Log Level
- **类型**: `int | str`
- **默认值**: `INFO`
NoneBot2 日志输出等级,可以为 `int` 类型等级或等级名称
参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。
```env
LOG_LEVEL=INFO
```
:::tip 提示
日志等级名称应为大写,如 `INFO`
:::
### API Timeout
- **类型**: `Optional[float]`
- **默认值**: `30.0`
API 请求超时时间,单位为秒。
```env
API_TIMEOUT=30.0
```
### SuperUsers
- **类型**: `Set[str]`
- **默认值**: `set()`
机器人超级用户,可以使用权限 [`SUPERUSER`](../api/permission.md#SUPERUSER)。
```env
SUPERUSERS=["1234567890"]
```
### Nickname
- **类型**: `Set[str]`
- **默认值**: `set()`
机器人昵称,通常协议适配器会根据用户是否 @user 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。
```env
NICKNAME=["bot"]
```
### Command Start 和 Command Separator
- **类型**: `Set[str]`
- **默认值**:
- Command Start: `{"/"}`
- Command Separator: `{"."}`
命令消息的起始符和分隔符。用于 [`command`](../api/rule.md#command) 规则。
```env
COMMAND_START={"/", "!"}
COMMAND_SEP={".", "/"}
```
### Session Expire Timeout
- **类型**: `timedelta`
- **默认值**: `timedelta(minutes=2)`
用户会话超时时间,配置格式参考 [Datetime Types](https://pydantic-docs.helpmanual.io/usage/types/#datetime-types)。
```env
SESSION_EXPIRE_TIMEOUT=120
```

View File

@ -0,0 +1,79 @@
---
sidebar_position: 0
description: 创建并运行项目
options:
menu:
weight: 20
category: guide
---
import Asciinema from "@site/src/components/Asciinema";
# 创建项目
可以使用 `nb-cli` 或者自行创建完整的项目目录:
```bash
nb create
```
<Asciinema
url="https://asciinema.org/a/464654.cast"
options={{ theme: "monokai", startAt: 4.5, poster: "npt:6.5" }}
/>
## 目录结构
```tree title=Project
📦 AweSome-Bot
├── 📂 awesome_bot # 或是 src
│ └── 📜 plugins
├── 📜 .env # 可选的
├── 📜 .env.dev # 可选的
├── 📜 .env.prod # 可选的
├── 📜 .gitignore
├── 📜 bot.py
├── 📜 docker-compose.yml
├── 📜 Dockerfile
├── 📜 pyproject.toml
└── 📜 README.md
```
- `awesome_bot/plugins` 或 `src/plugins`: 用于存放编写的 bot 插件
- `.env`、`.env.dev`、`.env.prod`: 各环境配置文件
- `bot.py`: bot 入口文件
- `pyproject.toml`: 项目插件配置文件
- `Dockerfile`、`docker-compose.yml`: Docker 镜像配置文件
## 启动 Bot
:::warning 提示
如果您使用如 `VSCode` / `PyCharm` 等 IDE 启动 nonebot请检查 IDE 当前工作空间目录是否与当前侧边栏打开目录一致。
> 注意: 在二者不一致的环境下可能导致 nonebot 读取配置文件和插件等不符合预期
:::
1. 通过 nb-cli
```bash
nb run [--file=bot.py] [--app=app]
```
其中 `--file` 参数可以指定 bot 入口文件,默认为 `bot.py``--app` 参数可以指定 asgi server默认为 `app`。
<Asciinema
url="https://asciinema.org/a/464654.cast"
options={{ theme: "monokai", startAt: 15.3, poster: "npt:20.3" }}
/>
2. 直接通过 Python 启动
```bash
python bot.py
```
:::tip 提示
如果在 bot 入口文件内定义了 asgi servernb-cli 将会为你启动**冷重载模式**(当文件发生变动时自动重启 NoneBot2 实例)
:::

View File

@ -0,0 +1,59 @@
---
sidebar_position: 100
description: 修改日志级别与输出
---
# 自定义日志
NoneBot 使用 [Loguru](https://loguru.readthedocs.io/) 进行日志记录,并提供了一些内置的格式和过滤器等。
## 默认日志
NoneBot 启动时会添加一个默认的日志 handler。此 handler 将会将日志输出到 **stdout**,并且根据配置的日志级别进行过滤。
[默认格式](../api/log.md#default_format)):
```python
default_format: str = (
"<g>{time:MM-DD HH:mm:ss}</g> "
"[<lvl>{level}</lvl>] "
"<c><u>{name}</u></c> | "
"{message}"
)
from nonebot.log import default_format
```
[默认过滤器](../api/log.md#default_filter):
```python
from nonebot.log import default_filter
```
## 转移 logging 日志
NoneBot 提供了一个 logging handler 用于将日志输出转移至 loguru 处理。将 logging 的默认 handler 替换为 `LoguruHandler` 即可。
```python
from nonebot.log import LoguruHandler
```
## 自定义日志记录
如果需要移除 NoneBot 的默认日志 handler可以在 `nonebot.init` 之前进行如下操作:
```python
from nonebot.log import logger, logger_id
logger.remove(logger_id)
```
如果需要添加自定义的日志 handler可以在 `nonebot.init` 之前添加 handler参考 [loguru 文档](https://loguru.readthedocs.io/)。
示例:
```python
from nonebot.log import logger, default_format
logger.add("error.log", level="ERROR", format=default_format, rotation="1 week")
```

View File

@ -0,0 +1,230 @@
---
sidebar_position: 11
description: 部署你的机器人
---
# 部署
在编写完成后,你需要部署你的机器人来使得用户能够使用它。通常,会将机器人部署在服务器上,来保证服务持久运行。
在开发时机器人运行的环境称为开发环境,而在部署后机器人运行的环境称为生产环境。与开发环境不同的是,在生产环境中,开发者通常不能随意地修改/添加/删除代码,开启或停止服务。
## 部署前准备
在生产环境中,为确保机器人能够正常运行,你需要固定你的依赖库版本。下面提供了几种常见的文件格式与生成方式:
- `poetry.lock`
[poetry](https://python-poetry.org/) 依赖管理工具使用的 lock 文件,通常会在安装依赖时自动生成,或者使用 `poetry lock` 来生成。
- `pdm.lock`
[pdm](https://pdm.fming.dev/) 依赖管理工具使用的 lock 文件,通常会在安装依赖时自动生成,或者使用 `pdm lock` 来生成。
- `Pipfile.lock`
[Pipenv](https://pipenv.pypa.io/en/latest/) 依赖管理工具使用的 lock 文件,通常会在安装依赖时自动生成,或者使用 `pipenv lock` 来生成。
- `requirements.txt`
如果你未使用任何依赖管理工具,你可以使用 `pip freeze` 来生成这个文件。
## 使用 Docker 部署(推荐)
请自行参考 [Docker 官方文档](https://docs.docker.com/engine/install/) 安装 Docker。
在生产环境安装 [docker-compose](https://docs.docker.com/compose/) 工具以便部署机器人。
### 编译镜像与部署配置
在项目目录下添加以下两个文件(以 poetry 和 FastAPI 驱动器为例):
```dockerfile title=Dockerfile
FROM python:3.9 as requirements-stage
WORKDIR /tmp
COPY ./pyproject.toml ./poetry.lock* /tmp/
RUN curl -sSL https://install.python-poetry.org -o install-poetry.py
RUN python install-poetry.py --yes
ENV PATH="${PATH}:/root/.local/bin"
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
WORKDIR /app
COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r requirements.txt
RUN rm requirements.txt
COPY ./ /app/
```
```yaml title=docker-compose.yml
version: "3"
services:
nonebot:
build: .
ports:
- "8080:8080" # 映射端口到宿主机 宿主机端口:容器端口
env_file:
- ".env.prod" # fastapi 使用的环境变量文件
environment:
- ENVIRONMENT=prod
- APP_MODULE=bot:app
- MAX_WORKERS=1
network_mode: bridge
```
配置完成后即可使用 `docker-compose up -d` 命令来启动机器人并在后台运行。
### CI/CD
配合 GitHub Actions 可以完成 CI/CD在 GitHub 上发布 Release 时自动部署至生产环境。
在 [Docker Hub](https://hub.docker.com/) 上创建仓库,并将下方 workflow 文件中高亮行中的仓库名称替换为你的仓库名称。
前往项目仓库的 `Settings` > `Secrets` > `actions` 栏目 `New Repository Secret` 添加部署所需的密钥:
- `DOCKERHUB_USERNAME`: 你的 Docker Hub 用户名
- `DOCKERHUB_PASSWORD`: 你的 Docker Hub PAT[创建方法](https://docs.docker.com/docker-hub/access-tokens/)
- `DEPLOY_HOST`: 部署服务器 IP 地址
- `DEPLOY_USER`: 部署服务器用户名
- `DEPLOY_KEY`: 部署服务器私钥
- `DEPLOY_PATH`: 部署服务器上的项目路径
将以下文件添加至项目下的 `.github/workflows/` 目录下:
```yaml title=.github/workflows/build.yml {30}
name: Docker Hub Release
on:
push:
tags:
- "v*"
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Docker
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Generate Tags
uses: docker/metadata-action@v3
id: metadata
with:
images: |
{organization}/{repository}
tags: |
type=semver,pattern={{version}}
type=sha
- name: Build and Publish
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
```
```yaml title=.github/workflows/deploy.yml
name: Deploy
on:
workflow_run:
workflows:
- Docker Hub Release
types:
- completed
jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: start deployment
uses: bobheadxi/deployments@v0.6
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: official-bot
- name: remote ssh command
uses: appleboy/ssh-action@master
env:
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
envs: DEPLOY_PATH
script: |
cd $DEPLOY_PATH
docker-compose down
docker-compose pull
docker-compose up -d
- name: update deployment status
uses: bobheadxi/deployments@v0.6
if: always()
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
```
将上一部分的 `docker-compose.yml` 文件以及 `.env.prod` 配置文件添加至 `DEPLOY_PATH` 目录下,并修改 `docker-compose.yml` 文件中的镜像配置,替换为 Docker Hub 的仓库名称。
```diff
- build: .
+ image: {organization}/{repository}:latest
```
## 使用 Supervisor 部署
参考:[Uvicorn - Supervisor](https://www.uvicorn.org/deployment/#supervisor)
```ini
[supervisord]
[fcgi-program:nonebot]
socket=tcp://localhost:8080
command=python3 -m uvicorn --fd 0 bot:app
directory=/path/to/bot
autorestart=true
startsecs=10
startretries=3
numprocs=1
process_name=%(program_name)s-%(process_num)d
stdout_logfile=/path/to/log/nonebot.out.log
stdout_logfile_maxbytes=2MB
```
:::warning 警告
请配合虚拟环境使用,如 venv 等,请勿直接在 Linux 服务器系统环境中安装。
:::
## 使用 PM2 部署
<!-- TODO -->

View File

@ -0,0 +1,5 @@
{
"position": 7,
"label": "插件",
"collapsible": false
}

View File

@ -0,0 +1,37 @@
---
sidebar_position: 2
description: 规范定义插件配置项
---
# 定义插件配置
通常,插件可以从配置文件中读取自己的配置项,但是由于额外的全局配置项没有预先定义的问题,导致开发时编辑器无法提示字段与类型,以及运行时没有对配置项直接进行检查。那么就需要一种方式来规范定义插件配置项。
## 定义配置模型
在 NoneBot2 中,我们使用强大高效的 [Pydantic](https://pydantic-docs.helpmanual.io/) 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如,我们可以定义一个配置模型包含一个 string 类型的配置项:
```python title=config.py {3,4}
from pydantic import BaseModel, Extra
class Config(BaseModel, extra=Extra.ignore):
token: str
```
:::important 参考
更多丰富的模型定义方法(默认值、自定义 validator 等),请参考 [Pydantic](https://pydantic-docs.helpmanual.io/) 文档。
:::
## 读取配置
定义完成配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型:
```python title=__init__.py {5}
from nonebot import get_driver
from .config import Config
plugin_config = Config.parse_obj(get_driver().config)
```
至此,插件已经成功读取了自身所需的配置项,并且具有字段和类型提示,也可以对配置进行运行时修改。

View File

@ -0,0 +1,447 @@
---
sidebar_position: 4
description: 定义事件处理流程,完成事件响应
options:
menu:
weight: 27
category: guide
---
# 定义事件处理流程
在上一章节中,我们已经定义了事件响应器,在这一章中,我们将会为事件响应器填充处理流程。
## 添加一个处理依赖
在事件响应器中,事件处理流程由一个或多个处理依赖组成,每个处理依赖都是一个 `Dependent`,详情可以参考[进阶 - 依赖注入](../../advanced/di/dependency-injection.md)。下面介绍如何添加一个处理依赖。
### 使用 `handle` 装饰器
```python {3-5}
matcher = on_message()
@matcher.handle()
async def handle_func():
# do something here
```
如上方示例所示,我们使用 `matcher` 响应器的 `handle` 装饰器装饰了一个函数 `handle_func` 。`handle_func` 函数会被自动转换为 `Dependent` 对象,并被添加到 `matcher` 的事件处理流程中。
在 `handle_func` 函数中,我们可以编写任何事件响应逻辑,如:操作数据库,发送消息等。上下文信息可以通过依赖注入的方式获取,参考:[获取上下文信息](#获取上下文信息)。发送消息可以通过[事件响应器操作](./matcher-operation.md)或者直接调用 Bot 的方法( API 等,由协议适配器决定)。
:::warning 注意
`handle_func` 函数虽然会被装饰器自动转换为 `Dependent` 对象,但 `handle_func` 仍然为原本的函数,因此 `handle_func` 函数可以进行复用。如:
```python
matcher1 = on_message()
matcher2 = on_message()
@matcher1.handle()
@matcher2.handle()
async def handle_func():
# do something here
```
:::
### 使用 `receive` 装饰器
```python {3-5}
matcher = on_message()
@matcher.receive("id")
async def handle_func(e: Event = Received("id")):
# do something here
```
`receive` 装饰器与 `handle` 装饰器一样,可以装饰一个函数添加到事件响应器的事件处理流程中。但与 `handle` 装饰器不同的是,`receive` 装饰器会中断当前事件处理流程,等待接收一个新的事件,就像是会话状态等待用户一个新的事件。可以接收的新的事件类型取决于事件响应器的 [`type`](./create-matcher.md#事件响应器类型-type) 更新值以及 [`permission`](./create-matcher.md#事件触发权限-permission) 更新值,可以通过自定义更新方法来控制会话响应(如进行非消息交互、多人会话、跨群会话等)。
`receive` 装饰器接受一个可选参数 `id`,用于标识当前需要接收的事件,如果不指定,则默认为空 `""`。
在 `handle_func` 函数中,可以通过依赖注入的方式来获取接收到的事件,参考:[`Received`](#received)、[`LastReceived`](#lastreceived)。
:::important 提示
`receive` 装饰器可以和自身与 `got` 装饰器嵌套使用
:::
:::warning 注意
如果存在多个 `receive` 装饰器,则必须指定不相同的多个 `id`;否则相同的 `id` 将会被跳过接收。
```python
matcher = on_message()
@matcher.receive("id1")
@matcher.receive("id2")
async def handle_func():
# do something here
```
:::
### 使用 `got` 装饰器
```python {3-5}
matcher = on_message()
@matcher.got("key")
async def handle_func(key: Message = Arg()):
# do something here
```
`got` 装饰器与 `receive` 装饰器一样,会中断当前事件处理流程,等待接收一个新的事件。但与 `receive` 装饰器不同的是,`got` 装饰器用于接收一条消息,并且可以控制是否向用户发送询问 `prompt` 等,更贴近于对话形式会话。
`got` 装饰器接受一个参数 `key` 和一个可选参数 `prompt`,当 `key` 不存在时,会向用户发送 `prompt` 消息,并等待用户回复。
在 `handle_func` 函数中,可以通过依赖注入的方式来获取接收到的消息,参考:[`Arg`](#arg)、[`ArgStr`](#argstr)、[`ArgPlainText`](#argplaintext)。
:::important 提示
`got` 装饰器可以和自身与 `receive` 装饰器嵌套使用
:::
### 直接添加
```python {2}
matcher = on_message(
handlers=[handle_func, or_dependent]
)
```
:::warning 注意
通过该方法添加的处理依赖将会处于整个事件处理流程的最前,因此,如果再使用 `handle` 等装饰器,则会在其之后。
:::
## 事件处理流程
在一个事件响应器中,事件被添加的处理依赖依次执行,直到所有处理依赖都执行完毕,或者遇到了某个处理依赖需要更多的事件来进行下一步的处理。在下一个事件到来并符合响应要求时,继续执行。更多有关 NoneBot 事件分发与处理流程的详细信息,请参考[进阶 - 深入](../../advanced/README.md)。
## 获取上下文信息
在事件处理流程中,事件响应器具有自己独立的上下文,例如:当前的事件、机器人等信息,可以通过依赖注入的方式来获取。
### Bot
获取当前事件的 Bot 对象。
```python {7-9}
from typing import Union
from nonebot.adapters import Bot
from nonebot.adapters.ding import Bot as DingBot
from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot
async def _(foo: Bot): ...
async def _(foo: Union[DingBot, OneBotV11Bot]): ...
async def _(bot): ... # 兼容性处理
```
### Event
获取当前事件。
```python {6-8}
from typing import Union
from nonebot.adapters import Event
from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent
async def _(foo: Event): ...
async def _(foo: Union[PrivateMessageEvent, GroupMessageEvent]): ...
async def _(event): ... # 兼容性处理
```
### EventType
获取当前事件的类型。
```python {3}
from nonebot.params import EventType
async def _(foo: str = EventType()): ...
```
### EventMessage
获取当前事件的消息。
```python {4}
from nonebot.adapters import Message
from nonebot.params import EventMessage
async def _(foo: str = EventMessage()): ...
```
### EventPlainText
获取当前事件的消息纯文本部分。
```python {3}
from nonebot.params import EventPlainText
async def _(foo: str = EventPlainText()): ...
```
### EventToMe
获取当前事件是否与机器人相关。
```python {3}
from nonebot.params import EventToMe
async def _(foo: bool = EventToMe()): ...
```
### State
获取当前事件处理上下文状态State 为一个字典,用户可以向 State 中添加数据来保存状态等操作。(请注意不要随意覆盖 State 中 NoneBot 的数据)
```python {4}
from nonebot.typing import T_State
async def _(foo: T_State): ...
```
### Command
获取当前命令型消息的元组形式命令名。
```python {7}
from nonebot import on_command
from nonebot.params import Command
matcher = on_command("cmd")
@matcher.handle()
async def _(foo: Tuple[str, ...] = Command()): ...
```
:::tip 提示
命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。
:::
### RawCommand
获取当前命令型消息的文本形式命令名。
```python {7}
from nonebot import on_command
from nonebot.params import RawCommand
matcher = on_command("cmd")
@matcher.handle()
async def _(foo: str = RawCommand()): ...
```
:::tip 提示
命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。
:::
### CommandArg
获取命令型消息命令后跟随的参数。
```python {8}
from nonebot import on_command
from nonebot.adapters import Message
from nonebot.params import CommandArg
matcher = on_command("cmd")
@matcher.handle()
async def _(foo: Message = CommandArg()): ...
```
:::tip 提示
命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。
:::
### ShellCommandArgs
获取 shell 命令解析后的参数。
:::tip 提示
如果参数解析失败,则为 [`ParserExit`](../../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。
由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。
:::
```python {8,12}
from nonebot import on_shell_command
from nonebot.params import ShellCommandArgs
matcher = on_shell_command("cmd", parser)
# 解析失败
@matcher.handle()
async def _(foo: ParserExit = ShellCommandArgs()): ...
# 解析成功
@matcher.handle()
async def _(foo: Dict[str, Any] = ShellCommandArgs()): ...
```
### ShellCommandArgv
获取 shell 命令解析前的参数列表。
```python {7}
from nonebot import on_shell_command
from nonebot.params import ShellCommandArgs
matcher = on_shell_command("cmd")
@matcher.handle()
async def _(foo: List[str] = ShellCommandArgv()): ...
```
### RegexMatched
获取正则匹配结果。
```python {7}
from nonebot import on_regex
from nonebot.params import RegexMatched
matcher = on_regex("regex")
@matcher.handle()
async def _(foo: str = RegexMatched()): ...
```
### RegexGroup
获取正则匹配结果的 group 元组。
```python {7}
from nonebot import on_regex
from nonebot.params import RegexGroup
matcher = on_regex("regex")
@matcher.handle()
async def _(foo: Tuple[Any, ...] = RegexGroup()): ...
```
### RegexDict
获取正则匹配结果的 group 字典。
```python {7}
from nonebot import on_regex
from nonebot.params import RegexDict
matcher = on_regex("regex")
@matcher.handle()
async def _(foo: Dict[str, Any] = RegexDict()): ...
```
### Matcher
获取当前事件响应器实例。
```python {7}
from nonebot import on_message
from nonebot.matcher import Matcher
foo = on_message()
@foo.handle()
async def _(matcher: Matcher): ...
```
### Received
获取某次 `receive` 接收的事件。
```python {8}
from nonebot import on_message
from nonebot.adapters import Event
from nonebot.params import Received
matcher = on_message()
@matcher.receive("id")
async def _(foo: Event = Received("id")): ...
```
### LastReceived
获取最近一次 `receive` 接收的事件。
```python {8}
from nonebot import on_message
from nonebot.adapters import Event
from nonebot.params import LastReceived
matcher = on_message()
@matcher.receive("any")
async def _(foo: Event = LastReceived()): ...
```
### Arg
获取某次 `got` 接收的参数。
```python {8-9}
from nonebot.params import Arg
from nonebot import on_message
from nonebot.adapters import Message
matcher = on_message()
@matcher.got("key")
async def _(key: Message = Arg()): ...
async def _(foo: Message = Arg("key")): ...
```
### ArgStr
获取某次 `got` 接收的参数,并转换为字符串。
```python {7-8}
from nonebot import on_message
from nonebot.params import ArgStr
matcher = on_message()
@matcher.got("key")
async def _(key: str = ArgStr()): ...
async def _(foo: str = ArgStr("key")): ...
```
### ArgPlainText
获取某次 `got` 接收的参数的纯文本部分。
```python {7-8}
from nonebot import on_message
from nonebot.params import ArgPlainText
matcher = on_message()
@matcher.got("key")
async def _(key: str = ArgPlainText()): ...
async def _(foo: str = ArgPlainText("key")): ...
```
### Exception
获取事件响应器运行中抛出的异常。
```python {4}
from nonebot.message import run_postprocessor
@run_postprocessor
async def _(e: Exception): ...
```
### Default
带有默认值的参数,便于复用依赖。
```python {1}
async def _(foo="bar"): ...
```

View File

@ -0,0 +1,138 @@
---
sidebar_position: 3
description: 定义事件响应器,对特定的事件进行处理
options:
menu:
weight: 26
category: guide
---
# 定义事件响应器
事件响应器(`Matcher`)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。为了方便开发者编写插件NoneBot2 在 `nonebot.plugin` 模块中为插件开发定义了一些辅助函数。首先,让我们来了解一下 `Matcher` 由哪些部分组成。
## 事件响应器的基本组成
### 事件响应器类型 `type`
事件响应器的类型即是该响应器所要响应的事件类型,只有在接收到的事件类型与该响应器的类型相同时,才会触发该响应器。如果类型留空,该响应器将会响应所有类型的事件。
NoneBot 内置了四种主要类型:`meta_event``message``notice``request`。通常情况下,协议适配器会将事件合理地分类至这四种类型中。如果有其他类型的事件需要响应,可以自行定义新的类型。
<!-- TODO: move session updater to advanced -->
:::warning 注意
当会话状态更新时,会执行 `type_updater` 以更新 `type` 属性,以便会话收到新事件时能够正确匹配。
`type_updater` 默认将 `type` 修改为 `message`,你也可以自行定义 `type_updater` 来控制 `type` 属性更新。`type_updater` 是一个返回 `str` 的函数,可选依赖注入参数参考类型 `T_TypeUpdater`
```python {3-5}
matcher = on_request()
@matcher.type_updater
async def update_type():
return "message"
```
:::
### 事件匹配规则
事件响应器的匹配规则是一个 `Rule` 对象,它是一系列 `checker` 的集合,当所有的 `checker` 都返回 `True` 时,才会触发该响应器。
规则编写方法参考[自定义规则](#自定义规则)。
:::warning 注意
当会话状态更新时,`rule` 会被清空,以便会话收到新事件时能够正确匹配。
:::
### 事件触发权限 `permission`
事件响应器的触发权限是一个 `Permission` 对象,它也是一系列 `checker` 的集合,当其中一个 `checker` 返回 `True` 时,就会触发该响应器。
权限编写方法参考[自定义权限](#自定义权限)。
:::warning 注意
与 `rule` 不同的是,`permission` 不会在会话状态更新时丢失,因此 `permission` 通常用于会话的响应控制。
并且,当会话状态更新时,会执行 `permission_updater` 以更新 `permission`。默认情况下,`permission_updater` 会在原有的 `permission` 基础上添加一个 `USER` 条件,以检查事件的 `session_id` 是否与当前会话一致。
你可以自行定义 `permission_updater` 来控制会话的响应权限更新。`permission_updater` 是一个返回 `Permission` 的函数,可选依赖注入参数参考类型 `T_PermissionUpdater`。
```python {3-5}
matcher = on_message()
@matcher.permission_updater
async def update_type(matcher: Matcher):
return matcher.permission # return same without session_id check
```
:::
### 优先级 `priority`
事件响应器的优先级代表事件响应器的执行顺序
:::warning 警告
同一优先级的事件响应器会**同时执行**,优先级数字**越小**越先响应!优先级请从 `1` 开始排序!
:::
### 阻断 `block`
当有任意事件响应器发出了阻止事件传递信号时,该事件将不再会传递给下一优先级,直接结束处理。
NoneBot 内置的事件响应器中,所有非 `command` 规则的 `message` 类型的事件响应器都会阻断事件传递,其他则不会。
在部分情况中,可以使用 `matcher.stop_propagation()` 方法动态阻止事件传播,该方法需要 `handler` 在参数中获取 `matcher` 实例后调用方法。
```python {5}
foo = on_request()
@foo.handle()
async def handle(matcher: Matcher):
matcher.stop_propagation()
```
### 有效期 `temp`/`expire_time`
事件响应器可以设置有效期,当事件响应器超过有效期时,将会被移除。
- `temp` 属性:配置事件响应器在下一次响应之后移除。
- `expire_time` 属性:配置事件响应器在指定时间之后移除。
## 创建事件响应器
在前面的介绍中,我们已经了解了事件响应器的组成,接下来我们就可以使用 `nonebot.plugin` 模块中定义的辅助函数来创建事件响应器。
```python {3}
from nonebot import on_message
matcher = on_message()
```
用于定义事件响应器的辅助函数已经在 `nonebot` 主模块中被 `re-export`,所以直接从 `nonebot` 导入即可。
辅助函数有以下几种:
1. `on`: 创建任何类型的事件响应器。
2. `on_metaevent`: 创建元事件响应器。
3. `on_message`: 创建消息事件响应器。
4. `on_request`: 创建请求事件响应器。
5. `on_notice`: 创建通知事件响应器。
6. `on_startswith`: 创建消息开头匹配事件响应器。
7. `on_endswith`: 创建消息结尾匹配事件响应器。
8. `on_keyword`: 创建消息关键词匹配事件响应器。
9. `on_command`: 创建命令消息事件响应器。
10. `on_shell_command`: 创建 shell 命令消息事件响应器。
11. `on_regex`: 创建正则表达式匹配事件响应器。
12. `CommandGroup`: 创建具有共同命令名称前缀的命令组。
13. `MatcherGroup`: 创建具有共同参数的响应器组。
其中,`on_metaevent` `on_message` `on_request` `on_notice` 函数都是在 `on` 的基础上添加了对应的事件类型 `type``on_startswith` `on_endswith` `on_keyword` `on_command` `on_shell_command` `on_regex` 函数都是在 `on_message` 的基础上添加了对应的匹配规则 `rule`。
## 自定义规则
<!-- TODO -->
## 自定义权限

View File

@ -0,0 +1,32 @@
---
sidebar_position: 6
description: 简单插件示例
---
import CodeBlock from "@theme/CodeBlock";
import Messenger from "@site/src/components/Messenger";
# 插件示例
## 命令式问答示例
import WeatherSource from "!!raw-loader!@site/../tests/examples/weather.py";
import WeatherTest from "!!raw-loader!@site/../tests/test_examples/test_weather.py";
<CodeBlock className="language-python">{WeatherSource}</CodeBlock>
<Messenger
msgs={[
{ position: "right", msg: "/天气" },
{ position: "left", msg: "你想查询哪个城市的天气呢?" },
{ position: "right", msg: "上海" },
{ position: "left", msg: "上海的天气是..." },
]}
/>
<details>
<summary>测试示例</summary>
<CodeBlock className="language-python">{WeatherTest}</CodeBlock>
</details>

View File

@ -0,0 +1,71 @@
---
sidebar_position: 0
description: 插件入门
---
# 插件入门
## 插件结构
在编写插件之前,首先我们需要了解一下插件的概念。
在 NoneBot 中,插件可以是 Python 的一个模块 `module`,也可以是一个包 `package` 。NoneBot 会在导入时对这些模块或包做一些特殊的处理使得他们成为一个插件。插件间应尽量减少耦合可以进行有限制的插件间调用NoneBot 能够正确解析插件间的依赖关系。
下面详细介绍两种插件的结构:
### 模块插件(单文件形式)
在合适的路径创建一个 `.py` 文件即可。例如在[创建项目](../create-project.mdx)中创建的项目中,我们可以在 `awesome_bot/plugins/` 目录中创建一个文件 `foo.py`
```tree title=Project {4}
📦 AweSome-Bot
├── 📂 awesome_bot
│ └── 📂 plugins
| └── 📜 foo.py
├── 📜 .env
├── 📜 .env.dev
├── 📜 .env.prod
├── 📜 .gitignore
├── 📜 bot.py
├── 📜 docker-compose.yml
├── 📜 Dockerfile
├── 📜 pyproject.toml
└── 📜 README.md
```
这个时候它已经可以被称为一个插件了,尽管它还什么都没做。
### 包插件(文件夹形式)
在合适的路径创建一个文件夹,并在文件夹内创建文件 `__init__.py` 即可。例如在[创建项目](../create-project.mdx)中创建的项目中,我们可以在 `awesome_bot/plugins/` 目录中创建一个文件夹 `foo`,并在这个文件夹内创建一个文件 `__init__.py`。
```tree title=Project {4,5}
📦 AweSome-Bot
├── 📂 awesome_bot
│ └── 📂 plugins
| └── 📂 foo
| └── 📜 __init__.py
├── 📜 .env
├── 📜 .env.dev
├── 📜 .env.prod
├── 📜 .gitignore
├── 📜 bot.py
├── 📜 docker-compose.yml
├── 📜 Dockerfile
├── 📜 pyproject.toml
└── 📜 README.md
```
这个时候 `foo` 就是一个合法的 Python 包了,同时也是合法的 NoneBot 插件,插件内容可以在 `__init__.py` 中编写。
## 创建插件
:::danger 警告
请注意,插件名称不能存在重复,即所有模块插件的文件名和所有包插件的文件夹名不能存在相同。
:::
除了通过手动创建的方式以外,还可以通过 nb-cli 来创建插件nb-cli 会为你在合适的位置创建一个模板包插件。
```bash
nb plugin create
```

View File

@ -0,0 +1,106 @@
---
sidebar_position: 1
description: 通过不同方式加载插件
options:
menu:
weight: 25
category: guide
---
# 加载插件
:::danger 警告
请勿在插件被加载前 `import` 插件模块,这会导致 NoneBot2 无法将其转换为插件而损失部分功能。
:::
加载插件通常在机器人的入口文件进行,例如在[创建项目](../create-project.mdx)中创建的项目中的 `bot.py` 文件。在 NoneBot2 初始化完成后即可加载插件。
```python title=bot.py {5}
import nonebot
nonebot.init()
# load your plugin here
nonebot.run()
```
加载插件的方式有多种,但在底层的加载逻辑是一致的。以下是为加载插件提供的几种方式:
## `load_plugin`
通过点分割模块名称来加载插件,通常用于加载单个插件或者是第三方插件。例如:
```python
nonebot.load_plugin("path.to.your.plugin")
```
## `load_plugins`
加载传入插件目录中的所有插件,通常用于加载一系列本地编写的插件。例如:
```python
nonebot.load_plugins("src/plugins", "path/to/your/plugins")
```
:::warning 警告
请注意,插件所在目录应该为相对机器人入口文件可导入的,例如与入口文件在同一目录下。
:::
## `load_all_plugins`
这种加载方式是以上两种方式的混合,加载所有传入的插件模块名称,以及所有给定目录下的插件。例如:
```python
nonebot.load_all_plugins(["path.to.your.plugin"], ["path/to/your/plugins"])
```
## `load_from_json`
通过 JSON 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 JSON 变种。通过读取 JSON 文件中的 `plugins` 字段和 `plugin_dirs` 字段进行加载。例如:
```json title=plugin_config.json
{
"plugins": ["path.to.your.plugin"],
"plugin_dirs": ["path/to/your/plugins"]
}
```
```python
nonebot.load_from_json("plugin_config.json", encoding="utf-8")
```
:::tip 提示
如果 JSON 配置文件中的字段无法满足你的需求,可以使用 [`load_all_plugins`](#load_all_plugins) 方法自行读取配置来加载插件。
:::
## `load_from_toml`
通过 TOML 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 TOML 变种。通过读取 TOML 文件中的 `[tool.nonebot]` Table 中的 `plugins` 和 `plugin_dirs` Array 进行加载。例如:
```toml title=plugin_config.toml
[tool.nonebot]
plugins = ["path.to.your.plugin"]
plugin_dirs = ["path/to/your/plugins"]
```
```python
nonebot.load_from_toml("plugin_config.toml", encoding="utf-8")
```
:::tip 提示
如果 TOML 配置文件中的字段无法满足你的需求,可以使用 [`load_all_plugins`](#load_all_plugins) 方法自行读取配置来加载插件。
:::
## `load_builtin_plugin`
加载一个内置插件,是 [`load_plugin`](#load_plugin) 的封装。例如:
```python
nonebot.load_builtin_plugin("echo")
```
## 嵌套插件
<!-- TODO -->

View File

@ -0,0 +1,146 @@
---
sidebar_position: 5
description: 使用事件响应器操作,改变事件处理流程
options:
menu:
weight: 28
category: guide
---
# 事件响应器操作
在事件处理流程中,我们可以使用事件响应器操作来进行一些交互或改变事件处理流程。
## send
向用户回复一条消息。回复的方式或途径由协议适配器自行实现。
可以是 `str`、[`Message`](../../api/adapters/index.md#Message)、[`MessageSegment`](../../api/adapters/index.md#MessageSegment) 或 [`MessageTemplate`](../../api/adapters/index.md#MessageTemplate)。
这个操作等同于使用 `bot.send(event, message, **kwargs)` 但不需要自行传入 `event`
```python {3}
@matcher.handle()
async def _():
await matcher.send("Hello world!")
```
## finish
向用户回复一条消息(可选),并立即结束当前事件的整个处理流程。
参数与 [`send`](#send) 相同。
```python {3}
@matcher.handle()
async def _():
await matcher.finish("Hello world!")
# something never run
...
```
## pause
向用户回复一条消息(可选),并立即结束当前事件处理依赖并等待接收一个新的事件后进入下一个事件处理依赖。
类似于 `receive` 的行为但可以根据事件来决定是否接收新的事件。
```python {4}
@matcher.handle()
async def _():
if serious:
await matcher.pause("Confirm?")
@matcher.handle()
async def _():
...
```
## reject
向用户回复一条消息(可选),并立即结束当前事件处理依赖并等待接收一个新的事件后再次执行当前事件处理依赖。
通常用于拒绝当前 `receive` 接收的事件或 `got` 接收的参数(如:不符合格式或标准)。
```python {4}
@matcher.got("arg")
async def _(arg: str = ArgPlainText()):
if not is_valid(arg):
await matcher.reject("Invalid arg!")
```
## reject_arg
向用户回复一条消息(可选),并立即结束当前事件处理依赖并等待接收一个新的事件后再次执行当前事件处理依赖。
用于拒绝指定 `got` 接收的参数,通常在嵌套装饰器时使用。
```python {4}
@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
向用户回复一条消息(可选),并立即结束当前事件处理依赖并等待接收一个新的事件后再次执行当前事件处理依赖。
用于拒绝指定 `receive` 接收的事件,通常在嵌套装饰器时使用。
```python {4}
@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 {2}
async def dependency(matcher: Matcher):
matcher.skip()
@matcher.handle()
async def _(sub=Depends(dependency)):
# never run
...
```
## get_receive
获取一个 `receive` 接收的事件。
## set_receive
设置/覆盖一个 `receive` 接收的事件。
## get_last_receive
获取最近一次 `receive` 接收的事件。
## get_arg
获取一个 `got` 接收的参数。
## set_arg
设置/覆盖一个 `got` 接收的参数。
## stop_propagation
阻止事件向更低优先级的事件响应器传播。
```python
@foo.handle()
async def _(matcher: Matcher):
matcher.stop_propagation()
```

View File

@ -0,0 +1,252 @@
---
sidebar_position: 9
description: 处理消息序列与消息段
options:
menu:
weight: 30
category: guide
---
# 处理消息
## NoneBot2 中的消息
在不同平台中,一条消息可能会有承载有各种不同的表现形式,它可能是一段纯文本、一张图片、一段语音、一篇富文本文章,也有可能是多种类型的组合等等。
在 NoneBot2 中,为确保消息的正常处理与跨平台兼容性,采用了扁平化的消息序列形式,即 `Message` 对象。
`Message` 是多个消息段 `MessageSegment` 的集合,它继承自 `List[MessageSegment]`,并在此基础上添加或强化了一些特性。
`MessageSegment` 是一个 [`dataclass`](https://docs.python.org/zh-cn/3/library/dataclasses.html#dataclasses.dataclass) ,它具有一个类型标识 `type`,以及一些对应的数据信息 `data`
此外NoneBot2 还提供了 `MessageTemplate` ,用于构建支持消息序列以及消息段的特殊消息模板。
## 使用消息序列
通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过 [`EventMessage`](./plugin/create-handler.md#EventMessage) 作为依赖注入, 或者使用 `event.get_message()` 获取。
由于它是`List[MessageSegment]`的子类, 所以你总是可以用和操作 List 类似的方式来处理消息序列
```python
>>> message = Message([
MessageSegment(type='text', data={'text':'hello'}),
MessageSegment(type='image', data={'url':'http://example.com/image.png'}),
MessageSegment(type='text', data={'text':'world'}),
])
>>> for segment in message:
... print(segment.type, segment.data)
...
text {'text': 'hello'}
image {'url': 'http://example.com/image.png'}
text {'text': 'world'}
>>> len(message)
3
```
### 构造消息序列
在使用事件响应器操作发送消息时,既可以使用 `str` 作为消息,也可以使用 `Message``MessageSegment` 或者 `MessageTemplate`。那么,我们就需要先构造一个消息序列。
#### 直接构造
`Message` 类可以直接实例化,支持 `str``MessageSegment``Iterable[MessageSegment]` 或适配器自定义类型的参数。
```python
# str
Message("Hello, world!")
# MessageSegment
Message(MessageSegment.text("Hello, world!"))
# List[MessageSegment]
Message([MessageSegment.text("Hello, world!")])
```
#### 运算构造
`Message` 对象可以通过 `str``MessageSegment` 相加构造,详情请参考[拼接消息](#拼接消息)。
#### 从字典数组构造
`Message` 对象支持 Pydantic 自定义类型构造,可以使用 Pydantic 的 `parse_obj_as` (`parse_raw_as`) 方法进行构造。
```python
from pydantic import parse_obj_as
# 由字典构造消息段
parse_obj_as(
MessageSegment, {"type": "text", "data": {"text": "text"}}
) == MessageSegment.text("text")
# 由字典数组构造消息序列
parse_obj_as(
Message,
[MessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
```
:::tip 提示
以上示例中的字典数据仅做参考,具体的数据格式由适配器自行定义。
:::
### 获取消息纯文本
由于消息中存在各种类型的消息段,因此 `str(message)` 通常并不能得到消息的纯文本,而是一个消息序列的字符串表示。
NoneBot2 为消息段定义了一个方法 `is_text()` ,可以用于判断消息段是否为纯文本;也可以使用 `message.extract_plain_text()` 方法获取消息纯文本。
```python
# 判断消息段是否为纯文本
MessageSegment.text("text").is_text() == True
# 提取消息纯文本字符串
Message(
[MessageSegment.text("text"), MessageSegment.at(123)]
).extract_plain_text() == "text"
```
### 遍历
`Message` 继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。
```python
for segment in message:
...
```
### 索引与切片
`Message` 对列表的索引与切片进行了增强,在原有列表 int 索引与切片的基础上,支持 `type` 过滤索引与切片。
```python
message = Message(
[
MessageSegment.text("test"),
MessageSegment.image("test2"),
MessageSegment.image("test3"),
MessageSegment.text("test4"),
]
)
# 索引
message[0] == MessageSegment.text("test")
# 切片
message[0:2] == Message(
[MessageSegment.text("test"), MessageSegment.image("test2")]
)
# 类型过滤
message["image"] == Message(
[MessageSegment.image("test2"), MessageSegment.image("test3")]
)
# 类型索引
message["image", 0] == MessageSegment.image("test2")
# 类型切片
message["image", 0:2] == Message(
[MessageSegment.image("test2"), MessageSegment.image("test3")]
)
```
同样的,`Message` 对列表的 `index``count` 方法也进行了增强,可以用于索引指定类型的消息段。
```python
# 指定类型首个消息段索引
message.index("image") == 1
# 指定类型消息段数量
message.count("image") == 2
```
此外,`Message` 添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。
```python
# 获取指定类型指定个数的消息段
message.get("image", 1) == Message([MessageSegment.image("test2")])
```
### 拼接消息
`str``Message``MessageSegment` 对象之间可以直接相加,相加均会返回一个新的 `Message` 对象。
```python
# 消息序列与消息段相加
Message([MessageSegment.text("text")]) + MessageSegment.text("text")
# 消息序列与字符串相加
Message([MessageSegment.text("text")]) + "text"
# 消息序列与消息序列相加
Message([MessageSegment.text("text")]) + Message([MessageSegment.text("text")])
# 字符串与消息序列相加
"text" + Message([MessageSegment.text("text")])
# 消息段与消息段相加
MessageSegment.text("text") + MessageSegment.text("text")
# 消息段与字符串相加
MessageSegment.text("text") + "text"
# 消息段与消息序列相加
MessageSegment.text("text") + Message([MessageSegment.text("text")])
# 字符串与消息段相加
"text" + MessageSegment.text("text")
```
如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append``Message.extend` 方法,或者使用自加。
```python
msg = Message([MessageSegment.text("text")])
# 自加
msg += "text"
msg += MessageSegment.text("text")
msg += Message([MessageSegment.text("text")])
# 附加
msg.append("text")
msg.append(MessageSegment.text("text"))
# 扩展
msg.extend([MessageSegment.text("text")])
```
## 使用消息模板
为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列
它在以下常见场景中尤其有用:
- 多行富文本编排(包含图片,文字以及表情等)
- 客制化(由 Bot 最终用户提供消息模板时)
在事实上, 它的用法和`str.format`极为相近, 所以你在使用的时候, 总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果
这里给出几个简单的例子:
:::tip
这里面所有的`Message`均是用对应 Adapter 的实现导入的, 而不是抽象基类
:::
```python title="基础格式化用法"
>>> Message.template("{} {}").format("hello", "world")
Message(
MessageSegment.text("hello"),
MessageSegment.text(" "),
MessageSegment.text("world")
)
```
```python title="对消息段进行安全的拼接"
>>> Message.template("{} {}").format(MessageSegment.image("file:///..."), "world")
Message(
MessageSegment(type='image', data={'file': 'file:///...'}),
MessageSegment(type='text', data={'text': 'world'})
)
```
```python title="以消息对象作为模板"
>>> Message.template(
... MessageSegment.text('test {user_id}') + MessageSegment.face(233) +
... MessageSegment.text('test {message}')).format_map({'user_id':123456, 'message':'hello world'}
... )
Message(
MessageSegment(type='text', data={'text': 'test 123456'}),
MessageSegment(type='face', data={'face': 233}),
MessageSegment(type='text', data={'text': 'test hello world'})
)
```
```python title="使用消息段的拓展格式规格"
>>> Message.template("{link:image}").format(link='https://...')
Message(MessageSegment(type='image', data={'file': 'https://...'}))
```

View File

@ -0,0 +1,93 @@
---
sidebar_position: 6
description: 协议适配器的功能与使用
options:
menu:
weight: 23
category: guide
---
# 使用适配器
:::tip 提示
如何**安装**协议适配器请参考[安装协议适配器](../start/install-adapter.mdx)。
:::
## 协议适配器的功能
由于 NoneBot2 的跨平台特性,需要支持不同的协议,因此需要对特定的平台协议编写一个转换器。
协议适配器即是充当中间人的转换器,它将驱动器所收到的数据转换为可以被 NoneBot2 处理的事件 Event并将事件传递给 NoneBot2。
同时,协议适配器还会处理 API 调用,转换为可以被驱动器处理的数据发送出去。
## 注册协议适配器
NoneBot2 在默认情况下并不会加载任何协议适配器,需要自己手动注册。下方是个加载协议适配器的例子:
```python title=bot.py
import nonebot
from your_adapter_package import Adapter
nonebot.init()
driver = nonebot.get_driver()
driver.register_adapter(Adapter)
nonebot.run()
```
加载步骤如下:
### 导入协议适配器
首先从你需要的协议适配器的包中导入适配器类,通常为 `Adapter`
```python title=bot.py {2}
import nonebot
from your_adapter_package import Adapter
nonebot.init()
driver = nonebot.get_driver()
driver.register_adapter(Adapter)
nonebot.run()
```
### 获得驱动器实例
加载协议适配器需要通过驱动器来进行,因此,你需要先初始化 NoneBot2并获得驱动器实例。
```python title=bot.py {4,5}
import nonebot
from your_adapter_package import Adapter
nonebot.init()
driver = nonebot.get_driver()
driver.register_adapter(Adapter)
nonebot.run()
```
### 注册
获得驱动器实例后,你需要调用 `register_adapter` 方法来注册协议适配器。NoneBot 会通过协议适配器的 `get_name` 方法来获得协议适配器的名字。
:::warning 注意
你可以多次调用来注册多个协议适配器,但不能注册多次相同的协议适配器,发生这种情况时 NoneBot 会给出一个警告并忽略这次注册。
:::
```python title=bot.py {6}
import nonebot
from your_adapter_package import Adapter
nonebot.init()
driver = nonebot.get_driver()
driver.register_adapter(Adapter)
nonebot.run()
```
:::danger 警告
协议适配器需要在 NoneBot 启动前进行注册,即 `nonebot.run()` 之前,否则会出现未知的错误。
:::