mirror of
				https://github.com/nonebot/nonebot2.git
				synced 2025-10-30 22:46:40 +00:00 
			
		
		
		
	🔖 Release 2.3.0
This commit is contained in:
		
							
								
								
									
										161
									
								
								website/versioned_docs/version-2.3.0/advanced/adapter.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								website/versioned_docs/version-2.3.0/advanced/adapter.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| --- | ||||
| sidebar_position: 1 | ||||
| description: 注册适配器与指定平台交互 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 20 | ||||
| --- | ||||
|  | ||||
| # 使用适配器 | ||||
|  | ||||
| 适配器 (Adapter) 是机器人与平台交互的核心桥梁,它负责在驱动器和机器人插件之间转换与传递消息。 | ||||
|  | ||||
| ## 适配器功能与组成 | ||||
|  | ||||
| 适配器通常有两种功能,分别是**接收事件**和**调用平台接口**。其中,接收事件是指将驱动器收到的事件消息转换为 NoneBot 定义的事件模型,然后交由机器人插件处理;调用平台接口是指将机器人插件调用平台接口的数据转换为平台指定的格式,然后交由驱动器发送,并接收接口返回数据。 | ||||
|  | ||||
| 为了实现这两种功能,适配器通常由四个部分组成: | ||||
|  | ||||
| - **Adapter**:负责转换事件和调用接口,正确创建 Bot 对象并注册到 NoneBot 中。 | ||||
| - **Bot**:负责存储平台机器人相关信息,并提供回复事件的方法。 | ||||
| - **Event**:负责定义事件内容,以及事件主体对象。 | ||||
| - **Message**:负责正确序列化消息,以便机器人插件处理。 | ||||
|  | ||||
| ## 注册适配器 | ||||
|  | ||||
| 在使用适配器之前,我们需要先将适配器注册到驱动器中,这样适配器就可以通过驱动器接收事件和调用接口了。我们以 Console 适配器为例,来看看如何注册适配器: | ||||
|  | ||||
| ```python {2,5} title=bot.py | ||||
| import nonebot | ||||
| from nonebot.adapters.console import Adapter | ||||
|  | ||||
| driver = nonebot.get_driver() | ||||
| driver.register_adapter(Adapter) | ||||
| ``` | ||||
|  | ||||
| 我们首先需要从适配器模块中导入所需要的适配器类,然后通过驱动器的 `register_adapter` 方法将适配器注册到驱动器中即可。如果我们需要多平台支持,可以多次调用 `register_adapter` 方法来注册多个适配器。 | ||||
|  | ||||
| ## 获取已注册的适配器 | ||||
|  | ||||
| NoneBot 提供了 `get_adapter` 方法来获取已注册的适配器,我们可以通过适配器的名称或类型来获取指定的适配器实例: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
| from nonebot.adapters.console import Adapter | ||||
|  | ||||
| adapters = nonebot.get_adapters() | ||||
| console_adapter = nonebot.get_adapter(Adapter) | ||||
| console_adapter = nonebot.get_adapter(Adapter.get_name()) | ||||
| ``` | ||||
|  | ||||
| ## 获取 Bot 对象 | ||||
|  | ||||
| 当前所有适配器已连接的 Bot 对象可以通过 `get_bots` 方法获取,这是一个以机器人 ID 为键的字典: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| bots = nonebot.get_bots() | ||||
| ``` | ||||
|  | ||||
| 我们也可以通过 `get_bot` 方法获取指定 ID 的 Bot 对象。如果省略 ID 参数,将会返回所有 Bot 中的第一个: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| bot = nonebot.get_bot("bot_id") | ||||
| ``` | ||||
|  | ||||
| 如果需要获取指定适配器连接的 Bot 对象,我们可以通过适配器的 `bots` 属性获取,这也是一个以机器人 ID 为键的字典: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
| from nonebot.adapters.console import Adapter | ||||
|  | ||||
| console_adapter = nonebot.get_adapter(Adapter) | ||||
| bots = console_adapter.bots | ||||
| ``` | ||||
|  | ||||
| Bot 对象都具有一个 `self_id` 属性,它是机器人的唯一 ID,由适配器填写,通常为机器人的帐号 ID 或者 APP ID。 | ||||
|  | ||||
| ## 获取事件通用信息 | ||||
|  | ||||
| 适配器的所有事件模型均继承自 `Event` 基类,在[事件类型与重载](../appendices/overload.md)一节中,我们也提到了如何使用基类抽象方法来获取事件通用信息。基类能提供如下信息: | ||||
|  | ||||
| ### 事件类型 | ||||
|  | ||||
| 事件类型通常为 `meta_event`、`message`、`notice`、`request`。 | ||||
|  | ||||
| ```python | ||||
| type: str = event.get_type() | ||||
| ``` | ||||
|  | ||||
| ### 事件名称 | ||||
|  | ||||
| 事件名称由适配器定义,通常用于日志记录。 | ||||
|  | ||||
| ```python | ||||
| name: str = event.get_event_name() | ||||
| ``` | ||||
|  | ||||
| ### 事件描述 | ||||
|  | ||||
| 事件描述由适配器定义,通常用于日志记录。 | ||||
|  | ||||
| ```python | ||||
| description: str = event.get_event_description() | ||||
| ``` | ||||
|  | ||||
| ### 事件日志字符串 | ||||
|  | ||||
| 事件日志字符串由事件名称和事件描述组成,用于日志记录。 | ||||
|  | ||||
| ```python | ||||
| log: str = event.get_log_string() | ||||
| ``` | ||||
|  | ||||
| ### 事件主体 ID | ||||
|  | ||||
| 事件主体 ID 通常为机器人用户 ID。 | ||||
|  | ||||
| ```python | ||||
| user_id: str = event.get_user_id() | ||||
| ``` | ||||
|  | ||||
| ### 事件会话 ID | ||||
|  | ||||
| 事件会话 ID 通常为机器人用户 ID 与群聊/频道 ID 组合而成。 | ||||
|  | ||||
| ```python | ||||
| session_id: str = event.get_session_id() | ||||
| ``` | ||||
|  | ||||
| ### 事件消息 | ||||
|  | ||||
| 如果事件包含消息,则可以通过该方法获取,否则会产生异常。 | ||||
|  | ||||
| ```python | ||||
| message: Message = event.get_message() | ||||
| ``` | ||||
|  | ||||
| ### 事件纯文本消息 | ||||
|  | ||||
| 通常为事件消息的纯文本内容,如果事件不包含消息,则会产生异常。 | ||||
|  | ||||
| ```python | ||||
| text: str = event.get_plaintext() | ||||
| ``` | ||||
|  | ||||
| ### 事件是否与机器人有关 | ||||
|  | ||||
| 由适配器实现的判断,通常将事件目标主体为机器人、消息中包含“@机器人”或以“机器人的昵称”开始视为与机器人有关。 | ||||
|  | ||||
| ```python | ||||
| is_tome: bool = event.is_tome() | ||||
| ``` | ||||
|  | ||||
| ## 更多 | ||||
|  | ||||
| 官方支持的适配器和社区贡献的适配器均可在[商店](/store/adapters)中查看。如果你想要开发自己的适配器,可以参考[开发文档](../developer/adapter-writing.md)。欢迎通过商店发布你的适配器。 | ||||
							
								
								
									
										1320
									
								
								website/versioned_docs/version-2.3.0/advanced/dependency.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1320
									
								
								website/versioned_docs/version-2.3.0/advanced/dependency.mdx
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										287
									
								
								website/versioned_docs/version-2.3.0/advanced/driver.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								website/versioned_docs/version-2.3.0/advanced/driver.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | ||||
