mirror of
				https://github.com/nonebot/nonebot2.git
				synced 2025-10-31 06:56:39 +00:00 
			
		
		
		
	📝 Docs: 重写教程与进阶指南 (#1604)
Co-authored-by: Johnny Hsieh <32300164+mnixry@users.noreply.github.com>
This commit is contained in:
		| @@ -1,42 +0,0 @@ | ||||
| --- | ||||
| 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!"} | ||||
| ``` | ||||
							
								
								
									
										110
									
								
								website/docs/tutorial/application.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								website/docs/tutorial/application.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| --- | ||||
| sidebar_position: 0 | ||||
| description: 创建一个 NoneBot 项目 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 20 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 手动创建项目 | ||||
|  | ||||
| 在[快速上手](./quick-start.mdx)中,我们已经介绍了如何安装和使用 `nb-cli` 创建一个项目。在本章节中,我们将简要介绍如何在不使用 `nb-cli` 的方式创建一个机器人项目的**最小实例**并启动。如果你想要了解 NoneBot 的启动流程,也可以阅读本章节。 | ||||
|  | ||||
| :::warning | ||||
| 我们十分不推荐直接创建机器人项目,请优先考虑使用 nb-cli 进行项目创建。 | ||||
| ::: | ||||
|  | ||||
| 一个机器人项目的**最小实例**中**至少**需要包含以下内容: | ||||
|  | ||||
| - 入口文件:初始化并运行机器人的 Python 文件 | ||||
| - 配置文件:存储机器人启动所需的配置 | ||||
| - 插件:为机器人提供具体的功能 | ||||
|  | ||||
| 下面我们创建一个项目文件夹,来存放项目所需文件,以下步骤均在该文件夹中进行。 | ||||
|  | ||||
| ## 安装依赖 | ||||
|  | ||||
| 在创建项目前,我们首先需要将项目所需依赖安装至环境中。 | ||||
|  | ||||
| 1. (可选)创建虚拟环境,以 venv 为例 | ||||
|  | ||||
|    ```bash | ||||
|    python -m venv .venv --prompt nonebot2 | ||||
|    # windows | ||||
|    .venv\Scripts\activate | ||||
|    # linux/macOS | ||||
|    source .venv/bin/activate | ||||
|    ``` | ||||
|  | ||||
| 2. 安装 nonebot2 以及驱动器 | ||||
|  | ||||
|    ```bash | ||||
|    pip install 'nonebot2[fastapi]' | ||||
|    ``` | ||||
|  | ||||
|    驱动器包名可以在 [驱动器商店](/store) 中找到。 | ||||
|  | ||||
| 3. 安装适配器 | ||||
|  | ||||
|    ```bash | ||||
|    pip install nonebot-adapter-console | ||||
|    ``` | ||||
|  | ||||
|    适配器包名可以在 [适配器商店](/store) 中找到。 | ||||
|  | ||||
| ## 创建配置文件 | ||||
|  | ||||
| 配置文件用于存放 NoneBot 运行所需要的配置项,使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合 dotenv 格式,复杂类型数据需使用 JSON 格式填写。具体可选配置方式以及配置项详情参考[配置](../appendices/config.mdx)。 | ||||
|  | ||||
| 在**项目文件夹**中创建一个 `.env` 文本文件,并写入以下内容: | ||||
|  | ||||
| ```bash title=.env | ||||
| HOST=0.0.0.0  # 配置 NoneBot 监听的 IP / 主机名 | ||||
| PORT=8080  # 配置 NoneBot 监听的端口 | ||||
| COMMAND_START=["/"]  # 配置命令起始字符 | ||||
| COMMAND_SEP=["."]  # 配置命令分割字符 | ||||
| ``` | ||||
|  | ||||
| ## 创建入口文件 | ||||
|  | ||||
| 入口文件( Entrypoint )顾名思义,是用来初始化并运行机器人的 Python 文件。入口文件需要完成框架的初始化、注册适配器、加载插件等工作。 | ||||
|  | ||||
| :::tip 提示 | ||||
| 如果你使用 `nb-cli` 创建项目,入口文件不会被创建,该文件功能会被 `nb run` 命令代替。 | ||||
| ::: | ||||
|  | ||||
| 在**项目文件夹**中创建一个 `bot.py` 文件,并写入以下内容: | ||||
|  | ||||
| ```python title=bot.py | ||||
| import nonebot | ||||
| from nonebot.adapters.console import Adapter as ConsoleAdapter  # 避免重复命名 | ||||
|  | ||||
| # 初始化 NoneBot | ||||
| nonebot.init() | ||||
|  | ||||
| # 注册适配器 | ||||
| driver = nonebot.get_driver() | ||||
| driver.register_adapter(ConsoleAdapter) | ||||
|  | ||||
| # 在这里加载插件 | ||||
| nonebot.load_builtin_plugins("echo")  # 内置插件 | ||||
| # nonebot.load_plugin("thirdparty_plugin")  # 第三方插件 | ||||
| # nonebot.load_plugins("awesome_bot/plugins")  # 本地插件 | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     nonebot.run() | ||||
| ``` | ||||
|  | ||||
| 我们暂时不需要了解其中内容的含义,这些将会在稍后的章节中逐一介绍。在创建完成以上文件并确认已安装所需适配器和插件后,即可运行机器人。 | ||||
|  | ||||
| ## 运行机器人 | ||||
|  | ||||
| 在**项目文件夹**中,使用配置好环境的 Python 解释器运行入口文件(如果使用虚拟环境,请先激活虚拟环境): | ||||
|  | ||||
| ```bash | ||||
| python bot.py | ||||
| ``` | ||||
|  | ||||
| 如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。 | ||||
| @@ -1,49 +0,0 @@ | ||||
| --- | ||||
| sidebar_position: 8 | ||||
| description: 调用机器人平台 API,完成更多的功能 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 29 | ||||
|     category: guide | ||||
| --- | ||||
|  | ||||
| # 调用平台 API | ||||
|  | ||||
| 在使用机器人功能时,除了发送消息以外,还可能需要调用机器人平台的 API 来完成更多的功能。 | ||||
|  | ||||
| NoneBot 提供了两种方式来调用机器人平台 API,两种方式都需要首先获得 Bot 实例,然后调用相应的方法。 | ||||
|  | ||||
| ## 获取 Bot 实例 | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_bot | ||||
|  | ||||
| bot = get_bot()  # 获取第一个已连接的 bot 实例 | ||||
| bot = get_bot("bot_id")  # 获取指定 bot_id 的 bot 实例 | ||||
| ``` | ||||
|  | ||||
| 在事件处理依赖中,我们可以使用更为简便的办法来获取 bot 实例,详情可以参考 [获取上下文信息-Bot](https://v2.nonebot.dev/docs/tutorial/plugin/create-handler#bot) | ||||
|  | ||||
| ```python | ||||
| from nonebot.adapters import Bot | ||||
|  | ||||
| async def handle_func(bot: Bot):  # 通过依赖注入获取 bot 实例 | ||||
|     ... | ||||
| ``` | ||||
|  | ||||
| ## 调用 API | ||||
|  | ||||
| 如果需要调用某个机器人平台的 `get_user_info` API,我们可以使用以下任意一种方式: | ||||
|  | ||||
| ```python | ||||
| # 通过 bot 实例上的魔术方法直接使用.操作符调用 API | ||||
| result = await bot.get_user_info(user_id=12345678) | ||||
|  | ||||
| # 通过 bot 实例上的 call_api 方法调用 API | ||||
| result = await bot.call_api("get_user_info", user_id=12345678) | ||||
| ``` | ||||
|  | ||||
| :::tip 提示 | ||||
| 实际可用的 API 由平台提供,请参考平台文档。 | ||||
| ::: | ||||
| @@ -1,274 +0,0 @@ | ||||
| --- | ||||
| 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") | ||||
| ``` | ||||
|  | ||||
| :::warning 警告 | ||||
| 在 Windows 平台上开启该功能有可能会造成预料之外的影响! | ||||
|  | ||||
| 在 `Python>=3.8` 环境下开启该功能后,在 uvicorn 运行时(FastAPI 提供的 ASGI 底层,即 reload 功能的实际来源),asyncio 使用的事件循环会被 uvicorn 从默认的 `ProactorEventLoop` 强制切换到 `SelectorEventLoop` | ||||
|  | ||||
| > 相关信息参考 [uvicorn#529](https://github.com/encode/uvicorn/issues/529),[uvicorn#1070](https://github.com/encode/uvicorn/pull/1070),[uvicorn#1257](https://github.com/encode/uvicorn/pull/1257) | ||||
|  | ||||
| 后者(`SelectorEventLoop`)在 Windows 平台的可使用性不如前者(`ProactorEventLoop`),包括但不限于 | ||||
|  | ||||
| 1. 不支持创建子进程 | ||||
| 2. 最多只支持 512 个套接字 | ||||
| 3. ... | ||||
|  | ||||
| > 具体信息参考 [Python 文档](https://docs.python.org/zh-cn/3/library/asyncio-platforms.html#windows) | ||||
|  | ||||
| 所以,一些使用了 asyncio 的库因此可能无法正常工作,如: | ||||
|  | ||||
| 1. [playwright](https://playwright.dev/python/docs/intro#incompatible-with-selectoreventloop-of-asyncio-on-windows) | ||||
|  | ||||
| 如果在开启该功能后,原本**正常运行**的代码报错,且打印的异常堆栈信息和 asyncio 有关(异常一般为 `NotImplementedError`), | ||||
| 你可能就需要考虑相关库对事件循环的支持,以及是否启用该功能 | ||||
| ::: | ||||
|  | ||||
| ##### `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 默认值 | ||||
|  | ||||
| ##### `fastapi_extra` | ||||
|  | ||||
| 类型:`Dist[str, Any]`   | ||||
| 默认值:`{}`   | ||||
| 说明:传递给 `FastAPI` 的其他参数 | ||||
|  | ||||
| ### 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 默认值 | ||||
|  | ||||
| ##### `quart_extra` | ||||
|  | ||||
| 类型:`Dist[str, Any]`   | ||||
| 默认值:`{}`   | ||||
| 说明:传递给 `Quart` 的其他参数 | ||||
|  | ||||
| ### 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` | ||||
| ::: | ||||
| @@ -1,240 +0,0 @@ | ||||
| --- | ||||
| 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 | ||||
| ``` | ||||
							
								
								
									
										226
									
								
								website/docs/tutorial/create-plugin.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								website/docs/tutorial/create-plugin.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | ||||
| --- | ||||
| sidebar_position: 3 | ||||
| description: 创建并加载自定义插件 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 50 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 插件编写准备 | ||||
|  | ||||
| 在正式编写插件之前,我们需要先了解一下插件的概念。 | ||||
|  | ||||
| ## 插件结构 | ||||
|  | ||||
| 在 NoneBot 中,插件即是 Python 的一个[模块(module)](https://docs.python.org/zh-cn/3/glossary.html#term-module)。NoneBot 会在导入时对这些模块做一些特殊的处理使得他们成为一个插件。插件间应尽量减少耦合,可以进行有限制的相互调用,NoneBot 能够正确解析插件间的依赖关系。 | ||||
|  | ||||
| ### 单文件插件 | ||||
|  | ||||
| 一个普通的 `.py` 文件即可以作为一个插件,例如创建一个 `foo.py` 文件: | ||||
|  | ||||
| ```tree title=Project | ||||
| 📂 plugins | ||||
| └── 📜 foo.py | ||||
| ``` | ||||
|  | ||||
| 这个时候模块 `foo` 已经可以被称为一个插件了,尽管它还什么都没做。 | ||||
|  | ||||
| ### 包插件 | ||||
|  | ||||
| 一个包含 `__init__.py` 的文件夹即是一个常规 Python [包 `package`](https://docs.python.org/zh-cn/3/glossary.html#term-regular-package),例如创建一个 `foo` 文件夹: | ||||
|  | ||||
| ```tree title=Project | ||||
| 📂 plugins | ||||
| └── 📂 foo | ||||
|     └── 📜 __init__.py | ||||
| ``` | ||||
|  | ||||
| 这个时候包 `foo` 同样是一个合法的插件,插件内容可以在 `__init__.py` 文件中编写。 | ||||
|  | ||||
| ## 创建插件 | ||||
|  | ||||
| :::warning 注意 | ||||
| 如果在之前的[快速上手](../quick-start.mdx)章节中已经使用 `bootstrap` 模板创建了项目,那么你需要做出如下修改: | ||||
|  | ||||
| 1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins` | ||||
|  | ||||
|    ```tree title=Project | ||||
|    📦 awesome-bot | ||||
|    ├── 📂 awesome_bot | ||||
|    │   └── 📂 plugins | ||||
|    ├── 📜 pyproject.toml | ||||
|    └── 📜 README.md | ||||
|    ``` | ||||
|  | ||||
| 2. 修改 `pyproject.toml` 文件中的 `nonebot` 配置项,在 `plugin_dirs` 中添加 `awesome_bot/plugins` | ||||
|  | ||||
|    ```toml title=pyproject.toml | ||||
|    [tool.nonebot] | ||||
|    plugin_dirs = ["awesome_bot/plugins"] | ||||
|    ``` | ||||
|  | ||||
| ::: | ||||
|  | ||||
| :::warning 注意 | ||||
| 如果在之前的[创建项目](./application.md)章节中手动创建了相关文件,那么你需要做出如下修改: | ||||
|  | ||||
| 1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins` | ||||
|  | ||||
|    ```tree title=Project | ||||
|    📦 awesome-bot | ||||
|    ├── 📂 awesome_bot | ||||
|    │   └── 📂 plugins | ||||
|    └── 📜 bot.py | ||||
|    ``` | ||||
|  | ||||
| 2. 修改 `bot.py` 文件中的加载插件部分,取消注释或者添加如下代码 | ||||
|  | ||||
|    ```python title=bot.py | ||||
|    # 在这里加载插件 | ||||
|    nonebot.load_builtin_plugins("echo")  # 内置插件 | ||||
|    nonebot.load_plugins("awesome_bot/plugins")  # 本地插件 | ||||
|    ``` | ||||
|  | ||||
| ::: | ||||
|  | ||||
| 创建插件可以通过 `nb-cli` 命令从完整模板创建,也可以手动新建空白文件。通过以下命令创建一个名为 `weather` 的插件: | ||||
|  | ||||
| ```bash | ||||
| $ nb plugin create | ||||
| [?] 插件名称: weather | ||||
| [?] 使用嵌套插件? (y/N) N | ||||
| [?] 输出目录: awesome_bot/plugins | ||||
| ``` | ||||
|  | ||||
| `nb-cli` 会在 `awesome_bot/plugins` 目录下创建一个名为 `weather` 的文件夹,其中包含的文件将在稍后章节中用到。 | ||||
|  | ||||
| ```tree title=Project | ||||
| 📦 awesome-bot | ||||
| ├── 📂 awesome_bot | ||||
| │   └── 📂 plugins | ||||
| |       └── 📂 foo | ||||
| |           ├── 📜 __init__.py | ||||
| |           └── 📜 config.py | ||||
| ├── 📜 pyproject.toml | ||||
| └── 📜 README.md | ||||
| ``` | ||||
|  | ||||
| ## 加载插件 | ||||
|  | ||||
| :::danger 警告 | ||||
| 请勿在插件被加载前 `import` 插件模块,这会导致 NoneBot 无法将其转换为插件而出现意料之外的情况。 | ||||
| ::: | ||||
|  | ||||
| 加载插件是在机器人入口文件中完成的,需要在框架初始化之后,运行之前进行。 | ||||
|  | ||||
| 请注意,加载的插件模块名称(插件文件名或文件夹名)**不能相同**,且每一个插件**只能被加载一次**,重复加载将会导致异常。 | ||||
|  | ||||
| 如果你使用 `nb-cli` 管理插件,那么你可以跳过这一节,`nb-cli` 将会自动处理加载。 | ||||
|  | ||||
| 如果你**使用自定义的入口文件** `bot.py`,那么你需要在 `bot.py` 中加载插件。 | ||||
|  | ||||
| ```python {5} title=bot.py | ||||
| import nonebot | ||||
|  | ||||
| nonebot.init() | ||||
|  | ||||
| # 加载插件 | ||||
|  | ||||
| nonebot.run() | ||||
| ``` | ||||
|  | ||||
| 加载插件的方式有多种,但在底层的加载逻辑是一致的。以下是为加载插件提供的几种方式: | ||||
|  | ||||
| ### `load_plugin` | ||||
|  | ||||
| 通过点分割模块名称或使用 [`pathlib`](https://docs.python.org/zh-cn/3/library/pathlib.html) 的 `Path` 对象来加载插件。通常用于加载第三方插件或者项目插件。例如: | ||||
|  | ||||
| ```python | ||||
| from pathlib import Path | ||||
|  | ||||
| nonebot.load_plugin("path.to.your.plugin")  # 加载第三方插件 | ||||
| nonebot.load_plugin(Path("./path/to/your/plugin.py"))  # 加载项目插件 | ||||
| ``` | ||||
|  | ||||
| :::warning 注意 | ||||
| 请注意,本地插件的路径应该为相对机器人 **入口文件(通常为 bot.py)** 可导入的,例如在项目 `plugins` 目录下。 | ||||
| ::: | ||||
|  | ||||
| ### `load_plugins` | ||||
|  | ||||
| 加载传入插件目录中的所有插件,通常用于加载一系列本地编写的项目插件。例如: | ||||
|  | ||||
| ```python | ||||
| nonebot.load_plugins("src/plugins", "path/to/your/plugins") | ||||
| ``` | ||||
|  | ||||
| :::warning 注意 | ||||
| 请注意,插件目录应该为相对机器人 **入口文件(通常为 bot.py)** 可导入的,例如在项目 `plugins` 目录下。 | ||||
| ::: | ||||
|  | ||||
| ### `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` | ||||
|  | ||||
| 加载一个内置插件,传入的插件名必须为 NoneBot 内置插件。该方法是 [`load_plugin`](#load_plugin) 的封装。例如: | ||||
|  | ||||
| ```python | ||||
| nonebot.load_builtin_plugin("echo") | ||||
| ``` | ||||
|  | ||||
| ### `load_builtin_plugins` | ||||
|  | ||||
| 加载传入插件列表中的所有内置插件。例如: | ||||
|  | ||||
| ```python | ||||
| nonebot.load_builtin_plugins("echo", "single_session") | ||||
| ``` | ||||
|  | ||||
| ### 其他加载方式 | ||||
|  | ||||
| 有关其他插件加载的方式,可参考[跨插件访问](../advanced/requiring.md)和[嵌套插件](../advanced/plugin-nesting.md)。 | ||||
| @@ -1,79 +0,0 @@ | ||||
| --- | ||||
| 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 server,nb-cli 将会为你启动**冷重载模式**(当文件发生变动时自动重启 NoneBot2 实例) | ||||
| ::: | ||||
| @@ -1,59 +0,0 @@ | ||||
| --- | ||||
| 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") | ||||
| ``` | ||||
| @@ -1,318 +0,0 @@ | ||||
| --- | ||||
| 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`: 部署服务器的 SSH 地址 | ||||
| - `DEPLOY_USER`: 部署服务器用户名 | ||||
| - `DEPLOY_KEY`: 部署服务器私钥 ([创建方法](https://github.com/appleboy/ssh-action#setting-up-a-ssh-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@v1 | ||||
|         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.2 | ||||
|         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 部署 | ||||
|  | ||||
| :::tip 提示 | ||||
| 在阅读这一节的过程中, 你总是可以参照 [PM2 官方文档](https://pm2.keymetrics.io/docs/usage/quick-start/) 来得到更多的信息 | ||||
| ::: | ||||
|  | ||||
| ### 安装 PM2 | ||||
|  | ||||
| 需要有 NodeJS 10+环境来运行 PM2, ~~(什么 NTR)~~ | ||||
|  | ||||
| 然后通过以下命令安装即可: | ||||
|  | ||||
| ```shell | ||||
| npm install -g pm2 | ||||
| ``` | ||||
|  | ||||
| 在安装完成后, 执行以下指令, 如果得到类似的输出则说明你安装成功了 PM2: | ||||
|  | ||||
| ```shell | ||||
| $ pm2 -V | ||||
| 5.2.0 | ||||
| ``` | ||||
|  | ||||
| ### 在后台运行进程 | ||||
|  | ||||
| :::tip 提示 | ||||
| 以下步骤要求您在您 Bot 的工作目录下执行 | ||||
|  | ||||
| 如果您使用了虚拟环境, 请确保 Bot 启动命令能在虚拟环境中正常执行 | ||||
|  | ||||
| 换言之, Bot 程序需要在当前终端环境下正常运行 | ||||
| ::: | ||||
|  | ||||
| #### 启动 Bot 进程 | ||||
|  | ||||
| ```shell | ||||
| $ pm2 start "python -m nb_cli run" # 或者直接 nb run 也行 | ||||
|  | ||||
| [PM2] Starting /usr/bin/bash in fork_mode (1 instance) | ||||
| [PM2] Done. | ||||
| ┌─────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ | ||||
| │ id  │ name   │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │ | ||||
| ├─────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ | ||||
| │ 0   │ nb run │ default     │ N/A     │ fork    │ 93061    │ 0s     │ 0    │ online    │ 0%       │ 8.3mb    │ mix      │ disabled │ | ||||
| └─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ | ||||
| ``` | ||||
|  | ||||
| 此时 Bot 进程就在后台运行了, 注意到表格第一列的 ID, 它可以用来查看和控制进程的状态 | ||||
|  | ||||
| #### 常用命令 | ||||
|  | ||||
| 更具体的用法请移步 PM2 官方文档, ~~如果想要详细示例建议直接上手试试~~ | ||||
|  | ||||
| 其中命令中的所有`<id>`应该替换为上文启动进程后返回的 ID | ||||
|  | ||||
| - 查看最近 150 行日志 | ||||
|  | ||||
|   - `pm2 log <id> --lines 150` | ||||
|  | ||||
| - 实时监控所有进程日志 | ||||
|  | ||||
|   - `pm2 monit` | ||||
|  | ||||
| - 展示当前 PM2 管理的所有进程 | ||||
|  | ||||
|   - `pm2 ls` | ||||
|  | ||||
| - 停止某个进程 | ||||
|  | ||||
|   - `pm2 stop <id>` | ||||
|  | ||||
| - 删除某个进程 | ||||
|  | ||||
|   - `pm2 del <id>` | ||||
|  | ||||
| - 重启某个进程 | ||||
|  | ||||
|   - `pm2 restart <id>` | ||||
|  | ||||
| - 保存当前进程列表 | ||||
|  | ||||
|   - `pm2 save` | ||||
|  | ||||
| - 恢复保存的进程列表 | ||||
|  | ||||
|   - `pm2 resurrect` | ||||
|  | ||||
| - 设置开机自动启动进程列表 | ||||
|   - `pm2 startup` | ||||
|   - 需要执行过 `pm2 save`   | ||||
|     如果不是 root 用户执行, 则需要手动添加指令返回的环境变量 | ||||
							
								
								
									
										64
									
								
								website/docs/tutorial/event-data.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								website/docs/tutorial/event-data.mdx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| --- | ||||
| sidebar_position: 6 | ||||
| description: 通过依赖注入获取所需事件信息 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 80 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 获取事件信息 | ||||
|  | ||||
| import Messenger from "@site/src/components/Messenger"; | ||||
|  | ||||
| 在 NoneBot 事件处理流程中,获取事件信息并做出对应的操作是非常常见的场景。本章节中我们将介绍如何通过**依赖注入**获取事件信息。 | ||||
|  | ||||
| ## 认识依赖注入 | ||||
|  | ||||
| 在事件处理流程中,事件响应器具有自己独立的上下文,例如:当前响应的事件、收到事件的机器人或者其他处理流程中新增的信息等。这些数据可以根据我们的需求,通过依赖注入的方式,在执行事件处理流程中注入到事件处理函数中。 | ||||
|  | ||||
| 相对于传统的信息获取方法,通过依赖注入获取信息的最大特色在于**按需获取**。如果该事件处理函数不需要任何额外信息即可运行,那么可以不进行依赖注入。如果事件处理函数需要额外的数据,可以通过依赖注入的方式灵活的标注出需要的依赖,在函数运行时便会被按需注入。 | ||||
|  | ||||
| ## 使用依赖注入 | ||||
|  | ||||
| 使用依赖注入获取上下文信息的方法十分简单,我们仅需要在函数的参数中声明所需的依赖,并正确的将函数添加为事件处理依赖即可。在 NoneBot 中,我们可以直接使用 `nonebot.params` 模块中定义的参数类型来声明依赖。 | ||||
|  | ||||
| 例如,我们可以继续改进上一章节中的 `weather` 插件,使其可以获取到 `天气` 命令的地名参数,并根据地名返回天气信息。 | ||||
|  | ||||
| ```python {8,10} title=weather/__init__.py | ||||
| from nonebot import on_command | ||||
| from nonebot.adapters import Message | ||||
| from nonebot.params import CommandArg | ||||
|  | ||||
| weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) | ||||
|  | ||||
| @weather.handle() | ||||
| async def handle_function(args: Message = CommandArg()): | ||||
|     # 提取参数纯文本作为地名,并判断是否有效 | ||||
|     if location := args.extract_plain_text(): | ||||
|         await weather.finish(f"今天{location}的天气是...") | ||||
|     else: | ||||
|         await weather.finish("请输入地名") | ||||
| ``` | ||||
|  | ||||
| 如上方示例所示,我们使用了 `args` 作为注入参数名,注入的内容为 `CommandArg()`,也就是**消息命令后跟随的内容**。在这个示例中,我们获得的参数会被检查是否有效,对无效参数则会结束事件。 | ||||
|  | ||||
| :::tip 提示 | ||||
| 命令与参数之间可以不需要空格,`CommandArg()` 获取的信息为命令后跟随的内容并去除了头部空白符。例如:`/天气 上海` 消息的参数为 `上海`。 | ||||
| ::: | ||||
|  | ||||
| :::tip 提示 | ||||
| `:=` 是 Python 3.8 引入的新语法 [Assignment Expressions](https://docs.python.org/zh-cn/3/reference/expressions.html#assignment-expressions),也称为海象表达式,可以在表达式中直接赋值。 | ||||
| ::: | ||||
|  | ||||
| <Messenger | ||||
|   msgs={[ | ||||
|     { position: "right", msg: "/天气" }, | ||||
|     { position: "left", msg: "请输入地名" }, | ||||
|     { position: "right", msg: "/天气 上海" }, | ||||
|     { position: "left", msg: "今天上海的天气是..." }, | ||||
|   ]} | ||||
| /> | ||||
|  | ||||
| NoneBot 提供了多种依赖注入类型,可以获取不同的信息,具体内容可参考[依赖注入](../advanced/dependency.mdx)。 | ||||
							
								
								
									
										24
									
								
								website/docs/tutorial/fundamentals.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								website/docs/tutorial/fundamentals.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| --- | ||||
| sidebar_position: 1 | ||||
| description: NoneBot 机器人构成及基本使用 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 30 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 机器人的构成 | ||||
|  | ||||
| 了解机器人的基本构成有助于你更好地使用 NoneBot,本章节将介绍 NoneBot 中的基本组成部分,稍后的文档中将会使用到这些概念。 | ||||
|  | ||||
| 使用 NoneBot 框架搭建的机器人具有以下几个基本组成部分: | ||||
|  | ||||
| 1. NoneBot 机器人框架主体:负责连接各个组成部分,提供基本的机器人功能 | ||||
| 2. 驱动器 `Driver`:客户端/服务端的功能实现,负责接收和发送消息(通常为 HTTP 通信) | ||||
| 3. 适配器 `Adapter`:驱动器的上层,负责将**平台消息**与 NoneBot 事件/操作系统的消息格式相互转换 | ||||
| 4. 插件 `Plugin`:机器人的功能实现,通常为负责处理事件并进行一系列的操作 | ||||
|  | ||||
| 除 NoneBot 机器人框架主体外,其他部分均可按需选择、互相搭配,但由于平台的兼容性问题,部分插件可能仅在某些特定平台上可用(这由插件编写者决定)。 | ||||
|  | ||||
| 在接下来的章节中,我们将重点介绍机器人功能实现,即插件 `Plugin` 部分。 | ||||
							
								
								
									
										85
									
								
								website/docs/tutorial/handler.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								website/docs/tutorial/handler.mdx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| --- | ||||
| sidebar_position: 5 | ||||
| description: 处理接收到的特定事件 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 70 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 事件处理 | ||||
|  | ||||
| import Messenger from "@site/src/components/Messenger"; | ||||
|  | ||||
| 在我们收到事件,并被某个事件响应器正确响应后,便正式开启了对于这个事件的**处理流程**。 | ||||
|  | ||||
| ## 认识事件处理流程 | ||||
|  | ||||
| 就像我们在解决问题时需要遵循流程一样,处理一个事件也需要一套流程。在事件响应器对一个事件进行响应之后,会依次执行一系列的**事件处理依赖**(通常是函数)。简单来说,事件处理流程并不是一个函数、一个对象或一个方法,而是一整套由开发者设计的流程。 | ||||
|  | ||||
| 在这个流程中,我们**目前**只需要了解两个概念:函数形式的“事件处理依赖”(下称“事件处理函数”)和“事件响应器操作”。 | ||||
|  | ||||
| ## 事件处理函数 | ||||
|  | ||||
| 在事件响应器中,事件处理流程可以由一个或多个“事件处理函数”组成,这些事件处理函数将会按照顺序依次对事件进行处理,直到全部执行完成或被中断。我们可以采用事件响应器的“事件处理函数装饰器”来添加这些“事件处理函数”。 | ||||
|  | ||||
| 顾名思义,“事件处理函数装饰器”是一个[装饰器(decorator)](https://docs.python.org/zh-cn/3/glossary.html#term-decorator),那么它的使用方法也同[函数定义](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions)中所展示的包装用法相同。例如: | ||||
|  | ||||
| ```python {5-7} title=weather/__init__.py | ||||
| from nonebot.plugin import on_command | ||||
|  | ||||
| weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) | ||||
|  | ||||
| @weather.handle() | ||||
| async def handle_function(): | ||||
|     pass  # do something here | ||||
| ``` | ||||
|  | ||||
| 如上方示例所示,我们使用 `weather` 响应器的 `handle` 装饰器装饰了一个函数 `handle_function`。`handle_function` 函数会被添加到 `weather` 的事件处理流程中。在 `weather` 响应器被触发之后,将会依次调用 `weather` 响应器的事件处理函数,即 `handle_function` 来对事件进行处理。 | ||||
|  | ||||
| ## 事件响应器操作 | ||||
|  | ||||
| 在事件处理流程中,我们可以使用事件响应器操作来进行一些交互或改变事件处理流程,例如向机器人用户发送消息或提前结束事件处理流程等。 | ||||
|  | ||||
| 事件响应器操作与事件处理函数装饰器类似,通常作为事件响应器 `Matcher` 的[类方法](https://docs.python.org/zh-cn/3/library/functions.html#classmethod)存在,因此事件响应器操作的调用方法也是 `Matcher.func()` 的形式。不过不同的是,事件响应器操作并不是装饰器,因此并不需要@进行标注。 | ||||
|  | ||||
| ```python {7,8} title=weather/__init__.py | ||||
| from nonebot.plugin import on_command | ||||
|  | ||||
| weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) | ||||
|  | ||||
| @weather.handle() | ||||
| async def handle_function(): | ||||
|     # await weather.send("天气是...") | ||||
|     await weather.finish("天气是...") | ||||
| ``` | ||||
|  | ||||
| 如上方示例所示,我们使用 `weather` 响应器的 `finish` 操作方法向机器人用户回复了 `天气是...` 并结束了事件处理流程。效果如下: | ||||
|  | ||||
| <Messenger | ||||
|   msgs={[ | ||||
|     { position: "right", msg: "/天气" }, | ||||
|     { position: "left", msg: "天气是..." }, | ||||
|   ]} | ||||
| /> | ||||
|  | ||||
| 值得注意的是,在执行 `finish` 方法时,NoneBot 会在向机器人用户发送消息内容后抛出 `FinishedException` 异常来结束事件响应流程。也就是说,在 `finish` 被执行后,后续的程序是不会被执行的。如果你需要回复机器人用户消息但不想事件处理流程结束,可以使用注释的部分中展示的 `send` 方法。 | ||||
|  | ||||
| :::danger 警告 | ||||
| 由于 `finish` 是通过抛出 `FinishedException` 异常来结束事件的,因此异常可能会被未加限制的 `try-except` 捕获,影响事件处理流程正确处理,导致无法正常结束此事件。请务必在异常捕获中指定错误类型或排除所有 [MatcherException](../api/exception.md#MatcherException) 类型的异常(如下所示),或将 `finish` 移出捕获范围进行使用。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.exception import MatcherException | ||||
|  | ||||
| try: | ||||
|     await weather.finish("天气是...") | ||||
| except MatcherException: | ||||
|     raise | ||||
| except Exception as e: | ||||
|     pass # do something here | ||||
| ``` | ||||
|  | ||||
| ::: | ||||
|  | ||||
| 目前 NoneBot 提供了多种事件响应器操作,其中包括用于机器人用户交互与流程控制两大类,进阶使用方法可以查看[会话控制](../appendices/session-control.mdx)。 | ||||
							
								
								
									
										58
									
								
								website/docs/tutorial/matcher.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								website/docs/tutorial/matcher.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| --- | ||||