| --- | ||||
| sidebar_position: 0 | ||||
| description: 选择合适的驱动器运行机器人 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 10 | ||||
| --- | ||||
|  | ||||
| # 选择驱动器 | ||||
|  | ||||
| 驱动器 (Driver) 是机器人运行的基石,它是机器人初始化的第一步,主要负责数据收发。 | ||||
|  | ||||
| :::important 提示 | ||||
| 驱动器的选择通常与机器人所使用的协议适配器相关,如果不知道该选择哪个驱动器,可以先阅读相关协议适配器文档说明。 | ||||
| ::: | ||||
|  | ||||
| :::tip 提示 | ||||
| 如何**安装**驱动器请参考[安装驱动器](../tutorial/store.mdx#安装驱动器)。 | ||||
| ::: | ||||
|  | ||||
| ## 驱动器类型 | ||||
|  | ||||
| 驱动器类型大体上可以分为两种: | ||||
|  | ||||
| - `Forward`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。 | ||||
| - `Reverse`:即服务端型驱动器,多用于使用 WebHook,接收 WebSocket 客户端连接等情形。 | ||||
|  | ||||
| 客户端型驱动器可以分为以下两种: | ||||
|  | ||||
| 1. 异步发送 HTTP 请求,自定义 `HTTP Method`、`URL`、`Header`、`Body`、`Cookie`、`Proxy`、`Timeout` 等。 | ||||
| 2. 异步建立 WebSocket 连接上下文,自定义 `WebSocket URL`、`Header`、`Cookie`、`Proxy`、`Timeout` 等。 | ||||
|  | ||||
| 服务端型驱动器目前有: | ||||
|  | ||||
| 1. ASGI 应用框架,具有以下功能: | ||||
|    - 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。 | ||||
|    - 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。 | ||||
|    - 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。 | ||||
|  | ||||
| ## 配置驱动器 | ||||
|  | ||||
| 驱动器的配置方法已经在[配置](../appendices/config.mdx)章节中简单进行了介绍,这里将详细介绍驱动器配置的格式。 | ||||
|  | ||||
| NoneBot 中的客户端和服务端型驱动器可以相互配合使用,但服务端型驱动器**仅能选择一个**。所有驱动器模块都会包含一个 `Driver` 子类,即驱动器类,他可以作为驱动器单独运行。同时,客户端驱动器模块中还会提供一个 `Mixin` 子类,用于在与其他驱动器配合使用时加载。因此,驱动器配置格式采用特殊语法:`<module>[:<Driver>][+<module>[:<Mixin>]]*`。 | ||||
|  | ||||
| 其中,`<module>` 代表**驱动器模块路径**;`<Driver>` 代表**驱动器类名**,默认为 `Driver`;`<Mixin>` 代表**驱动器混入类名**,默认为 `Mixin`。即,我们需要选择一个主要驱动器,然后在其基础上配合使用其他驱动器的功能。主要驱动器可以为客户端或服务端类型,但混入类驱动器只能为客户端类型。 | ||||
|  | ||||
| 特别的,为了简化内置驱动器模块路径,我们可以使用 `~` 符号作为内置驱动器模块路径的前缀,如 `~fastapi` 代表使用内置驱动器 `fastapi`。NoneBot 内置了多个驱动器适配,但需要安装额外依赖才能使用,具体请参考[安装驱动器](../tutorial/store.mdx#安装驱动器)。常见的驱动器配置如下: | ||||
|  | ||||
| ```dotenv | ||||
| DRIVER=~fastapi | ||||
| DRIVER=~aiohttp | ||||
| DRIVER=~httpx+~websockets | ||||
| DRIVER=~fastapi+~httpx+~websockets | ||||
| ``` | ||||
|  | ||||
| ## 获取驱动器 | ||||
|  | ||||
| 在 NoneBot 框架初始化完成后,我们就可以通过 `get_driver()` 方法获取全局驱动器实例: | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
|  | ||||
| driver = get_driver() | ||||
| ``` | ||||
|  | ||||
| ## 内置驱动器 | ||||
|  | ||||
| ### None | ||||
|  | ||||
| **类型:**服务端驱动器 | ||||
|  | ||||
| NoneBot 内置的空驱动器,不提供任何收发数据功能,可以在不需要外部网络连接时使用。 | ||||
|  | ||||
| ```env | ||||
| DRIVER=~none | ||||
| ``` | ||||
|  | ||||
| ### FastAPI(默认) | ||||
|  | ||||
| **类型:**ASGI 服务端驱动器 | ||||
|  | ||||
| > FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. | ||||
|  | ||||
| [FastAPI](https://fastapi.tiangolo.com/) 是一个易上手、高性能的异步 Web 框架,具有极佳的编写体验。 FastAPI 可以通过类型注解、依赖注入等方式实现输入参数校验、自动生成 API 文档等功能,也可以挂载其他 ASGI、WSGI 应用。 | ||||
|  | ||||
| ```env | ||||
| DRIVER=~fastapi | ||||
| ``` | ||||
|  | ||||
| #### FastAPI 配置项 | ||||
|  | ||||
| ##### `fastapi_openapi_url` | ||||
|  | ||||
| 类型:`str | None`   | ||||
| 默认值:`None`   | ||||
| 说明:`FastAPI` 提供的 `OpenAPI` JSON 定义地址,如果为 `None`,则不提供 `OpenAPI` JSON 定义。 | ||||
|  | ||||
| ##### `fastapi_docs_url` | ||||
|  | ||||
| 类型:`str | None`   | ||||
| 默认值:`None`   | ||||
| 说明:`FastAPI` 提供的 `Swagger` 文档地址,如果为 `None`,则不提供 `Swagger` 文档。 | ||||
|  | ||||
| ##### `fastapi_redoc_url` | ||||
|  | ||||
| 类型:`str | None`   | ||||
| 默认值:`None`   | ||||
| 说明:`FastAPI` 提供的 `ReDoc` 文档地址,如果为 `None`,则不提供 `ReDoc` 文档。 | ||||
|  | ||||
| ##### `fastapi_include_adapter_schema` | ||||
|  | ||||
| 类型:`bool`   | ||||
| 默认值:`True`   | ||||
| 说明:`FastAPI` 提供的 `OpenAPI` JSON 定义中是否包含适配器路由的 `Schema`。 | ||||
|  | ||||
| ##### `fastapi_reload` | ||||
|  | ||||
| :::caution 警告 | ||||
| 不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。 | ||||
|  | ||||
| ```bash | ||||
| nb run --reload | ||||
| ``` | ||||
|  | ||||
| 开启该功能后,在 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/library#incompatible-with-selectoreventloop-of-asyncio-on-windows) | ||||
|  | ||||
| 如果在开启该功能后,原本**正常运行**的代码报错,且打印的异常堆栈信息和 asyncio 有关(异常一般为 `NotImplementedError`), | ||||
| 你可能就需要考虑相关库对事件循环的支持,以及是否启用该功能。 | ||||
| ::: | ||||
|  | ||||
| 类型:`bool`   | ||||
| 默认值:`False`   | ||||
| 说明:是否开启 `uvicorn` 的 `reload` 功能,需要在机器人入口文件提供 ASGI 应用路径。 | ||||
|  | ||||
| ```python title=bot.py | ||||
| app = nonebot.get_asgi() | ||||
| nonebot.run(app="bot:app") | ||||
| ``` | ||||
|  | ||||
| ##### `fastapi_reload_dirs` | ||||
|  | ||||
| 类型:`List[str] | None`   | ||||
| 默认值:`None`   | ||||
| 说明:重载监控文件夹列表,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `fastapi_reload_delay` | ||||
|  | ||||
| 类型:`float | None`   | ||||
| 默认值:`None`   | ||||
| 说明:重载延迟,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `fastapi_reload_includes` | ||||
|  | ||||
| 类型:`List[str] | None`   | ||||
| 默认值:`None`   | ||||
| 说明:要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `fastapi_reload_excludes` | ||||
|  | ||||
| 类型:`List[str] | None`   | ||||
| 默认值:`None`   | ||||
| 说明:不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `fastapi_extra` | ||||
|  | ||||
| 类型:`Dist[str, Any]`   | ||||
| 默认值:`{}`   | ||||
| 说明:传递给 `FastAPI` 的其他参数 | ||||
|  | ||||
| ### Quart | ||||
|  | ||||
| **类型:**ASGI 服务端驱动器 | ||||
|  | ||||
| > Quart is an asyncio reimplementation of the popular Flask microframework API. | ||||
|  | ||||
| [Quart](https://quart.palletsprojects.com/) 是一个类 Flask 的异步版本,拥有与 Flask 非常相似的接口和使用方法。 | ||||
|  | ||||
| ```env | ||||
| DRIVER=~quart | ||||
| ``` | ||||
|  | ||||
| #### Quart 配置项 | ||||
|  | ||||
| ##### `quart_reload` | ||||
|  | ||||
| :::caution 警告 | ||||
| 不推荐开启该配置项,在 Windows 平台上开启该功能有可能会造成预料之外的影响!替代方案:使用 `nb-cli` 命令行工具以及参数 `--reload` 启动 NoneBot。 | ||||
|  | ||||
| ```bash | ||||
| nb run --reload | ||||
| ``` | ||||
|  | ||||
| ::: | ||||
|  | ||||
| 类型:`bool`   | ||||
| 默认值:`False`   | ||||
| 说明:是否开启 `uvicorn` 的 `reload` 功能,需要在机器人入口文件提供 ASGI 应用路径。 | ||||
|  | ||||
| ```python title=bot.py | ||||
| app = nonebot.get_asgi() | ||||
| nonebot.run(app="bot:app") | ||||
| ``` | ||||
|  | ||||
| ##### `quart_reload_dirs` | ||||
|  | ||||
| 类型:`List[str] | None`   | ||||
| 默认值:`None`   | ||||
| 说明:重载监控文件夹列表,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `quart_reload_delay` | ||||
|  | ||||
| 类型:`float | None`   | ||||
| 默认值:`None`   | ||||
| 说明:重载延迟,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `quart_reload_includes` | ||||
|  | ||||
| 类型:`List[str] | None`   | ||||
| 默认值:`None`   | ||||
| 说明:要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `quart_reload_excludes` | ||||
|  | ||||
| 类型:`List[str] | None`   | ||||
| 默认值:`None`   | ||||
| 说明:不要监听的文件列表,支持 glob pattern,默认为 uvicorn 默认值 | ||||
|  | ||||
| ##### `quart_extra` | ||||
|  | ||||
| 类型:`Dist[str, Any]`   | ||||
| 默认值:`{}`   | ||||
| 说明:传递给 `Quart` 的其他参数 | ||||
|  | ||||
| ### HTTPX | ||||
|  | ||||
| **类型:**HTTP 客户端驱动器 | ||||
|  | ||||
| :::caution 注意 | ||||
| 本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。 | ||||
| ::: | ||||
|  | ||||
| > [HTTPX](https://www.python-httpx.org/) 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. | ||||
|  | ||||
| ```env | ||||
| DRIVER=~httpx | ||||
| ``` | ||||
|  | ||||
| ### websockets | ||||
|  | ||||
| **类型:**WebSocket 客户端驱动器 | ||||
|  | ||||
| :::caution 注意 | ||||
| 本驱动器仅支持 WebSocket 连接请求,不支持 HTTP 请求。 | ||||
| ::: | ||||
|  | ||||
| > [websockets](https://websockets.readthedocs.io/) is a library for building WebSocket servers and clients in Python with a focus on correctness, simplicity, robustness, and performance. | ||||
|  | ||||
| ```env | ||||
| DRIVER=~websockets | ||||
| ``` | ||||
|  | ||||
| ### AIOHTTP | ||||
|  | ||||
| **类型:**HTTP/WebSocket 客户端驱动器 | ||||
|  | ||||
| > [AIOHTTP](https://docs.aiohttp.org/): Asynchronous HTTP Client/Server for asyncio and Python. | ||||
|  | ||||
| ```env | ||||
| DRIVER=~aiohttp | ||||
| ``` | ||||
| @@ -0,0 +1,40 @@ | ||||
| --- | ||||
| sidebar_position: 10 | ||||
| description: 自定义事件响应器存储 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 110 | ||||
| --- | ||||
|  | ||||
| # 事件响应器存储 | ||||
|  | ||||
| 事件响应器是 NoneBot 处理事件的核心,它们默认存储在一个字典中。在进入会话状态后,事件响应器将会转为临时响应器,作为最高优先级同样存储于该字典中。因此,事件响应器的存储类似于会话存储,它决定了整个 NoneBot 对事件的处理行为。 | ||||
|  | ||||
| NoneBot 默认使用 Python 的字典将事件响应器存储于内存中,但是我们也可以自定义事件响应器存储,将事件响应器存储于其他地方,例如 Redis 等。这样我们就可以实现持久化、在多实例间共享会话状态等功能。 | ||||
|  | ||||
| ## 编写存储提供者 | ||||
|  | ||||
| 事件响应器的存储提供者 `MatcherProvider` 抽象类继承自 `MutableMapping[int, list[type[Matcher]]]`,即以优先级为键,以事件响应器列表为值的映射。我们可以方便地进行逐优先级事件传播。 | ||||
|  | ||||
| 编写一个自定义的存储提供者,只需要继承并实现 `MatcherProvider` 抽象类: | ||||
|  | ||||
| ```python | ||||
| from nonebot.matcher import MatcherProvider | ||||
|  | ||||
| class CustomProvider(MatcherProvider): | ||||
|     ... | ||||
| ``` | ||||
|  | ||||
| ## 设置存储提供者 | ||||
|  | ||||
| 我们可以通过 `matchers.set_provider` 方法设置存储提供者: | ||||
|  | ||||
| ```python {3} | ||||
| from nonebot.matcher import matchers | ||||
|  | ||||
| matchers.set_provider(CustomProvider) | ||||
|  | ||||
| assert isinstance(matchers.provider, CustomProvider) | ||||
| ``` | ||||
							
								
								
									
										338
									
								
								website/versioned_docs/version-2.3.0/advanced/matcher.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								website/versioned_docs/version-2.3.0/advanced/matcher.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,338 @@ | ||||
| --- | ||||
| sidebar_position: 5 | ||||
| description: 事件响应器组成与内置响应规则 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 60 | ||||
| --- | ||||
|  | ||||
| # 事件响应器进阶 | ||||
|  | ||||
| 在[指南](../tutorial/matcher.md)与[深入](../appendices/rule.md)中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,内置的响应规则,与第三方响应规则拓展。 | ||||
|  | ||||
| :::tip 提示 | ||||
| 事件响应器允许继承,你可以通过直接继承 `Matcher` 类来创建一个新的事件响应器。 | ||||
| ::: | ||||
|  | ||||
| ## 事件响应器组成 | ||||
|  | ||||
| ### 事件响应器类型 | ||||
|  | ||||
| 事件响应器类型 `type` 即是该响应器所要响应的事件类型,只有在接收到的事件类型与该响应器的类型相同时,才会触发该响应器。如果类型为空字符串 `""`,则响应器将会响应所有类型的事件。事件响应器类型的检查在所有其他检查(权限控制、响应规则)之前进行。 | ||||
|  | ||||
| NoneBot 内置了四种常用事件类型:`meta_event`、`message`、`notice`、`request`,分别对应元事件、消息、通知、请求。通常情况下,协议适配器会将事件合理地分类至这四种类型中。如果有其他类型的事件需要响应,可以自行定义新的类型。 | ||||
|  | ||||
| ### 事件触发权限 | ||||
|  | ||||
| 事件触发权限 `permission` 是一个 `Permission` 对象,这在[权限控制](../appendices/permission.mdx)一节中已经介绍过。事件触发权限会在事件响应器的类型检查通过后进行检查,如果权限检查通过,则执行响应规则检查。 | ||||
|  | ||||
| ### 事件响应规则 | ||||
|  | ||||
| 事件响应规则 `rule` 是一个 `Rule` 对象,这在[响应规则](../appendices/rule.md)一节中已经介绍过。事件响应器的响应规则会在事件响应器的权限检查通过后进行匹配,如果响应规则检查通过,则触发该响应器。 | ||||
|  | ||||
| ### 响应优先级 | ||||
|  | ||||
| 响应优先级 `priority` 是一个正整数,用于指定响应器的优先级。响应器的优先级越小,越先被触发。如果响应器的优先级相同,则按照响应器的注册顺序进行触发。 | ||||
|  | ||||
| ### 阻断 | ||||
|  | ||||
| 阻断 `block` 是一个布尔值,用于指定响应器是否阻断事件的传播。如果阻断为 `True`,则在该响应器被触发后,事件将不会再传播给其他下一优先级的响应器。 | ||||
|  | ||||
| NoneBot 内置的事件响应器中,所有非 `command` 规则的 `message` 类型的事件响应器都会阻断事件传递,其他则不会。 | ||||
|  | ||||
| 在部分情况中,可以使用 [`stop_propagation`](../appendices/session-control.mdx#stop_propagation) 方法动态阻止事件传播,该方法需要 handler 在参数中获取 matcher 实例后调用方法。 | ||||
|  | ||||
| ### 有效期 | ||||
|  | ||||
| 事件响应器的有效期分为 `temp` 和 `expire_time` 。`temp` 是一个布尔值,用于指定响应器是否为临时响应器。如果为 `True`,则该响应器在被触发后会被自动销毁。`expire_time` 是一个 `datetime` 对象,用于指定响应器的过期时间。如果 `expire_time` 不为 `None`,则在该时间点后,该响应器会被自动销毁。 | ||||
|  | ||||
| ### 默认状态 | ||||
|  | ||||
| 事件响应器的默认状态 `default_state` 是一个 `dict` 对象,用于指定响应器的默认状态。在响应器被触发时,响应器将会初始化默认状态然后开始执行事件处理流程。 | ||||
|  | ||||
| ## 基本辅助函数 | ||||
|  | ||||
| NoneBot 为四种类型的事件响应器提供了五个基本的辅助函数: | ||||
|  | ||||
| - `on`:创建任何类型的事件响应器。 | ||||
| - `on_metaevent`:创建元事件响应器。 | ||||
| - `on_message`:创建消息事件响应器。 | ||||
| - `on_request`:创建请求事件响应器。 | ||||
| - `on_notice`:创建通知事件响应器。 | ||||
|  | ||||
| 除了 `on` 函数具有一个 `type` 参数外,其余参数均相同: | ||||
|  | ||||
| - `rule`:响应规则,可以是 `Rule` 对象或者 `RuleChecker` 函数。 | ||||
| - `permission`:事件触发权限,可以是 `Permission` 对象或者 `PermissionChecker` 函数。 | ||||
| - `handlers`:事件处理函数列表。 | ||||
| - `temp`:是否为临时响应器。 | ||||
| - `expire_time`:响应器的过期时间。 | ||||
| - `priority`:响应器的优先级。 | ||||
| - `block`:是否阻断事件传播。 | ||||
| - `state`:响应器的默认状态。 | ||||
|  | ||||
| 在消息类型的事件响应器的基础上,NoneBot 还内置了一些常用的响应规则,并结合为辅助函数来方便我们快速创建指定功能的响应器。下面我们逐个介绍。 | ||||
|  | ||||
| ## 内置响应规则 | ||||
|  | ||||
| ### `startswith` | ||||
|  | ||||
| `startswith` 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。 | ||||
|  | ||||
| 例如,我们可以创建一个匹配消息开头为 `!` 或者 `/` 的规则: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import startswith | ||||
|  | ||||
| rule = startswith(("!", "/"), ignorecase=False) | ||||
| ``` | ||||
|  | ||||
| 也可以直接使用辅助函数新建一个响应器: | ||||
|  | ||||
| ```python | ||||
| from nonebot import on_startswith | ||||
|  | ||||
| matcher = on_startswith(("!", "/"), ignorecase=False) | ||||
| ``` | ||||
|  | ||||
| ### `endswith` | ||||
|  | ||||
| `endswith` 响应规则用于匹配消息纯文本部分的结尾是否与指定字符串(或一系列字符串)相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。 | ||||
|  | ||||
| 例如,我们可以创建一个匹配消息结尾为 `.` 或者 `。` 的规则: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import endswith | ||||
|  | ||||
| rule = endswith((".", "。"), ignorecase=False) | ||||
| ``` | ||||
|  | ||||
| 也可以直接使用辅助函数新建一个响应器: | ||||
|  | ||||
| ```python | ||||
| from nonebot import on_endswith | ||||
|  | ||||
| matcher = on_endswith((".", "。"), ignorecase=False) | ||||
| ``` | ||||
|  | ||||
| ### `fullmatch` | ||||
|  | ||||
| `fullmatch` 响应规则用于匹配消息纯文本部分是否与指定字符串(或一系列字符串)完全相同。可选参数 `ignorecase` 用于指定是否忽略大小写,默认为 `False`。 | ||||
|  | ||||
| 例如,我们可以创建一个匹配消息为 `ping` 或者 `pong` 的规则: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import fullmatch | ||||
|  | ||||
| rule = fullmatch(("ping", "pong"), ignorecase=False) | ||||
| ``` | ||||
|  | ||||
| 也可以直接使用辅助函数新建一个响应器: | ||||
|  | ||||
| ```python | ||||
| from nonebot import on_fullmatch | ||||
|  | ||||
| matcher = on_fullmatch(("ping", "pong"), ignorecase=False) | ||||
| ``` | ||||
|  | ||||
| ### `keyword` | ||||
|  | ||||
| `keyword` 响应规则用于匹配消息纯文本部分是否包含指定字符串(或一系列字符串)。 | ||||
|  | ||||
| 例如,我们可以创建一个匹配消息中包含 `hello` 或者 `hi` 的规则: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import keyword | ||||
|  | ||||
| rule = keyword("hello", "hi") | ||||
| ``` | ||||
|  | ||||
| 也可以直接使用辅助函数新建一个响应器: | ||||
|  | ||||
| ```python | ||||
| from nonebot import on_keyword | ||||
|  | ||||
| matcher = on_keyword("hello", "hi") | ||||
| ``` | ||||
|  | ||||
| ### `command` | ||||
|  | ||||
| `command` 是最常用的响应规则,它用于匹配消息是否为命令。它会根据配置中的 [Command Start 和 Command Separator](../appendices/config.mdx#command-start-和-command-separator) 来判断消息是否为命令。 | ||||
|  | ||||
| 例如,当我们配置了 `Command Start` 为 `/`,`Command Separator` 为 `.` 时: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import command | ||||
|  | ||||
| # 匹配 "/help" 或者 "/帮助" 开头的消息 | ||||
| rule = command("help", "帮助") | ||||
| # 匹配 "/help.cmd" 开头的消息 | ||||
| rule = command(("help", "cmd")) | ||||
| ``` | ||||
|  | ||||
| 也可以直接使用辅助函数新建一个响应器: | ||||
|  | ||||
| ```python | ||||
| from nonebot import on_command | ||||
|  | ||||
| matcher = on_command("help", aliases={"帮助"}) | ||||
| ``` | ||||
|  | ||||
| 此外,`command` 响应规则默认允许消息命令与参数间不加空格,如果需要严格匹配命令与参数间的空白符,可以使用 `command` 函数的 `force_whitespace` 参数。`force_whitespace` 参数可以是 bool 类型或者具体的字符串,默认为 `False`。如果为 `True`,则命令与参数间必须有任意个数的空白符;如果为字符串,则命令与参数间必须有且与给定字符串一致的空白符。 | ||||
|  | ||||
| ```python | ||||
| rule = command("help", force_whitespace=True) | ||||
| rule = command("help", force_whitespace=" ") | ||||
| ``` | ||||
|  | ||||
| 命令解析后的结果可以通过 [`Command`](./dependency.mdx#command)、[`RawCommand`](./dependency.mdx#rawcommand)、[`CommandArg`](./dependency.mdx#commandarg)、[`CommandStart`](./dependency.mdx#commandstart)、[`CommandWhitespace`](./dependency.mdx#commandwhitespace) 依赖注入获取。 | ||||
|  | ||||
| ### `shell_command` | ||||
|  | ||||
| `shell_command` 响应规则用于匹配类 shell 命令形式的消息。它首先与 [`command`](#command) 响应规则一样进行命令匹配,如果匹配成功,则会进行进一步的参数解析。参数解析采用 `argparse` 标准库进行,在此基础上添加了消息序列 `Message` 支持。 | ||||
|  | ||||
| 例如,我们可以创建一个匹配 `/cmd` 命令并且带有 `-v` 选项与默认 `-h` 帮助选项的规则: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import shell_command, ArgumentParser | ||||
|  | ||||
| parser = ArgumentParser() | ||||
| parser.add_argument("-v", "--verbose", action="store_true") | ||||
|  | ||||
| rule = shell_command("cmd", parser=parser) | ||||
| ``` | ||||
|  | ||||
| 更多关于 `argparse` 的使用方法请参考 [argparse 文档](https://docs.python.org/zh-cn/3/library/argparse.html)。我们也可以选择不提供 `parser` 参数,这样 `shell_command` 将不会解析参数,但会提供参数列表 `argv`。 | ||||
|  | ||||
| 直接使用辅助函数新建一个响应器: | ||||
|  | ||||
| ```python | ||||
| from nonebot import on_shell_command | ||||
| from nonebot.rule import ArgumentParser | ||||
|  | ||||
| parser = ArgumentParser() | ||||
| parser.add_argument("-v", "--verbose", action="store_true") | ||||
|  | ||||
| matcher = on_shell_command("cmd", parser=parser) | ||||
| ``` | ||||
|  | ||||
| 参数解析后的结果可以通过 [`ShellCommandArgv`](./dependency.mdx#shellcommandargv)、[`ShellCommandArgs`](./dependency.mdx#shellcommandargs) 依赖注入获取。 | ||||
|  | ||||
| ### `regex` | ||||
|  | ||||
| `regex` 响应规则用于匹配消息是否与指定正则表达式匹配。 | ||||
|  | ||||
| :::tip 提示 | ||||
| 正则表达式匹配使用 search 而非 match,如需从头匹配请使用 `r"^xxx"` 模式来确保匹配开头。 | ||||
| ::: | ||||
|  | ||||
| 例如,我们可以创建一个匹配消息中包含字母并且忽略大小写的规则: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import regex | ||||
|  | ||||
| rule = regex(r"[a-z]+", flags=re.IGNORECASE) | ||||
| ``` | ||||
|  | ||||
| 也可以直接使用辅助函数新建一个响应器: | ||||
|  | ||||
| ```python | ||||
| from nonebot import on_regex | ||||
|  | ||||
| matcher = on_regex(r"[a-z]+", flags=re.IGNORECASE) | ||||
| ``` | ||||
|  | ||||
| 正则匹配后的结果可以通过 [`RegexStr`](./dependency.mdx#regexstr)、[`RegexGroup`](./dependency.mdx#regexgroup)、[`RegexDict`](./dependency.mdx#regexdict) 依赖注入获取。 | ||||
|  | ||||
| ### `to_me` | ||||
|  | ||||
| `to_me` 响应规则用于匹配事件是否与机器人相关。 | ||||
|  | ||||
| 例如: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import to_me | ||||
|  | ||||
| rule = to_me() | ||||
| ``` | ||||
|  | ||||
| ### `is_type` | ||||
|  | ||||
| `is_type` 响应规则用于匹配事件类型是否为指定类型(或者一系列类型)。 | ||||
|  | ||||
| 例如,我们可以创建一个匹配 OneBot v11 私聊和群聊消息事件的规则: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import is_type | ||||
| from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent | ||||
|  | ||||
| rule = is_type(PrivateMessageEvent, GroupMessageEvent) | ||||
| ``` | ||||
|  | ||||
| ## 响应器组 | ||||
|  | ||||
| 为了更方便的管理一系列功能相近的响应器,NoneBot 提供了两种响应器组,它们可以帮助我们进行响应器的统一管理。 | ||||
|  | ||||
| ### `CommandGroup` | ||||
|  | ||||
| `CommandGroup` 可以用于管理一系列具有相同前置命令的子命令响应器。 | ||||
|  | ||||
| 例如,我们创建 `/cmd`、`/cmd.sub`、`/cmd.help` 三个命令,他们具有相同的优先级: | ||||
|  | ||||
| ```python | ||||
| from nonebot import CommandGroup | ||||
|  | ||||
| group = CommandGroup("cmd", priority=10) | ||||
|  | ||||
| cmd = group.command(tuple()) | ||||
| sub_cmd = group.command("sub") | ||||
| help_cmd = group.command("help") | ||||
| ``` | ||||
|  | ||||
| 命令别名 aliases 默认不会添加 `CommandGroup` 设定的前缀,如果需要为 aliases 添加前缀,可以添加 `prefix_aliases=True` 参数: | ||||
|  | ||||
| ```python | ||||
| from nonebot import CommandGroup | ||||
|  | ||||
| group = CommandGroup("cmd", prefix_aliases=True) | ||||
|  | ||||
| cmd = group.command(tuple()) | ||||
| help_cmd = group.command("help", aliases={"帮助"}) | ||||
| ``` | ||||
|  | ||||
| 这样就能成功匹配 `/cmd`、`/cmd.help`、`/cmd.帮助` 命令。如果未设置,将默认匹配 `/cmd`、`/cmd.help`、`/帮助` 命令。 | ||||
|  | ||||
| ### `MatcherGroup` | ||||
|  | ||||
| `MatcherGroup` 可以用于管理一系列具有相同属性的响应器。 | ||||
|  | ||||
| 例如,我们创建一个具有相同响应规则的响应器组: | ||||
|  | ||||
| ```python | ||||
| from nonebot.rule import to_me | ||||
| from nonebot import MatcherGroup | ||||
|  | ||||
| group = MatcherGroup(rule=to_me()) | ||||
|  | ||||
| matcher1 = group.on_message() | ||||
| matcher2 = group.on_message() | ||||
| ``` | ||||
|  | ||||
| ## 第三方响应规则 | ||||
|  | ||||
| ### Alconna | ||||
|  | ||||
| [`nonebot-plugin-alconna`](https://github.com/nonebot/plugin-alconna) 是一类提供了拓展响应规则的插件。 | ||||
| 该插件使用 [Alconna](https://github.com/ArcletProject/Alconna) 作为命令解析器, | ||||
| 是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。 | ||||
|  | ||||
| 该插件提供了一类新的事件响应器辅助函数 `on_alconna`,以及 `AlconnaResult` 等依赖注入函数。 | ||||
|  | ||||
| 基于 `Alconna` 的特性,该插件同时提供了一系列便捷的消息段标注。 | ||||
| 标注可用于在 `Alconna` 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 `nonebot_plugin_alconna.adapters` 中。 | ||||
|  | ||||
| 该插件同时通过提供 `UniMessage` (通用消息模型) 实现了**跨平台接收和发送消息**的功能。 | ||||
|  | ||||
| 详情请阅读最佳实践中的 [命令解析拓展](../best-practice/alconna/README.mdx) 章节。 | ||||
							
								
								
									
										104
									
								
								website/versioned_docs/version-2.3.0/advanced/plugin-info.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								website/versioned_docs/version-2.3.0/advanced/plugin-info.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| --- | ||||
| sidebar_position: 2 | ||||
| description: 填写与获取插件相关的信息 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 30 | ||||
| --- | ||||
|  | ||||
| # 插件信息 | ||||
|  | ||||
| NoneBot 是一个插件化的框架,可以通过加载插件来扩展功能。同时,我们也可以通过 NoneBot 的插件系统来获取相关信息,例如插件的名称、使用方法,用于收集帮助信息等。下面我们将介绍如何为插件添加元数据,以及如何获取插件信息。 | ||||
|  | ||||
| ## 插件元数据 | ||||
|  | ||||
| 在 NoneBot 中,插件 [`Plugin`](../api/plugin/model.md#Plugin) 对象中存储了插件系统所需要的一系列信息。包括插件的索引名称、插件模块、插件中的事件响应器、插件父子关系等。通常,只有插件开发者才需要关心这些信息,而插件使用者或者机器人用户想要看到的是插件使用方法等帮助信息。因此,我们可以为插件添加插件元数据 `PluginMetadata`,它允许插件开发者为插件添加一些额外的信息。这些信息编写于插件模块的顶层,可以直接通过源码查看,或者通过 NoneBot 插件系统获取收集到的信息,通过其他方式发送给机器人用户等。 | ||||
|  | ||||
| 现在,假设我们有一个插件 `example`, 它的模块结构如下: | ||||
|  | ||||
| ```tree {4-6} title=Project | ||||
| 📦 awesome-bot | ||||
| ├── 📂 awesome_bot | ||||
| │   └── 📂 plugins | ||||
| |       └── 📂 example | ||||
| |           ├── 📜 __init__.py | ||||
| |           └── 📜 config.py | ||||
| ├── 📜 pyproject.toml | ||||
| └── 📜 README.md | ||||
| ``` | ||||
|  | ||||
| 我们需要在插件顶层模块 `example/__init__.py` 中添加插件元数据,如下所示: | ||||
|  | ||||
| ```python {1,5-12} title=example/__init__.py | ||||
| from nonebot.plugin import PluginMetadata | ||||
|  | ||||
| from .config import Config | ||||
|  | ||||
| __plugin_meta__ = PluginMetadata( | ||||
|     name="示例插件", | ||||
|     description="这是一个示例插件", | ||||
|     usage="没什么用", | ||||
|     type="application", | ||||
|     config=Config, | ||||
|     extra={}, | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| 我们可以看到,插件元数据 `PluginMetadata` 有三个基本属性:插件名称、插件描述、插件使用方法。除此之外,还有几个可选的属性(具体填写见[发布插件](../developer/plugin-publishing.mdx#填写插件元数据)章节): | ||||
|  | ||||
| - `type`:插件类别,发布插件必填。当前有效类别有:`library`(为其他插件编写提供功能),`application`(向机器人用户提供功能); | ||||
| - `homepage`:插件项目主页,发布插件必填; | ||||
| - `config`:插件的[配置类](../appendices/config.mdx#插件配置),如无配置类可不填; | ||||
| - `supported_adapters`:支持的适配器模块名集合,若插件可以保证兼容所有适配器(即仅使用基本适配器功能)可不填写; | ||||
| - `extra`:一个字典,可以用于存储任意信息。其他插件可以通过约定 `extra` 字典的键名来达成收集某些特殊信息的目的。 | ||||
|  | ||||
| 请注意,这里的**插件名称**是供使用者或机器人用户查看的,与插件索引名称无关。**插件索引名称(插件模块名称)**仅用于 NoneBot 插件系统**内部索引**。 | ||||
|  | ||||
| ## 获取插件信息 | ||||
|  | ||||
| NoneBot 提供了多种获取插件对象的方法,例如获取当前所有已导入的插件: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| plugins: set[Plugin] = nonebot.get_loaded_plugins() | ||||
| ``` | ||||
|  | ||||
| 也可以通过插件索引名称获取插件对象: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| plugin: Plugin | None = nonebot.get_plugin("example") | ||||
| ``` | ||||
|  | ||||
| 或者通过模块路径获取插件对象: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| plugin: Plugin | None = nonebot.get_plugin_by_module_name("awesome_bot.plugins.example") | ||||
| ``` | ||||
|  | ||||
| 如果需要获取所有当前声明的插件名称(可能还未加载),可以使用 `get_available_plugin_names` 函数: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| plugin_names: set[str] = nonebot.get_available_plugin_names() | ||||
| ``` | ||||
|  | ||||
| 插件对象 `Plugin` 中包含了多个属性: | ||||
|  | ||||
| - `name`:插件索引名称 | ||||
| - `module`:插件模块 | ||||
| - `module_name`:插件模块路径 | ||||
| - `manager`:插件管理器 | ||||
| - `matcher`:插件中定义的事件响应器 | ||||
| - `parent_plugin`:插件的父插件 | ||||
| - `sub_plugins`:插件的子插件集合 | ||||
| - `metadata`:插件元数据 | ||||
|  | ||||
| 通过这些属性以及插件元数据,我们就可以收集所需要的插件信息了。 | ||||
| @@ -0,0 +1,41 @@ | ||||
| --- | ||||
| sidebar_position: 3 | ||||
| description: 编写与加载嵌套插件 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 40 | ||||
| --- | ||||
|  | ||||
| # 嵌套插件 | ||||
|  | ||||
| NoneBot 支持嵌套插件,即一个插件可以包含其他插件。通过这种方式,我们可以将一个大型插件拆分成多个功能子插件,使得插件更加清晰、易于维护。我们可以直接在插件中使用 NoneBot 加载插件的方法来加载子插件。 | ||||
|  | ||||
| ## 创建嵌套插件 | ||||
|  | ||||
| 我们可以在使用 `nb-cli` 命令[创建插件](../tutorial/create-plugin.md#创建插件)时,选择直接通过模板创建一个嵌套插件: | ||||
|  | ||||
| ```bash | ||||
| $ nb plugin create | ||||
| [?] 插件名称: parent | ||||
| [?] 使用嵌套插件? (y/N) Y | ||||
| [?] 输出目录: awesome_bot/plugins | ||||
| ``` | ||||
|  | ||||
| 或者使用 `nb plugin create --sub-plugin` 选项直接创建一个嵌套插件。 | ||||
|  | ||||
| ## 已有插件 | ||||
|  | ||||
| 如果你已经有一个插件,想要在其中嵌套加载子插件,可以在插件的 `__init__.py` 中添加如下代码: | ||||
|  | ||||
| ```python title=parent/__init__.py | ||||
| import nonebot | ||||
| from pathlib import Path | ||||
|  | ||||
| sub_plugins = nonebot.load_plugins( | ||||
|     str(Path(__file__).parent.joinpath("plugins").resolve()) | ||||
| ) | ||||
| ``` | ||||
|  | ||||
| 这样,`parent` 插件就会加载 `parent/plugins` 目录下的所有插件。NoneBot 会正确识别这些插件的父子关系,你可以在 `parent` 的插件信息中看到这些子插件的信息,也可以在子插件信息中看到它们的父插件信息。 | ||||
							
								
								
									
										37
									
								
								website/versioned_docs/version-2.3.0/advanced/requiring.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								website/versioned_docs/version-2.3.0/advanced/requiring.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| --- | ||||
| sidebar_position: 4 | ||||
| description: 使用其他插件提供的功能 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 50 | ||||
| --- | ||||
|  | ||||
| # 跨插件访问 | ||||
|  | ||||
| NoneBot 插件化系统的设计使得插件之间可以功能独立、各司其职,我们可以更好地维护和扩展插件。但是,有时候我们可能需要在不同插件之间调用功能。NoneBot 生态中就有一类插件,它们专为其他插件提供功能支持,如:[定时任务插件](../best-practice/scheduler.md)、[数据存储插件](../best-practice/data-storing.md)等。这时候我们就需要在插件之间进行跨插件访问。 | ||||
|  | ||||
| ## 插件跟踪 | ||||
|  | ||||
| 由于 NoneBot 插件系统通过 [Import Hooks](https://docs.python.org/3/reference/import.html#import-hooks) 的方式实现插件加载与跟踪管理,因此我们**不能**在 NoneBot 跟踪插件前进行模块 import,这会导致插件加载失败。即,我们不能在使用 NoneBot 提供的加载插件方法前,直接使用 `import` 语句导入插件。 | ||||
|  | ||||
| 对于在项目目录下的插件,我们通常直接使用 `load_from_toml` 等方法一次性加载所有插件。由于这些插件已经被声明,即便插件导入顺序不同,NoneBot 也能正确跟踪插件。此时,我们不需要对跨插件访问进行特殊处理。但当我们使用了外部插件,如果没有事先声明或加载插件,NoneBot 并不会将其当作插件进行跟踪,可能会出现意料之外的错误出现。 | ||||
|  | ||||
| 简单来说,我们必须在 `import` 外部插件之前,确保依赖的外部插件已经被声明或加载。 | ||||
|  | ||||
| ## 插件依赖声明 | ||||
|  | ||||
| NoneBot 提供了一种方法来确保我们依赖的插件已经被正确加载,即使用 `require` 函数。通过 `require` 函数,我们可以在当前插件中声明依赖的插件,NoneBot 会在加载当前插件时,检查依赖的插件是否已经被加载,如果没有,会尝试优先加载依赖的插件。 | ||||
|  | ||||
| 假设我们有一个插件 `a` 依赖于插件 `b`,我们可以在插件 `a` 中使用 `require` 函数声明其依赖于插件 `b`: | ||||
|  | ||||
| ```python {3} title=a/__init__.py | ||||
| from nonebot import require | ||||
|  | ||||
| require("b") | ||||
|  | ||||
| from b import some_function | ||||
| ``` | ||||
|  | ||||
| 其中,`require` 函数的参数为插件索引名称或者外部插件的模块名称。在完成依赖声明后,我们可以在插件 `a` 中直接导入插件 `b` 所提供的功能。 | ||||
							
								
								
									
										135
									
								
								website/versioned_docs/version-2.3.0/advanced/routing.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								website/versioned_docs/version-2.3.0/advanced/routing.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| --- | ||||
| sidebar_position: 9 | ||||
| description: 添加服务端路由规则 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 100 | ||||
| --- | ||||
|  | ||||
| # 添加路由 | ||||
|  | ||||
| 在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。 | ||||
|  | ||||
| NoneBot 中,我们可以通过两种途径向 ASGI 驱动器添加路由规则: | ||||
|  | ||||
| 1. 通过 NoneBot 的兼容层建立路由规则。 | ||||
| 2. 直接向 ASGI 应用添加路由规则。 | ||||
|  | ||||
| 这两种途径各有优劣,前者可以在各种服务端型驱动器下运行,但并不能直接使用 ASGI 应用框架提供的特性与功能;后者直接使用 ASGI 应用,更自由、功能完整,但只能在特定类型驱动器下运行。 | ||||
|  | ||||
| 在向驱动器添加路由规则时,我们需要注意驱动器是否为服务端类型,我们可以通过以下方式判断: | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
| from nonebot.drivers import ASGIMixin | ||||
|  | ||||
| # highlight-next-line | ||||
| can_use = isinstance(get_driver(), ASGIMixin) | ||||
| ``` | ||||
|  | ||||
| ## 通过兼容层添加路由 | ||||
|  | ||||
| NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServerSetup`,分别用于定义 HTTP 服务端和 WebSocket 服务端的路由规则。 | ||||
|  | ||||
| ### HTTP 路由 | ||||
|  | ||||
| `HTTPServerSetup` 具有四个属性: | ||||
|  | ||||
| - `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。 | ||||
| - `method`:请求方法。类型为 `str`。 | ||||
| - `name`:路由名称,不可重复。类型为 `str`。 | ||||
| - `handle_func`:路由处理函数。类型为 `Callable[[Request], Awaitable[Response]]`。 | ||||
|  | ||||
| 例如,我们添加一个 `/hello` 的路由,当请求方法为 `GET` 时,返回 `200 OK` 以及返回体信息: | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
| from nonebot.drivers import URL, Request, Response, ASGIMixin, HTTPServerSetup | ||||
|  | ||||
| async def hello(request: Request) -> Response: | ||||
|     return Response(200, content="Hello, world!") | ||||
|  | ||||
| if isinstance((driver := get_driver()), ASGIMixin): | ||||
|     driver.setup_http_server( | ||||
|         HTTPServerSetup( | ||||
|             path=URL("/hello"), | ||||
|             method="GET", | ||||
|             name="hello", | ||||
|             handle_func=hello, | ||||
|         ) | ||||
|     ) | ||||
| ``` | ||||
|  | ||||
| 对于 `Request` 和 `Response` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。 | ||||
|  | ||||
| ### WebSocket 路由 | ||||
|  | ||||
| `WebSocketServerSetup` 具有三个属性: | ||||
|  | ||||
| - `path`:路由路径,不支持特殊占位表达式。类型为 `URL`。 | ||||
| - `name`:路由名称,不可重复。类型为 `str`。 | ||||
| - `handle_func`:路由处理函数。类型为 `Callable[[WebSocket], Awaitable[Any]]`。 | ||||
|  | ||||
| 例如,我们添加一个 `/ws` 的路由,发送所有接收到的数据: | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
| from nonebot.drivers import URL, ASGIMixin, WebSocket, WebSocketServerSetup | ||||
|  | ||||
| async def ws_handler(ws: WebSocket): | ||||
|     await ws.accept() | ||||
|     try: | ||||
|       while True: | ||||
|           data = await ws.receive() | ||||
|           await ws.send(data) | ||||
|     except WebSocketClosed as e: | ||||
|         # handle closed | ||||
|         ... | ||||
|     finally: | ||||
|         with contextlib.suppress(Exception): | ||||
|             await websocket.close() | ||||
|         # do some cleanup | ||||
|  | ||||
| if isinstance((driver := get_driver()), ASGIMixin): | ||||
|     driver.setup_websocket_server( | ||||
|         WebSocketServerSetup( | ||||
|             path=URL("/ws"), | ||||
|             name="ws", | ||||
|             handle_func=ws_handler, | ||||
|         ) | ||||
|     ) | ||||
| ``` | ||||
|  | ||||
| 对于 `WebSocket` 的详细信息,可以参考 [API 文档](../api/drivers/index.md)。 | ||||
|  | ||||
| ## 使用 ASGI 应用添加路由 | ||||
|  | ||||
| ### 获取 ASGI 应用 | ||||
|  | ||||
| NoneBot 服务端类型的驱动器具有两个属性 `server_app` 和 `asgi`,分别对应驱动框架应用和 ASGI 应用。通常情况下,这两个应用是同一个对象。我们可以通过 `get_app()` 方法快速获取: | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
|  | ||||
| app = nonebot.get_app() | ||||
| asgi = nonebot.get_asgi() | ||||
| ``` | ||||
|  | ||||
| ### 添加路由规则 | ||||
|  | ||||
| 在获取到了 ASGI 应用后,我们就可以直接使用 ASGI 应用框架提供的功能来添加路由规则了。这里我们以 [FastAPI](./driver.md#fastapi默认) 为例,演示如何添加路由规则。 | ||||
|  | ||||
| 在下面的代码中,我们添加了一个 `GET` 类型的 `/api` 路由,具体方法参考 [FastAPI 文档](https://fastapi.tiangolo.com/)。 | ||||
|  | ||||
| ```python | ||||
| import nonebot | ||||
| from fastapi import FastAPI | ||||
|  | ||||
| app: FastAPI = nonebot.get_app() | ||||
|  | ||||
| @app.get("/api") | ||||
| async def custom_api(): | ||||
|     return {"message": "Hello, world!"} | ||||
| ``` | ||||
							
								
								
									
										159
									
								
								website/versioned_docs/version-2.3.0/advanced/runtime-hook.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								website/versioned_docs/version-2.3.0/advanced/runtime-hook.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| --- | ||||
| sidebar_position: 8 | ||||
| description: 在特定的生命周期中执行代码 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 90 | ||||
| --- | ||||
|  | ||||
| # 钩子函数 | ||||
|  | ||||
| > [钩子编程](https://zh.wikipedia.org/wiki/%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B)(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。 | ||||
|  | ||||
| 在 NoneBot 中有一系列预定义的钩子函数,可以分为两类:**全局钩子函数**和**事件处理钩子函数**,这些钩子函数可以用装饰器的形式来使用。 | ||||
|  | ||||
| ## 全局钩子函数 | ||||
|  | ||||
| 全局钩子函数是指 NoneBot 针对其本身运行过程的钩子函数。 | ||||
|  | ||||
| 这些钩子函数是由驱动器来运行的,故需要先[获得全局驱动器](./driver.md#获取驱动器)。 | ||||
|  | ||||
| ### 启动准备 | ||||
|  | ||||
| 这个钩子函数会在 NoneBot 启动时运行。很多时候,我们并不希望在模块被导入时就执行一些耗时操作,如:连接数据库,这时候我们可以在这个钩子函数中进行这些操作。 | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
|  | ||||
| driver = get_driver() | ||||
|  | ||||
| @driver.on_startup | ||||
| async def do_something(): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ### 终止处理 | ||||
|  | ||||
| 这个钩子函数会在 NoneBot 终止时运行。我们可以在这个钩子函数中进行一些清理工作,如:关闭数据库连接。 | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
|  | ||||
| driver = get_driver() | ||||
|  | ||||
| @driver.on_shutdown | ||||
| async def do_something(): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ### Bot 连接处理 | ||||
|  | ||||
| 这个钩子函数会在任何协议适配器连接 `Bot` 对象至 NoneBot 时运行。支持依赖注入,可以直接注入 `Bot` 对象。 | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
|  | ||||
| driver = get_driver() | ||||
|  | ||||
| @driver.on_bot_connect | ||||
| async def do_something(bot: Bot): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ### Bot 断开处理 | ||||
|  | ||||
| 这个钩子函数会在 `Bot` 断开与 NoneBot 的连接时运行。支持依赖注入,可以直接注入 `Bot` 对象。 | ||||
|  | ||||
| ```python | ||||
| from nonebot import get_driver | ||||
|  | ||||
| driver = get_driver() | ||||
|  | ||||
| @driver.on_bot_disconnect | ||||
| async def do_something(bot: Bot): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ## 事件处理钩子函数 | ||||
|  | ||||
| 这些钩子函数指的是影响 NoneBot 进行**事件处理**的函数, 这些函数可以跟普通的事件处理函数一样接受相应的参数。 | ||||
|  | ||||
| ### 事件预处理 | ||||
|  | ||||
| 这个钩子函数会在 NoneBot 接收到新的事件时运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.message import event_preprocessor | ||||
|  | ||||
| @event_preprocessor | ||||
| async def do_something(event: Event): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ### 事件后处理 | ||||
|  | ||||
| 这个钩子函数会在 NoneBot 处理事件完成后运行。支持依赖注入,可以注入 `Bot` 对象、事件、会话状态。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.message import event_postprocessor | ||||
|  | ||||
| @event_postprocessor | ||||
| async def do_something(event: Event): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ### 运行预处理 | ||||
|  | ||||
| 这个钩子函数会在 NoneBot 运行事件响应器前运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.message import run_preprocessor | ||||
|  | ||||
| @run_preprocessor | ||||
| async def do_something(event: Event, matcher: Matcher): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ### 运行后处理 | ||||
|  | ||||
| 这个钩子函数会在 NoneBot 运行事件响应器后运行。支持依赖注入,可以注入 `Bot` 对象、事件、事件响应器、会话状态、运行中产生的异常。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.message import run_postprocessor | ||||
|  | ||||
| @run_postprocessor | ||||
| async def do_something(event: Event, matcher: Matcher, exception: Optional[Exception]): | ||||
|     pass | ||||
| ``` | ||||
|  | ||||
| ### 平台接口调用钩子 | ||||
|  | ||||
| 这个钩子函数会在 `Bot` 对象调用平台接口时运行。在这个钩子函数中,我们可以通过引起 `MockApiException` 异常来阻止 `Bot` 对象调用平台接口并返回指定的结果。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.adapters import Bot | ||||
| from nonebot.exception import MockApiException | ||||
|  | ||||
| @Bot.on_calling_api | ||||
| async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]): | ||||
|     if api == "send_msg": | ||||
|         raise MockApiException(result={"message_id": 123}) | ||||
| ``` | ||||
|  | ||||
| ### 平台接口调用后钩子 | ||||
|  | ||||
| 这个钩子函数会在 `Bot` 对象调用平台接口后运行。在这个钩子函数中,我们可以通过引起 `MockApiException` 异常来忽略平台接口返回的结果并返回指定的结果。 | ||||
|  | ||||
| ```python | ||||
| from nonebot.adapters import Bot | ||||
| from nonebot.exception import MockApiException | ||||
|  | ||||
| @Bot.on_called_api | ||||
| async def handle_api_result( | ||||
|     bot: Bot, exception: Optional[Exception], api: str, data: Dict[str, Any], result: Any | ||||
| ): | ||||
|     if not exception and api == "send_msg": | ||||
|         raise MockApiException(result={**result, "message_id": 123}) | ||||
| ``` | ||||
| @@ -0,0 +1,59 @@ | ||||
| --- | ||||
| sidebar_position: 7 | ||||
| description: 控制会话响应对象 | ||||
|  | ||||
| options: | ||||
|   menu: | ||||
|     - category: advanced | ||||
|       weight: 80 | ||||
| --- | ||||
|  | ||||
| # 会话更新 | ||||
|  | ||||
| 在 NoneBot 中,在某个事件响应器对事件响应后,即是进入了会话状态,会话状态会持续到整个事件响应流程结束。会话过程中,机器人可以与用户进行多次交互。每次需要等待用户事件时,NoneBot 将会复制一个新的临时事件响应器,并更新该事件响应器使其响应当前会话主体的消息,这个过程称为会话更新。 | ||||
|  | ||||
| 会话更新分为两部分:**更新[事件响应器类型](./matcher.md#事件响应器类型)**和**更新[事件触发权限](./matcher.md#事件触发权限)**。 | ||||
|  | ||||
| ## 更新事件响应器类型 | ||||
|  | ||||
| 通常情况下,与机器人用户进行的会话都是通过消息事件进行的,因此会话更新后的默认响应事件类型为 `message`。如果希望接收一个特定类型的消息,比如 `notice` 等,我们需要自定义响应事件类型更新函数。响应事件类型更新函数是一个 `Dependent`,可以使用依赖注入。 | ||||
|  | ||||
| ```python {3-5} | ||||
| foo = on_message() | ||||
|  | ||||
| @foo.type_updater | ||||
| async def _() -> str: | ||||
|     return "notice" | ||||
| ``` | ||||
|  | ||||
| 在注册了上述响应事件类型更新函数后,当我们需要等待用户事件时,将只会响应 `notice` 类型的事件。如果希望在会话过程中的不同阶段响应不同类型的事件,我们就需要使用更复杂的逻辑来更新响应事件类型(如:根据会话状态),这里将不再展示。 | ||||
|  | ||||
| ## 更新事件触发权限 | ||||
|  | ||||
| 会话通常是由机器人与用户进行的一对一交互,因此会话更新后的默认触发权限为当前事件的会话 ID。这个会话 ID 由协议适配器生成,通常由用户 ID 和群 ID 等组成。如果希望实现更复杂的会话功能(如:多用户同时参与的会话),我们需要自定义触发权限更新函数。触发权限更新函数是一个 `Dependent`,可以使用依赖注入。 | ||||
|  | ||||
| ```python {5-7} | ||||
| from nonebot.permission import User | ||||
|  | ||||
| foo = on_message() | ||||
|  | ||||
| @foo.permission_updater | ||||
| async def _(event: Event, matcher: Matcher) -> Permission: | ||||
|     return Permission(User.from_event(event, perm=matcher.permission)) | ||||
| ``` | ||||
|  | ||||
| 上述权限更新函数是默认的权限更新函数,它将会话的触发权限更新为当前事件的会话 ID。如果我们希望响应多个用户的消息,我们可以如下修改: | ||||
|  | ||||
| ```python {5-7} | ||||
| from nonebot.permission import USER | ||||
|  | ||||
| foo = on_message() | ||||
|  | ||||
| @foo.permission_updater | ||||
| async def _(matcher: Matcher) -> Permission: | ||||
|     return USER("session1", "session2", perm=matcher.permission) | ||||
| ``` | ||||
|  | ||||
| 请注意,此处为全大写字母的 `USER` 权限,它可以匹配多个会话 ID。通过这种方式,我们可以实现多用户同时参与的会话。 | ||||
|  | ||||
| 我们已经了解了如何控制会话的更新,相信你已经能够实现更复杂的会话功能了,例如多人小游戏等等。欢迎将你的作品分享到[插件商店](/store/plugins)。 | ||||
		Reference in New Issue
	
	Block a user