| sidebar_position: 4 | ||||
| description: 响应接收到的特定事件 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 60 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 事件响应器 | ||||
|  | ||||
| 事件响应器(Matcher)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。 | ||||
|  | ||||
| 在 NoneBot 中,事件响应器可以通过一系列特定的规则**筛选**出**具有某种特征的事件**,并按照**特定的流程**交由**预定义的事件处理依赖**进行处理。例如,在[快速上手](../quick-start.mdx)中,我们使用了内置插件 `echo` ,它定义的事件响应器能响应机器人用户发送的“/echo hello world”消息,提取“hello world”信息并作为回复消息发送。 | ||||
|  | ||||
| ## 事件响应器辅助函数 | ||||
|  | ||||
| NoneBot 中所有事件响应器均继承自 `Matcher` 基类,但直接使用 `Matcher.new()` 方法创建事件响应器过于繁琐且不能记录插件信息。因此,NoneBot 中提供了一系列“事件响应器辅助函数”(下称“辅助函数”)来辅助我们用**最简的方式**创建**带有不同规则预设**的事件响应器,提高代码可读性和书写效率。通常情况下,我们只需要使用辅助函数即可完成事件响应器的创建。 | ||||
|  | ||||
| 在 NoneBot 中,辅助函数以 `on()` 或 `on_<type/rule>()` 形式出现(例如 `on_command()`),调用后根据不同的参数返回一个 `Type[Matcher]` 类型的新事件响应器。 | ||||
|  | ||||
| 目前 NoneBot 提供了多种功能各异的辅助函数、具有共同命令名称前缀的命令组以及具有共同参数的响应器组,均可以从 `nonebot` 模块直接导入使用,具体内容参考[事件响应器进阶](../advanced/matcher.md)。 | ||||
|  | ||||
| ## 创建事件响应器 | ||||
|  | ||||
| 在上一节[创建插件](./create-plugin.md#创建插件)中,我们创建了一个 `weather` 插件,现在我们来实现他的功能。 | ||||
|  | ||||
| 我们直接使用 `on_command()` 辅助函数来创建一个事件响应器: | ||||
|  | ||||
| ```python {3} title=weather/__init__.py | ||||
| from nonebot import on_command | ||||
|  | ||||
| weather = on_command("天气") | ||||
| ``` | ||||
|  | ||||
| 这样,我们就获得一个名为 `weather` 的事件响应器了,这个事件响应器会对 `/天气` 开头的消息进行响应。 | ||||
|  | ||||
| :::tip 提示 | ||||
| 如果一条消息中包含“@机器人”或以“机器人的昵称”开始,例如 `@bot /天气` 时,协议适配器会将 `event.is_tome()` 判断为 `True` ,同时也会自动去除 `@bot`,即事件响应器收到的信息内容为 `/天气`,方便进行命令匹配。 | ||||
| ::: | ||||
|  | ||||
| ### 为事件响应器添加参数 | ||||
|  | ||||
| 在辅助函数中,我们可以添加一些参数来对事件响应器进行更加精细的调整,例如事件响应器的优先级、匹配规则等。例如: | ||||
|  | ||||
| ```python {4} title=weather/__init__.py | ||||
| from nonebot import on_command | ||||
| from nonebot.rule import to_me | ||||
|  | ||||
| weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True) | ||||
| ``` | ||||
|  | ||||
| 这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令,需要私聊或 `@bot` 时才会响应,优先级为 10 ,阻断事件传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。 | ||||
|  | ||||
| :::tip 提示 | ||||
| 需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶](../advanced/matcher.md)或编辑器的提示。 | ||||
| ::: | ||||
							
								
								
									
										284
									
								
								website/docs/tutorial/message.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								website/docs/tutorial/message.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,284 @@ | ||||
| --- | ||||
| sidebar_position: 7 | ||||
| description: 处理消息序列与消息段 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 90 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 处理消息 | ||||
|  | ||||
| 在不同平台中,一条消息可能会有承载有各种不同的表现形式,它可能是一段纯文本、一张图片、一段语音、一篇富文本文章,也有可能是多种类型的组合等等。 | ||||
|  | ||||
| 在 NoneBot 中,为确保消息的正常处理与跨平台兼容性,采用了扁平化的消息序列形式,即 `Message` 对象。消息序列是 NoneBot 中的消息载体,无论是接收还是发送的消息,都采用消息序列的形式进行处理。 | ||||
|  | ||||
| ## 认识消息类型 | ||||
|  | ||||
| ### 消息序列 `Message` | ||||
|  | ||||
| 在 NoneBot 中,消息序列 `Message` 的主要作用是用于表达“一串消息”。由于消息序列继承自 `List[MessageSegment]`,所以 `Message` 的本质是由若干消息段所组成的序列。因此,消息序列的使用方法与 `List` 有很多相似之处,例如切片、索引、拼接等。 | ||||
|  | ||||
| 在上一节的[使用依赖注入](./event-data.mdx#使用依赖注入)中,我们已经通过依赖注入 `CommandArg()` 获取了命令的参数,它的类型即是消息序列。我们使用了消息序列的 `extract_plain_text()` 方法来获取消息序列中的纯文本内容。 | ||||
|  | ||||
| ### 消息段 `MessageSegment` | ||||
|  | ||||
| 顾名思义,消息段 `MessageSegment` 是一段消息。由于消息序列的本质是由若干消息段所组成的序列,消息段可以被认为是构成消息序列的最小单位。简单来说,消息序列类似于一个自然段,而消息段则是组成自然段的一句话。同时,作为特殊消息载体的存在,绝大多数的平台都有着**独特的消息类型**,这些独特的内容均需要由对应的**协议适配器**所提供,以适应不同平台中的消息模式。**这也意味着,你需要导入对应的协议适配器中的消息序列和消息段后才能使用其特殊的工厂方法。** | ||||
|  | ||||
| :::warning 注意 | ||||
| 消息段的类型是由协议适配器提供的,因此你需要参考协议适配器的文档并导入对应的消息段后才能使用其特殊的消息类型。 | ||||
|  | ||||
| 在上一节的[使用依赖注入](./event-data.mdx#使用依赖注入)中,我们导入的为 `nonebot.adapters.Message` 抽象基类,因此我们无法使用平台特有的消息类型。仅能使用 `str` 作为纯文本消息回复。 | ||||
| ::: | ||||
|  | ||||
| ## 使用消息序列 | ||||
|  | ||||
| :::warning 注意 | ||||
| 在以下的示例中,为了更好的理解多种类型的消息组成方式,我们将使用 `Console` 协议适配器来演示消息序列的使用方法。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。 | ||||
| ::: | ||||
|  | ||||
| 通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage), 或者使用 `event.get_message()` 获取。 | ||||
|  | ||||
| 由于消息序列是 `List[MessageSegment]` 的子类, 所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如: | ||||
|  | ||||
| ```python | ||||
| >>> from nonebot.adapters.console import Message, MessageSegment | ||||
| >>> message = Message([ | ||||
|     MessageSegment(type="text", data={"text":"hello"}), | ||||
|     MessageSegment(type="markdown", data={"markup":"**world**"}), | ||||
| ]) | ||||
| >>> for segment in message: | ||||
| ...     print(segment.type, segment.data) | ||||
| ... | ||||
| text {'text': 'hello'} | ||||
| markdown {'markup': '**world**'} | ||||
| >>> len(message) | ||||
| 2 | ||||
| ``` | ||||
|  | ||||
| ### 构造消息序列 | ||||
|  | ||||
| 在使用事件响应器操作发送消息时,既可以使用 `str` 作为消息,也可以使用 `Message`、`MessageSegment` 或者 `MessageTemplate`。那么,我们就需要先构造一个消息序列。消息序列可以通过多种方式构造: | ||||
|  | ||||
| #### 直接构造 | ||||
|  | ||||
| `Message` 类可以直接实例化,支持 `str`、`MessageSegment`、`Iterable[MessageSegment]` 或适配器自定义类型的参数。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.adapters.console import Message, MessageSegment | ||||
|  | ||||
| # 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` 方法进行构造。 | ||||
|  | ||||
| ```python | ||||
| from pydantic import parse_obj_as | ||||
| from nonebot.adapters.console import Message, MessageSegment | ||||
|  | ||||
| # 由字典构造消息段 | ||||
| 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")]) | ||||
| ``` | ||||
|  | ||||
| ### 获取消息纯文本 | ||||
|  | ||||
| 由于消息中存在各种类型的消息段,因此 `str(message)` 通常**不能得到消息的纯文本**,而是一个消息序列的字符串表示。 | ||||
|  | ||||
| NoneBot 为消息段定义了一个方法 `is_text()` ,可以用于判断消息段是否为纯文本;也可以使用 `message.extract_plain_text()` 方法获取消息纯文本。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.adapters.console import Message, MessageSegment | ||||
|  | ||||
| # 判断消息段是否为纯文本 | ||||
| MessageSegment.text("text").is_text() == True | ||||
|  | ||||
| # 提取消息纯文本字符串 | ||||
| Message( | ||||
|     [MessageSegment.text("text"), MessageSegment.markdown("**markup**")] | ||||
| ).extract_plain_text() == "text" | ||||
| ``` | ||||
|  | ||||
| ### 遍历 | ||||
|  | ||||
| `Message` 继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。 | ||||
|  | ||||
| ```python | ||||
| for segment in message: | ||||
|     ... | ||||
| ``` | ||||
|  | ||||
| ### 索引与切片 | ||||
|  | ||||
| `Message` 对列表的索引与切片进行了增强,在原有列表 int 索引与切片的基础上,支持 `type` 过滤索引与切片。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.adapters.console import Message, MessageSegment | ||||
|  | ||||
| message = Message( | ||||
|     [ | ||||
|         MessageSegment.text("test"), | ||||
|         MessageSegment.markdown("test2"), | ||||
|         MessageSegment.markdown("test3"), | ||||
|         MessageSegment.text("test4"), | ||||
|     ] | ||||
| ) | ||||
| # 索引 | ||||
| message[0] == MessageSegment.text("test") | ||||
| # 切片 | ||||
| message[0:2] == Message( | ||||
|     [MessageSegment.text("test"), MessageSegment.markdown("test2")] | ||||
| ) | ||||
| # 类型过滤 | ||||
| message["markdown"] == Message( | ||||
|     [MessageSegment.markdown("test2"), MessageSegment.markdown("test3")] | ||||
| ) | ||||
| # 类型索引 | ||||
| message["markdown", 0] == MessageSegment.markdown("test2") | ||||
| # 类型切片 | ||||
| message["markdown", 0:2] == Message( | ||||
|     [MessageSegment.markdown("test2"), MessageSegment.markdown("test3")] | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| 同样的,`Message` 对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。 | ||||
|  | ||||
| ```python | ||||
| # 指定类型首个消息段索引 | ||||
| message.index("markdown") == 1 | ||||
| # 指定类型消息段数量 | ||||
| message.count("markdown") == 2 | ||||
| ``` | ||||
|  | ||||
| 此外,`Message` 添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。 | ||||
|  | ||||
| ```python | ||||
| # 获取指定类型指定个数的消息段 | ||||
| message.get("markdown", 1) == Message([MessageSegment.markdown("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)来达到你想要的效果,这里给出几个简单的例子。 | ||||
|  | ||||
| 默认情况下,消息模板采用 `str` 纯文本形式的格式化: | ||||
|  | ||||
| ```python title=基础格式化用法 | ||||
| >>> from nonebot.adapters import MessageTemplate | ||||
| >>> MessageTemplate("{} {}").format("hello", "world") | ||||
| 'hello world' | ||||
| ``` | ||||
|  | ||||
| 如果 `Message.template` 构建消息模板,那么消息模板将采用消息序列形式的格式化,此时的消息将会是平台特定的: | ||||
|  | ||||
| ```python title=平台格式化用法 | ||||
| >>> from nonebot.adapters.console import Message, MessageSegment | ||||
| >>> Message.template("{} {}").format("hello", "world") | ||||
| Message( | ||||
|     MessageSegment.text("hello"), | ||||
|     MessageSegment.text(" "), | ||||
|     MessageSegment.text("world") | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| 消息模板支持使用消息段进行格式化: | ||||
|  | ||||
| ```python title=对消息段进行安全的拼接 | ||||
| >>> from nonebot.adapters.console import Message, MessageSegment | ||||
| >>> Message.template("{}{}").format(MessageSegment.markdown("**markup**"), "world") | ||||
| Message( | ||||
|     MessageSegment(type='markdown', data={'markup': '**markup**'}), | ||||
|     MessageSegment(type='text', data={'text': 'world'}) | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| 消息模板同样支持使用消息序列作为模板: | ||||
|  | ||||
| ```python title=以消息对象作为模板 | ||||
| >>> from nonebot.adapters.console import Message, MessageSegment | ||||
| >>> Message.template( | ||||
| ...     MessageSegment.text("{user_id}") + MessageSegment.emoji("tada") + | ||||
| ...     MessageSegment.text("{message}") | ||||
| ... ).format_map({"user_id": 123456, "message": "hello world"}) | ||||
| Message( | ||||
|     MessageSegment(type='text', data={'text': '123456'}), | ||||
|     MessageSegment(type='emoji', data={'emoji': 'tada'}), | ||||
|     MessageSegment(type='text', data={'text': 'hello world'}) | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| :::warning 注意 | ||||
| 只有消息序列中的文本类型消息段才能被格式化,其他类型的消息段将会原样添加。 | ||||
| ::: | ||||
|  | ||||
| 消息模板支持使用拓展控制符来控制消息段类型: | ||||
|  | ||||
| ```python title=使用消息段的拓展控制符 | ||||
| >>> from nonebot.adapters.console import Message, MessageSegment | ||||
| >>> Message.template("{name:emoji}").format(name='tada') | ||||
| Message(MessageSegment(type='emoji', data={'name': 'tada'})) | ||||
| ``` | ||||
| @@ -1,5 +0,0 @@ | ||||
| { | ||||
|   "position": 7, | ||||
|   "label": "插件", | ||||
|   "collapsible": false | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| --- | ||||
| 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) | ||||
| ``` | ||||
|  | ||||
| 至此,插件已经成功读取了自身所需的配置项,并且具有字段和类型提示,也可以对配置进行运行时修改。 | ||||
| @@ -1,529 +0,0 @@ | ||||
| --- | ||||
| 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", prompt="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: Message = 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 提示 | ||||
| 命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。 | ||||
| ::: | ||||
|  | ||||
| ### CommandStart | ||||
|  | ||||
| 获取命令型消息命令前缀。 | ||||
|  | ||||
| ```python {8} | ||||
| from nonebot import on_command | ||||
| from nonebot.adapters import Message | ||||
| from nonebot.params import CommandStart | ||||
|  | ||||
| matcher = on_command("cmd") | ||||
|  | ||||
| @matcher.handle() | ||||
| async def _(foo: str = CommandStart()): ... | ||||
| ``` | ||||
|  | ||||
| :::tip 提示 | ||||
| 命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。 | ||||
| ::: | ||||
|  | ||||
| ### ShellCommandArgs | ||||
|  | ||||
| 获取 shell 命令解析后的参数,支持 MessageSegment 富文本(如:图片)。 | ||||
|  | ||||
| :::tip 提示 | ||||
| 如果参数解析失败,则为 [`ParserExit`](../../api/exception.md#ParserExit) 异常,并携带错误码与错误信息。 | ||||
|  | ||||
| 由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。 | ||||
| ::: | ||||
|  | ||||
| ```python {8,12} | ||||
| from nonebot import on_shell_command | ||||
| from nonebot.params import ShellCommandArgs | ||||
| from nonebot.rule import Namespace, ArgumentParser | ||||
|  | ||||
| parser = ArgumentParser("demo") | ||||
| # parser.add_argument ... | ||||
| matcher = on_shell_command("cmd", parser) | ||||
|  | ||||
| # 解析失败 | ||||
| @matcher.handle() | ||||
| async def _(foo: ParserExit = ShellCommandArgs()): | ||||
|     if foo.status == 0: | ||||
|         foo.message  # help message | ||||
|     else: | ||||
|         foo.message  # error message | ||||
|  | ||||
| # 解析成功 | ||||
| @matcher.handle() | ||||
| async def _(foo: Namespace = ShellCommandArgs()): ... | ||||
| ``` | ||||
|  | ||||
| ### ShellCommandArgv | ||||
|  | ||||
| 获取 shell 命令解析前的参数列表,支持 MessageSegment 富文本(如:图片)。 | ||||
|  | ||||
| ```python {7} | ||||
| from nonebot import on_shell_command | ||||
| from nonebot.params import ShellCommandArgs | ||||
|  | ||||
| matcher = on_shell_command("cmd") | ||||
|  | ||||
| @matcher.handle() | ||||
| async def _(foo: List[Union[str, MessageSegment]] = ShellCommandArgv()): ... | ||||
| ``` | ||||
|  | ||||
| ### RegexStr | ||||
|  | ||||
| 获取正则匹配结果的文本。 | ||||
|  | ||||
| ```python {7} | ||||
| from nonebot import on_regex | ||||
| from nonebot.params import RegexStr | ||||
|  | ||||
| matcher = on_regex("regex") | ||||
|  | ||||
| @matcher.handle() | ||||
| async def _(foo: str = RegexStr()): ... | ||||
| ``` | ||||
|  | ||||
| ### 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()): ... | ||||
| ``` | ||||
|  | ||||
| ### Startswith | ||||
|  | ||||
| 获取触发响应器的消息前缀字符串。 | ||||
|  | ||||
| ```python {7} | ||||
| from nonebot import on_startswith | ||||
| from nonebot.params import Startswith | ||||
|  | ||||
| matcher = on_startswith("prefix") | ||||
|  | ||||
| @matcher.handle() | ||||
| async def _(foo: str = Startswith()): ... | ||||
| ``` | ||||
|  | ||||
| ### Endswith | ||||
|  | ||||
| 获取触发响应器的消息后缀字符串。 | ||||
|  | ||||
| ```python {7} | ||||
| from nonebot import on_endswith | ||||
| from nonebot.params import Endswith | ||||
|  | ||||
| matcher = on_endswith("suffix") | ||||
|  | ||||
| @matcher.handle() | ||||
| async def _(foo: str = Endswith()): ... | ||||
| ``` | ||||
|  | ||||
| ### Fullmatch | ||||
|  | ||||
| 获取触发响应器的消息字符串。 | ||||
|  | ||||
| ```python {7} | ||||
| from nonebot import on_fullmatch | ||||
| from nonebot.params import Fullmatch | ||||
|  | ||||
| matcher = on_fullmatch("fullmatch") | ||||
|  | ||||
| @matcher.handle() | ||||
| async def _(foo: str = Fullmatch()): ... | ||||
| ``` | ||||
|  | ||||
| ### Keyword | ||||
|  | ||||
| 获取触发响应器的关键字字符串。 | ||||
|  | ||||
| ```python {7} | ||||
| from nonebot import on_keyword | ||||
| from nonebot.params import Keyword | ||||
|  | ||||
| matcher = on_keyword({"keyword"}) | ||||
|  | ||||
| @matcher.handle() | ||||
| async def _(foo: str = Keyword()): ... | ||||
| ``` | ||||
|  | ||||
| ### 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"): ... | ||||
| ``` | ||||
| @@ -1,133 +0,0 @@ | ||||
| --- | ||||
| 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` 时,才会触发该响应器。 | ||||
|  | ||||
| 规则编写方法参考[进阶 - 自定义规则](../../advanced/rule.md)。 | ||||
|  | ||||
| :::warning 注意 | ||||
| 当会话状态更新时,`rule` 会被清空,以便会话收到新事件时能够正确匹配。 | ||||
| ::: | ||||
|  | ||||
| ### 事件触发权限 `permission` | ||||
|  | ||||
| 事件响应器的触发权限是一个 `Permission` 对象,它也是一系列 `checker` 的集合,当其中一个 `checker` 返回 `True` 时,就会触发该响应器。 | ||||
|  | ||||
| 权限编写方法参考[进阶 - 自定义权限](../../advanced/permission.md)。 | ||||
|  | ||||
| :::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_fullmatch`: 创建消息完全匹配事件响应器。 | ||||
| 9. `on_keyword`: 创建消息关键词匹配事件响应器。 | ||||
| 10. `on_command`: 创建命令消息事件响应器。 | ||||
| 11. `on_shell_command`: 创建 shell 命令消息事件响应器。 | ||||
| 12. `on_regex`: 创建正则表达式匹配事件响应器。 | ||||
| 13. `CommandGroup`: 创建具有共同命令名称前缀的命令组。 | ||||
| 14. `MatcherGroup`: 创建具有共同参数的响应器组。 | ||||
|  | ||||
| 其中,`on_metaevent` `on_message` `on_request` `on_notice` 函数都是在 `on` 的基础上添加了对应的事件类型 `type`;`on_startswith` `on_endswith` `on_fullmatch` `on_keyword` `on_command` `on_shell_command` `on_regex` 函数都是在 `on_message` 的基础上添加了对应的匹配规则 `rule`。 | ||||
| @@ -1,32 +0,0 @@ | ||||
| --- | ||||
| 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> | ||||
| @@ -1,71 +0,0 @@ | ||||
| --- | ||||
| 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 | ||||
| ``` | ||||
| @@ -1,138 +0,0 @@ | ||||
| --- | ||||
| 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") | ||||
| ``` | ||||
|  | ||||
| ## 确保插件加载和跨插件访问 | ||||
|  | ||||
| 倘若 `plugin_a`, `plugin_b` 均需被加载, 且 `plugin_b` 插件需要导入 `plugin_a` 才可运行, 可以在 `plugin_b` 利用 `require` 方法来确保插件加载, 同时可以直接 `import` 导入 `plugin_a` ,进行跨插件访问。 | ||||
|  | ||||
| ```python title=plugin_b.py | ||||
| from nonebot import require | ||||
|  | ||||
| require('plugin_a') | ||||
|  | ||||
| import plugin_a | ||||
| ``` | ||||
|  | ||||
| :::danger 警告 | ||||
| 不用 `require` 方法也可以进行跨插件访问,但需要保证插件已加载。例如,以下两种方式均可确保插件正确加载: | ||||
|  | ||||
| ```python title=bot.py | ||||
| import nonebot | ||||
|  | ||||
| # 顺序加载 | ||||
| nonebot.load_plugin("plugin_a") | ||||
| nonebot.load_plugin("plugin_b") | ||||
| ``` | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| # 同时加载 | ||||
| nonebot.load_all_plugins(["plugin_a", "plugin_b"], []) | ||||
| ``` | ||||
|  | ||||
| ::: | ||||
|  | ||||
| ## 嵌套插件 | ||||
|  | ||||
| <!-- TODO --> | ||||
| @@ -1,146 +0,0 @@ | ||||
| --- | ||||
| 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() | ||||
| ``` | ||||
| @@ -1,253 +0,0 @@ | ||||
| --- | ||||
| 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('{user_id}') + MessageSegment.face(233) + | ||||
| ...     MessageSegment.text('{message}') | ||||
| ... ).format_map({'user_id':123456, 'message':'hello world'} | ||||
| ... | ||||
| Message( | ||||
|     MessageSegment(type='text', data={'text': '123456'}), | ||||
|     MessageSegment(type='face', data={'face': 233}), | ||||
|     MessageSegment(type='text', data={'text': 'hello world'}) | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| ```python title="使用消息段的拓展控制符" | ||||
| >>> Message.template("{link:image}").format(link='https://...') | ||||
| Message(MessageSegment(type='image', data={'file': 'https://...'})) | ||||
| ``` | ||||
| @@ -1,97 +0,0 @@ | ||||
| --- | ||||
| sidebar_position: 6 | ||||
| description: 协议适配器的功能与使用 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 23 | ||||
|     category: guide | ||||
| --- | ||||
|  | ||||
| # 使用适配器 | ||||
|  | ||||
| :::tip 提示 | ||||
| 如何**安装**协议适配器请参考[安装协议适配器](../start/install-adapter.mdx)。 | ||||
| ::: | ||||
|  | ||||
| :::warning 提示 | ||||
| 各适配器的具体配置与说明请跳转至 [商店 - 适配器](/store) 中各适配器右上角的主页或文档进行查看。 | ||||
| ::: | ||||
|  | ||||
| ## 协议适配器的功能 | ||||
|  | ||||
| 由于 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()` 之前,否则会出现未知的错误。 | ||||
| ::: | ||||
							
								
								
									
										265
									
								
								website/docs/tutorial/store.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								website/docs/tutorial/store.mdx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,265 @@ | ||||
| --- | ||||
| sidebar_position: 2 | ||||
| description: 从商店安装适配器和插件 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     weight: 40 | ||||
|     category: tutorial | ||||
| --- | ||||
|  | ||||
| # 获取商店内容 | ||||
|  | ||||
| import Tabs from "@theme/Tabs"; | ||||
| import TabItem from "@theme/TabItem"; | ||||
| import Asciinema from "@site/src/components/Asciinema"; | ||||
|  | ||||
| :::tip 提示 | ||||
| 如果你暂时没有获取商店内容的需求,可以跳过本章节。 | ||||
| ::: | ||||
|  | ||||
| NoneBot 提供了一个[商店](/store),商店内容均由社区开发者贡献。你可以在商店中查找你需要的适配器和插件等,进行安装或者参考其文档等。 | ||||
|  | ||||
| 商店中每个内容的卡片都包含了其名称和简介等信息,点击**卡片右上角**链接图标即可跳转到其主页。 | ||||
|  | ||||
| ## 安装插件 | ||||
|  | ||||
| <Asciinema | ||||
|   url="https://asciinema.org/a/569650.cast" | ||||
|   options={{ theme: "monokai", poster: "npt:16.8" }} | ||||
| /> | ||||
|  | ||||
| 在商店插件页面中,点击你需要安装的插件下方的 `点击复制安装命令` 按钮,即可复制 `nb-cli` 命令。 | ||||
|  | ||||
| 请在你的**项目目录**下执行该命令。`nb-cli` 会自动安装插件并将其添加到加载列表中。 | ||||
|  | ||||
| <Tabs groupId="cli-install"> | ||||
|   <TabItem value="cli" label="使用命令" default> | ||||
|  | ||||
| ```bash | ||||
| nb plugin install <插件名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="interactive" label="交互式安装"> | ||||
|  | ||||
| ```bash | ||||
| $ nb plugin install | ||||
| [?] 想要安装的插件名称: <插件名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="pip" label="使用 pip"> | ||||
|  | ||||
| ```bash | ||||
| pip install <插件包名> | ||||
| ``` | ||||
|  | ||||
| 插件包名可以在商店插件卡片中找到,或者使用 `nb-cli` 搜索插件显示的详情中找到。安装完成后,需要参考[加载插件章节](./create-plugin.md#加载插件)自行加载。 | ||||
|  | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
|  | ||||
| 如果想要查看插件列表,可以使用以下命令 | ||||
|  | ||||
| ```bash | ||||
| # 列出商店所有插件 | ||||
| nb plugin list | ||||
| # 搜索商店插件 | ||||
| nb plugin search [可选关键词] | ||||
| ``` | ||||
|  | ||||
| 升级和卸载插件可以使用以下命令 | ||||
|  | ||||
| <Tabs groupId="cli-install"> | ||||
|   <TabItem value="cli" label="使用命令" default> | ||||
|  | ||||
| ```bash | ||||
| nb plugin update <插件名称> | ||||
| nb plugin uninstall <插件名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="interactive" label="交互式安装"> | ||||
|  | ||||
| ```bash | ||||
| $ nb plugin update | ||||
| [?] 想要安装的插件名称: <插件名称> | ||||
| $ nb plugin uninstall | ||||
| [?] 想要卸载的插件名称: <插件名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="pip" label="使用 pip"> | ||||
|  | ||||
| ```bash | ||||
| pip install --upgrade <插件包名> | ||||
| pip uninstall <插件包名> | ||||
| ``` | ||||
|  | ||||
| 插件包名可以在商店插件卡片中找到,或者使用 `nb-cli` 搜索插件显示的详情中找到。卸载完成后,需要自行移除插件加载。 | ||||
|  | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
|  | ||||
| ## 安装适配器 | ||||
|  | ||||
| <Asciinema | ||||
|   url="https://asciinema.org/a/569664.cast" | ||||
|   options={{ theme: "monokai", poster: "npt:12.0" }} | ||||
| /> | ||||
|  | ||||
| 安装适配器与安装插件类似,只是将命令换为 `nb adapter`,这里就不再赘述。 | ||||
|  | ||||
| 请在你的**项目目录**下执行该命令。`nb-cli` 会自动安装适配器并将其添加到注册列表中。 | ||||
|  | ||||
| <Tabs groupId="cli-install"> | ||||
|   <TabItem value="cli" label="使用命令" default> | ||||
|  | ||||
| ```bash | ||||
| nb adapter install <适配器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="interactive" label="交互式安装"> | ||||
|  | ||||
| ```bash | ||||
| $ nb adapter install | ||||
| [?] 想要安装的适配器名称: <适配器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="pip" label="使用 pip"> | ||||
|  | ||||
| ```bash | ||||
| pip install <适配器包名> | ||||
| ``` | ||||
|  | ||||
| 适配器包名可以在商店适配器卡片中找到,或者使用 `nb-cli` 搜索适配器显示的详情中找到。安装完成后,需要参考[注册适配器章节](../advanced/adapter.md#注册适配器)自行注册。 | ||||
|  | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
|  | ||||
| 如果想要查看适配器列表,可以使用以下命令 | ||||
|  | ||||
| ```bash | ||||
| # 列出商店所有适配器 | ||||
| nb adapter list | ||||
| # 搜索商店适配器 | ||||
| nb adapter search [可选关键词] | ||||
| ``` | ||||
|  | ||||
| 升级和卸载适配器可以使用以下命令 | ||||
|  | ||||
| <Tabs groupId="cli-install"> | ||||
|   <TabItem value="cli" label="使用命令" default> | ||||
|  | ||||
| ```bash | ||||
| nb adapter update <适配器名称> | ||||
| nb adapter uninstall <适配器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="interactive" label="交互式安装"> | ||||
|  | ||||
| ```bash | ||||
| $ nb adapter update | ||||
| [?] 想要安装的适配器名称: <适配器名称> | ||||
| $ nb adapter uninstall | ||||
| [?] 想要卸载的适配器名称: <适配器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="pip" label="使用 pip"> | ||||
|  | ||||
| ```bash | ||||
| pip install --upgrade <适配器包名> | ||||
| pip uninstall <适配器包名> | ||||
| ``` | ||||
|  | ||||
| 适配器包名可以在商店适配器卡片中找到,或者使用 `nb-cli` 搜索适配器显示的详情中找到。卸载完成后,需要自行移除适配器加载。 | ||||
|  | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
|  | ||||
| ## 安装驱动器 | ||||
|  | ||||
| <Asciinema | ||||
|   url="https://asciinema.org/a/569665.cast" | ||||
|   options={{ theme: "monokai", poster: "npt:14.0" }} | ||||
| /> | ||||
|  | ||||
| 安装驱动器与安装插件同样类似,只是将命令换为 `nb driver`,这里就不再赘述。 | ||||
|  | ||||
| 如果你使用了虚拟环境,请在你的**项目目录**下执行该命令,`nb-cli` 会自动安装驱动器到虚拟环境中。 | ||||
|  | ||||
| 请注意 `nb-cli` 并不会在安装驱动器后修改项目所使用的驱动器,请自行参考[配置方法](../appendices/config.mdx)章节以及 [`DRIVER` 配置项](../appendices/config.mdx#driver)修改驱动器。 | ||||
|  | ||||
| <Tabs groupId="cli-install"> | ||||
|   <TabItem value="cli" label="使用命令" default> | ||||
|  | ||||
| ```bash | ||||
| nb driver install <驱动器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="interactive" label="交互式安装"> | ||||
|  | ||||
| ```bash | ||||
| $ nb driver install | ||||
| [?] 想要安装的驱动器名称: <驱动器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="pip" label="使用 pip"> | ||||
|  | ||||
| ```bash | ||||
| pip install <驱动器包名> | ||||
| ``` | ||||
|  | ||||
| 驱动器包名可以在商店驱动器卡片中找到,或者使用 `nb-cli` 搜索驱动器显示的详情中找到。 | ||||
|  | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
|  | ||||
| 如果想要查看驱动器列表,可以使用以下命令 | ||||
|  | ||||
| ```bash | ||||
| # 列出商店所有驱动器 | ||||
| nb driver list | ||||
| # 搜索商店驱动器 | ||||
| nb driver search [可选关键词] | ||||
| ``` | ||||
|  | ||||
| 升级和卸载驱动器可以使用以下命令 | ||||
|  | ||||
| <Tabs groupId="cli-install"> | ||||
|   <TabItem value="cli" label="使用命令" default> | ||||
|  | ||||
| ```bash | ||||
| nb driver update <驱动器名称> | ||||
| nb driver uninstall <驱动器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="interactive" label="交互式安装"> | ||||
|  | ||||
| ```bash | ||||
| $ nb driver update | ||||
| [?] 想要安装的驱动器名称: <驱动器名称> | ||||
| $ nb driver uninstall | ||||
| [?] 想要卸载的驱动器名称: <驱动器名称> | ||||
| ``` | ||||
|  | ||||
|   </TabItem> | ||||
|   <TabItem value="pip" label="使用 pip"> | ||||
|  | ||||
| ```bash | ||||
| pip install --upgrade <驱动器包名> | ||||
| pip uninstall <驱动器包名> | ||||
| ``` | ||||
|  | ||||
| 驱动器包名可以在商店驱动器卡片中找到,或者使用 `nb-cli` 搜索驱动器显示的详情中找到。卸载完成后,需要自行移除适配器加载。 | ||||
|  | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
		Reference in New Issue
	
	Block a user