🔀 Merge pull request #308

Pre Release 2.0.0a12
This commit is contained in:
Ju4tCode 2021-03-31 08:55:18 -05:00 committed by GitHub
commit 953328903c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 1917 additions and 744 deletions

View File

@ -1,60 +0,0 @@
# 钩子函数
[`钩子编程`](https://zh.wikipedia.org/wiki/%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B)
> 钩子编程hooking也称作“挂钩”是计算机程序设计术语指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码被称为钩子hook
`nonebot2` 中有一系列预定义的钩子函数,这些函数位于 [`nonebot.message`](https://v2.nonebot.dev/api/message.html) 模块下,我们可以以装饰器的形式利用这些函数,进行以下四种操作:
:::warning 注意
1.在钩子函数中,与 `matcher` 运行状态相关的函数将不可用,如 `matcher.finish()`
2.如果需要在钩子函数中打断整个对话的执行,请参考以下范例:
```python
from nonebot.exception import IgnoredException
@event_preprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
raise IgnoredException("reason")
```
:::
## 事件预处理
```python
from nonebot.message import event_preprocessor
@event_preprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
pass
```
## 事件后处理
```python
from nonebot.message import event_postprocessor
@event_postprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
pass
```
## 运行预处理
```python
from nonebot.message import run_preprocessor
@run_preprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
pass
```
## 运行后处理
```python
from nonebot.message import run_postprocessor
@run_postprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
pass
```

View File

@ -10,9 +10,17 @@
**便捷起见,以下内容对 `Nonebot2` 会被称为 `nonebot`,与 `Nonebot2` 交互的机器人实现会被称为 `协议端`**。
在实际应用中,`nonebot` 会充当一个高性能,轻量级的 Python 微服务框架。协议端可以通过 `http`, `websocket` 等方式与之通信,这个通信往往是双向的:一方面,协议端可以上报数据给 `nonebot``nonebot` 会处理数据并返回响应给协议端;另一方面,`nonebot` 可以主动推送数据给协议端。而 `nonebot` 便是围绕上述的双向通信进行工作的。
在实际应用中,`nonebot` 会充当一个高性能,轻量级的 Python 微服务框架。协议端可以通过 `http`, `websocket` 等方式与之通信,这个通信往往是双向的:一方面,协议端可以上报数据给 `nonebot``nonebot` 会处理数据并返回响应给协议端;另一方面,`nonebot` 可以主动推送数据给协议端。而 `nonebot` 便是围绕双向通信进行工作的。
在开始工作之前,`nonebot` 会依照**配置文件或初始化配置**启动,并会注册**协议适配器** `adapter`,之后便会加载**插件** 随后,倘若一个协议端与 `nonebot` 进行了连接,`nonebot` 的后端驱动 `driver` 就会将 `adapter` 实例化为 `bot``nonebot` 便会利用 `bot` 开始工作,它的工作内容分为两个方面:
在开始工作之前,`nonebot` 需要进行准备工作:
1. **运行 `nonebot.init` 初始化函数**,它会读取配置文件,并初始化 `nonebot` 和后端驱动 `driver` 对象。
2. **注册协议适配器 `adapter`**
3. **加载插件**
准备工作完成后,`nonebot` 会利用 `uvicorn` 启动,并运行 `on_startup` 钩子函数。
随后,倘若一个协议端与 `nonebot` 进行了连接,`nonebot` 的后端驱动 `driver` 就会将 `adapter` 实例化为 `bot``nonebot` 便会利用 `bot` 开始工作,它的工作内容分为两个方面:
1. **事件处理**`bot` 会将协议端上报的数据转化为 `事件`(`Event`),之后 `nonebot` 会根据一套既定流程来处理 `事件`
@ -41,7 +49,7 @@
1. 协议端会通过 `websocket` 或者 `http` 等方式与 `nonebot` 的后端驱动 `driver` 连接,`driver` 会根据之前注册的 `adapter` 和配置文件的内容来进行鉴权,从而获得这个连接的唯一识别 id `self-id`,随后 `adapter` 就会利用 `self-id` 实例化为 `bot` 对象。
::: tip
需要注意的是,如果协议端通过 `websocket``nonebot` 连接,这个步骤只会在建立连接时进行;通过 `http` 方式连接时,会在协议端每次上报数据时都进行这个步骤。
需要注意的是,如果协议端通过 `websocket``nonebot` 连接,这个步骤只会在建立连接时进行,并在之后运行 `on_bot_connect` 钩子函数;通过 `http` 方式连接时,会在协议端每次上报数据时都进行这个步骤。
:::
::: warning
@ -142,7 +150,7 @@
这个异常可以在 `handler` 中由 `Matcher.reject` 抛出。
`nonebot` 捕获到它时,会停止运行当前 `handler` 并结束当前 `matcher` 的运行,并将**当前 handler 和后续 `handler`**交给一个临时 `Matcher` 来响应当前交互用户的下一个消息事件,当临时 `Matcher` 响应时,临时 `Matcher` 会运行当前 `handler` 和后续的 `handler`
`nonebot` 捕获到它时,会停止运行当前 `handler` 并结束当前 `matcher` 的运行,并将当前 handler 和后续 `handler` 交给一个临时 `Matcher` 来响应当前交互用户的下一个消息事件,当临时 `Matcher` 响应时,临时 `Matcher` 会运行当前 `handler` 和后续的 `handler`
4. **FinishedException**
@ -158,7 +166,7 @@
## 调用 API
`nonebot` 可以通过 `bot` 来调用 APIAPI 可以向协议端发送数据,也可以向协议端请求更多的数据。
`nonebot` 可以通过 `bot` 来调用 `API` `API` 可以向协议端发送数据,也可以向协议端请求更多的数据。
::: tip
不同 `adapter` 规定了不同的 API对应的 API 列表请参照协议规范。

View File

@ -0,0 +1,151 @@
# 钩子函数
[`钩子编程`](https://zh.wikipedia.org/wiki/%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B)
> 钩子编程hooking也称作“挂钩”是计算机程序设计术语指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码被称为钩子hook
`nonebot2` 中有一系列预定义的钩子函数,分为两类:`全局钩子函数` 和 `事件钩子函数` ,这些钩子函数可以用装饰器的形式来使用。
## 全局钩子函数
全局钩子函数是指 `nonebot2` 针对其本身运行过程的钩子函数。
这些钩子函数是由其后端驱动 `driver`来运行的,故需要先获得全局 `driver` 对象:
```python
from nonebot import get_driver
driver=get_driver()
```
共分为五种函数:
### 启动准备
这个钩子函数会在 `nonebot2` 启动时运行。
```python
@driver.on_startup
async def do_something():
pass
```
### 终止处理
这个钩子函数会在 `nonebot2` 终止时运行。
```python
@driver.on_shutdown
async def do_something():
pass
```
### bot 连接处理
这个钩子函数会在 `bot` 通过 `websocket` 连接到 `nonebot2` 时运行。
```python
@driver.on_bot_connect
async def do_something(bot: Bot):
pass
```
### bot 断开处理
这个钩子函数会在 `bot` 断开与 `nonebot2``websocket` 连接时运行。
```python
@driver.on_bot_disconnect
async def do_something(bot: Bot):
pass
```
### bot api 调用钩子
这个钩子函数会在 `Bot` 调用 API 时运行。
```python
from nonebot.adapters import Bot
@Bot.on_calling_api
async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
pass
```
## 事件处理钩子
这些钩子函数指的是影响 `nonebot2` 进行 `事件处理` 的函数。
:::tip 提示
关于 `事件处理` 的流程,可以在[这里](./README)查阅。
:::
:::warning 注意
1.在事件处理钩子函数中,与 `matcher` 运行状态相关的函数将不可用,如 `matcher.finish()`
2.如果需要在事件处理钩子函数中打断整个对话的执行,请参考以下范例:
```python
from nonebot.exception import IgnoredException
@event_preprocessor
async def do_something(bot: Bot, event: Event, state: T_State):
raise IgnoredException("reason")
```
:::
共分为四种函数:
### 事件预处理
这个钩子函数会在 `Event` 上报到 `nonebot2` 时运行
```python
from nonebot.message import event_preprocessor
@event_preprocessor
async def do_something(bot: Bot, event: Event, state: T_State):
pass
```
### 事件后处理
这个钩子函数会在 `nonebot2` 处理 `Event` 后运行
```python
from nonebot.message import event_postprocessor
@event_postprocessor
async def do_something(bot: Bot, event: Event, state: T_State):
pass
```
### 运行预处理
这个钩子函数会在 `nonebot2`运行 `matcher` 前运行。
```python
from nonebot.message import run_preprocessor
@run_preprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
pass
```
### 运行后处理
这个钩子函数会在 `nonebot2`运行 `matcher` 后运行。
```python
from nonebot.message import run_postprocessor
@run_postprocessor
async def do_something(matcher: Matcher, exception: Optional[Exception], bot: Bot, event: Event, state: T_State):
pass
```

View File

@ -19,6 +19,9 @@
* [nonebot.matcher](matcher.html)
* [nonebot.handler](handler.html)
* [nonebot.rule](rule.html)

View File

@ -27,6 +27,21 @@ Driver 对象
Config 配置对象
### `_call_api_hook`
* **类型**
`Set[T_CallingAPIHook]`
* **说明**
call_api 时执行的函数
### _abstract_ `__init__(connection_type, self_id, *, websocket=None)`
@ -93,7 +108,7 @@ Adapter 类型
* `headers: dict`: 请求头
* `body: Optional[dict]`: 请求数据WebSocket 连接该部分为空
* `body: Optional[bytes]`: 请求数据WebSocket 连接该部分为 None
@ -127,7 +142,26 @@ Adapter 类型
### _abstract async_ `call_api(api, **data)`
### _abstract async_ `_call_api(api, **data)`
* **说明**
`adapter` 实际调用 api 的逻辑实现函数,实现该方法以调用 api。
* **参数**
* `api: str`: API 名称
* `**data`: API 数据
### _async_ `call_api(api, **data)`
* **说明**
@ -142,6 +176,9 @@ Adapter 类型
* `api: str`: API 名称
* `self_id: Optional[str]`: 指定调用 API 的机器人
* `**data`: API 数据

View File

@ -129,6 +129,9 @@ sidebarDepth: 0
* `api: str`: API 名称
* `event: Optional[MessageEvent]`: Event 对象
* `**data: Any`: API 参数
@ -150,7 +153,7 @@ sidebarDepth: 0
### _async_ `send(event, message, at_sender=False, **kwargs)`
### _async_ `send(event, message, at_sender=False, webhook=None, secret=None, **kwargs)`
* **说明**
@ -171,6 +174,12 @@ sidebarDepth: 0
* `at_sender: bool`: 是否 @ 事件主体
* `webhook: Optional[str]`: 该条消息将调用的 webhook 地址。不传则将使用 sessionWebhook若其也不存在该条消息不发送使用自定义 webhook 时注意你设置的安全方式如加关键词IP地址加签等等。
* `secret: Optional[str]`: 如果你使用自定义的 webhook 地址,推荐使用加签方式对消息进行验证,将 机器人安全设置页面加签一栏下面显示的SEC开头的字符串 传入这个参数即可。
* `**kwargs`: 覆盖默认参数

View File

@ -28,7 +28,7 @@ FastAPI 驱动框架设置,详情参考 FastAPI 文档
* **说明**
openapi.json 地址,默认为 None 即关闭
`openapi.json` 地址,默认为 `None` 即关闭
@ -43,7 +43,7 @@ FastAPI 驱动框架设置,详情参考 FastAPI 文档
* **说明**
swagger 地址,默认为 None 即关闭
`swagger` 地址,默认为 `None` 即关闭
@ -58,7 +58,22 @@ FastAPI 驱动框架设置,详情参考 FastAPI 文档
* **说明**
redoc 地址,默认为 None 即关闭
`redoc` 地址,默认为 `None` 即关闭
### `fastapi_reload_dirs`
* **类型**
`List[str]`
* **说明**
`debug` 模式下重载监控文件夹列表,默认为 uvicorn 默认值

View File

@ -0,0 +1,111 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.handler 模块
## 事件处理函数
该模块实现事件处理函数的封装,以实现动态参数等功能。
## _class_ `Handler`
基类:`object`
事件处理函数类
### `__init__(func)`
装饰事件处理函数以便根据动态参数运行
### `func`
* **类型**
`T_Handler`
* **说明**
事件处理函数
### `signature`
* **类型**
`inspect.Signature`
* **说明**
事件处理函数签名
### _property_ `bot_type`
* **类型**
`Union[Type["Bot"], inspect.Parameter.empty]`
* **说明**
事件处理函数接受的 Bot 对象类型
### _property_ `event_type`
* **类型**
`Optional[Union[Type[Event], inspect.Parameter.empty]]`
* **说明**
事件处理函数接受的 event 类型 / 不需要 event 参数
### _property_ `state_type`
* **类型**
`Optional[Union[T_State, inspect.Parameter.empty]]`
* **说明**
事件处理函数是否接受 state 参数
### _property_ `matcher_type`
* **类型**
`Optional[Union[Type["Matcher"], inspect.Parameter.empty]]`
* **说明**
事件处理函数是否接受 matcher 参数

View File

@ -7,7 +7,7 @@ sidebarDepth: 0
## 事件响应器
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行 对话 。
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话 。
## `matchers`
@ -202,7 +202,7 @@ sidebarDepth: 0
* **类型**
`Optional[T_ArgsParser]`
`Optional[T_TypeUpdater]`
@ -217,7 +217,7 @@ sidebarDepth: 0
* **类型**
`Optional[T_ArgsParser]`
`Optional[T_PermissionUpdater]`
@ -237,7 +237,7 @@ sidebarDepth: 0
* **类型**
`List[T_Handler]`
`List[Handler]`

View File

@ -25,38 +25,6 @@ sidebarDepth: 0
## _class_ `Export`
基类:`dict`
* **说明**
插件导出内容以使得其他插件可以获得。
* **示例**
```python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
```
## _class_ `Plugin`
基类:`object`
@ -82,15 +50,6 @@ def something_else():
* **说明**: 插件模块对象
### `matcher`
* **类型**: `Set[Type[Matcher]]`
* **说明**: 插件内定义的 `Matcher`
### `export`
@ -100,6 +59,15 @@ def something_else():
* **说明**: 插件内定义的导出内容
### _property_ `matcher`
* **类型**: `Set[Type[Matcher]]`
* **说明**: 插件内定义的 `Matcher`
## `on(type='', rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=False, state=None, state_factory=None)`
@ -121,7 +89,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -162,7 +130,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -206,7 +174,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -247,7 +215,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -288,7 +256,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -335,7 +303,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -382,7 +350,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -429,7 +397,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -481,7 +449,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -538,7 +506,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -590,7 +558,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -767,7 +735,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -808,7 +776,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -852,7 +820,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -893,7 +861,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -934,7 +902,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -981,7 +949,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1028,7 +996,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1075,7 +1043,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1127,7 +1095,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1184,7 +1152,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1236,7 +1204,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1442,22 +1410,6 @@ def something_else():
## `export()`
* **说明**
获取插件的导出内容对象
* **返回**
* `Export`
## `require(name)`
@ -1478,3 +1430,51 @@ def something_else():
* `Optional[Export]`
## _class_ `Export`
基类:`dict`
* **说明**
插件导出内容以使得其他插件可以获得。
* **示例**
```python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
```
## `export()`
* **说明**
获取插件的导出内容对象
* **返回**
* `Export`

View File

@ -1,5 +1,11 @@
# CQHTTP 协议使用指南
## 安装 NoneBot CQHTTP 适配器
```bash
pip install nonebot-adapter-cqhttp
```
## 配置 CQHTTP 协议端(以 QQ 为例)
单纯运行 NoneBot 实例并不会产生任何效果,因为此刻 QQ 这边还不知道 NoneBot 的存在,也就无法把消息发送给它,因此现在需要使用一个无头 QQ 来把消息等事件上报给 NoneBot。

View File

@ -11,6 +11,16 @@
- [群机器人概述](https://developers.dingtalk.com/document/app/overview-of-group-robots)
- [开发企业内部机器人](https://developers.dingtalk.com/document/app/develop-enterprise-internal-robots)
钉钉官方机器人教程Java
- [开发一个钉钉机器人](https://developers.dingtalk.com/document/tutorial/create-a-robot)
## 安装 NoneBot 钉钉 适配器
```bash
pip install nonebot-adapter-ding
```
## 关于 DingAdapter 的说明
你需要显式的注册 ding 这个适配器:
@ -87,6 +97,58 @@ async def raw_handler(bot: DingBot, event: MessageEvent):
其他消息格式请查看 [钉钉适配器的 MessageSegment](https://github.com/nonebot/nonebot2/blob/dev/nonebot/adapters/ding/message.py#L8),里面封装了很多有关消息的方法,比如 `code`、`image`、`feedCard` 等。
## 发送到特定群聊
钉钉也支持通过 Webhook 的方式直接将消息推送到某个群聊([参考链接](https://developers.dingtalk.com/document/app/custom-robot-access/title-zob-eyu-qse)),你可以在机器人的设置中看到当前群的 Webhook 地址。
![机器人所在群的 Webhook 地址](./images/ding/webhook.png)
获取到Webhook地址后用户可以向这个地址发起HTTP POST 请求,即可实现给该钉钉群发送消息。
对于这种通过 Webhook 推送的消息钉钉需要开发者进行安全方面的设置目前有3种安全设置方式请根据需要选择一种如下
1. **自定义关键词:** 最多可以设置10个关键词消息中至少包含其中1个关键词才可以发送成功。
例如添加了一个自定义关键词:监控报警,则这个机器人所发送的消息,必须包含监控报警这个词,才能发送成功。
2. **加签:** 发送请求时带上验签的值,可以在机器人设置里看到密钥。
![加签密钥](./images/ding/jiaqian.png)
3. **IP地址** 设定后只有来自IP地址范围内的请求才会被正常处理。支持两种设置方式IP地址和IP地址段暂不支持IPv6地址白名单。
如果你选择 1/3 两种安全设置,你需要自己确认当前网络和发送的消息能被钉钉接受,然后使用 `bot.send` 的时候将 webhook 地址传入 webhook 参数即可。
如我设置了 `打卡` 为关键词:
```python
message = MessageSegment.text("打卡成功XXXXXX")
await hello.send(
message,
webhook=
"https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
)
```
对于第二种加签方式,你可以在 `bot.send` 的时候把 `secret` 参数传进去Nonebot 内部会自动帮你计算发送该消息的签名并发送,如:
这里的 `secret` 参数就是加签选项给出的那个密钥。
```python
message = MessageSegment.raw({
"msgtype": "text",
"text": {
"content": 'hello from webhook,一定要注意安全方式的鉴权哦,否则可能发送失败的'
},
})
message += MessageSegment.atDingtalkIds(event.senderId)
await hello.send(
message,
webhook="https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
secret="SECXXXXXXXXXXXXXXXXXXXXXXXXX",
)
```
然后就可以发送成功了。
![测试 Webhook 发送](images/ding/test_webhook.png)
## 创建机器人并连接
在钉钉官方文档 [「开发企业内部机器人 -> 步骤一:创建机器人应用」](https://developers.dingtalk.com/document/app/develop-enterprise-internal-robots/title-ufs-4gh-poh) 中有详细介绍,这里就省去创建的步骤,介绍一下如何连接上程序。

View File

@ -4,6 +4,5 @@
- 请千万注意事件处理器的优先级设定
- 在匹配规则中请勿使用耗时极长的函数
- 同一个用户可以**跨群**(**私聊**)继续他的事件处理(除非做出权限限制,将在后续介绍)
如果「指南」还不能满足你,前往 [进阶](../advanced/README.md) 查看更多的功能信息。

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -1,6 +1,6 @@
# 安装
## NoneBot
## 安装 NoneBot
:::warning 注意
请确保你的 Python 版本 >= 3.7。
@ -67,6 +67,19 @@ poetry install --no-dev # 推荐
pip install . # 不推荐
```
## 安装适配器
适配器可以通过 `nb-cli` 在创建项目时根据你的选择自动安装,也可以自行使用 `pip` 安装
```bash
pip install nonebot-adapter-<adapter-name>
```
```bash
# 列出所有的适配器
nb adapter list
```
## 安装插件
插件可以通过 `nb-cli` 进行安装,也可以自行安装并加载插件。
@ -87,6 +100,7 @@ nb plugin install xxx
- [NoneBot-Plugin-Docs](https://github.com/nonebot/nonebot2/tree/master/packages/nonebot-plugin-docs) 离线文档插件
- [NoneBot-Plugin-Test](https://github.com/nonebot/plugin-test) 本地机器人测试前端插件
- [NoneBot-Plugin-APScheduler](https://github.com/nonebot/plugin-apscheduler) 定时任务插件
- [NoneBot-Plugin-LocalStore](https://github.com/nonebot/plugin-localstore) 本地数据文件存储插件
- [NoneBot-Plugin-Sentry](https://github.com/cscs181/QQ-GitHub-Bot/tree/master/src/plugins/nonebot_plugin_sentry) Sentry 在线日志分析插件
- [NoneBot-Plugin-Status](https://github.com/cscs181/QQ-GitHub-Bot/tree/master/src/plugins/nonebot_plugin_status) 服务器状态查看插件

View File

@ -28,6 +28,12 @@ Mirai-API-HTTP 的适配器以 [AGPLv3 许可](https://opensource.org/licenses/A
**为了便捷起见, 以下内容均以缩写 `MAH` 代替 `mirai-api-http`**
## 安装 NoneBot Mirai 适配器
```bash
pip install nonebot-adapter-mirai
```
## 配置 MAH 客户端
正如你可能刚刚在[CQHTTP 协议使用指南](./cqhttp-guide.md)中所读到的:

View File

@ -119,6 +119,10 @@
"title": "nonebot.matcher 模块",
"path": "matcher"
},
{
"title": "nonebot.handler 模块",
"path": "handler"
},
{
"title": "nonebot.rule 模块",
"path": "rule"

View File

@ -166,6 +166,10 @@ module.exports = context => ({
title: "nonebot.matcher 模块",
path: "matcher"
},
{
title: "nonebot.handler 模块",
path: "handler"
},
{
title: "nonebot.rule 模块",
path: "rule"

View File

@ -1,5 +1,5 @@
[
"2.0.0a11",
"2.0.0a12",
"2.0.0a10",
"2.0.0a8.post2",
"2.0.0a7"

View File

@ -10,9 +10,17 @@
**便捷起见,以下内容对 `Nonebot2` 会被称为 `nonebot`,与 `Nonebot2` 交互的机器人实现会被称为 `协议端`**。
在实际应用中,`nonebot` 会充当一个高性能,轻量级的 Python 微服务框架。协议端可以通过 `http`, `websocket` 等方式与之通信,这个通信往往是双向的:一方面,协议端可以上报数据给 `nonebot``nonebot` 会处理数据并返回响应给协议端;另一方面,`nonebot` 可以主动推送数据给协议端。而 `nonebot` 便是围绕上述的双向通信进行工作的。
在实际应用中,`nonebot` 会充当一个高性能,轻量级的 Python 微服务框架。协议端可以通过 `http`, `websocket` 等方式与之通信,这个通信往往是双向的:一方面,协议端可以上报数据给 `nonebot``nonebot` 会处理数据并返回响应给协议端;另一方面,`nonebot` 可以主动推送数据给协议端。而 `nonebot` 便是围绕双向通信进行工作的。
在开始工作之前,`nonebot` 会依照**配置文件或初始化配置**启动,并会注册**协议适配器** `adapter`,之后便会加载**插件** 随后,倘若一个协议端与 `nonebot` 进行了连接,`nonebot` 的后端驱动 `driver` 就会将 `adapter` 实例化为 `bot``nonebot` 便会利用 `bot` 开始工作,它的工作内容分为两个方面:
在开始工作之前,`nonebot` 需要进行准备工作:
1. **运行 `nonebot.init` 初始化函数**,它会读取配置文件,并初始化 `nonebot` 和后端驱动 `driver` 对象。
2. **注册协议适配器 `adapter`**
3. **加载插件**
准备工作完成后,`nonebot` 会利用 `uvicorn` 启动,并运行 `on_startup` 钩子函数。
随后,倘若一个协议端与 `nonebot` 进行了连接,`nonebot` 的后端驱动 `driver` 就会将 `adapter` 实例化为 `bot``nonebot` 便会利用 `bot` 开始工作,它的工作内容分为两个方面:
1. **事件处理**`bot` 会将协议端上报的数据转化为 `事件`(`Event`),之后 `nonebot` 会根据一套既定流程来处理 `事件`
@ -41,7 +49,7 @@
1. 协议端会通过 `websocket` 或者 `http` 等方式与 `nonebot` 的后端驱动 `driver` 连接,`driver` 会根据之前注册的 `adapter` 和配置文件的内容来进行鉴权,从而获得这个连接的唯一识别 id `self-id`,随后 `adapter` 就会利用 `self-id` 实例化为 `bot` 对象。
::: tip
需要注意的是,如果协议端通过 `websocket``nonebot` 连接,这个步骤只会在建立连接时进行;通过 `http` 方式连接时,会在协议端每次上报数据时都进行这个步骤。
需要注意的是,如果协议端通过 `websocket``nonebot` 连接,这个步骤只会在建立连接时进行,并在之后运行 `on_bot_connect` 钩子函数;通过 `http` 方式连接时,会在协议端每次上报数据时都进行这个步骤。
:::
::: warning
@ -142,7 +150,7 @@
这个异常可以在 `handler` 中由 `Matcher.reject` 抛出。
`nonebot` 捕获到它时,会停止运行当前 `handler` 并结束当前 `matcher` 的运行,并将**当前 handler 和后续 `handler`**交给一个临时 `Matcher` 来响应当前交互用户的下一个消息事件,当临时 `Matcher` 响应时,临时 `Matcher` 会运行当前 `handler` 和后续的 `handler`
`nonebot` 捕获到它时,会停止运行当前 `handler` 并结束当前 `matcher` 的运行,并将当前 handler 和后续 `handler` 交给一个临时 `Matcher` 来响应当前交互用户的下一个消息事件,当临时 `Matcher` 响应时,临时 `Matcher` 会运行当前 `handler` 和后续的 `handler`
4. **FinishedException**
@ -158,7 +166,7 @@
## 调用 API
`nonebot` 可以通过 `bot` 来调用 APIAPI 可以向协议端发送数据,也可以向协议端请求更多的数据。
`nonebot` 可以通过 `bot` 来调用 `API` `API` 可以向协议端发送数据,也可以向协议端请求更多的数据。
::: tip
不同 `adapter` 规定了不同的 API对应的 API 列表请参照协议规范。

View File

@ -4,43 +4,131 @@
> 钩子编程hooking也称作“挂钩”是计算机程序设计术语指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码被称为钩子hook
`nonebot2` 中有一系列预定义的钩子函数,这些函数位于 [`nonebot.message`](https://v2.nonebot.dev/api/message.html) 模块下,我们可以以装饰器的形式利用这些函数,进行以下四种操作:
`nonebot2` 中有一系列预定义的钩子函数,分为两类:`全局钩子函数` 和 `事件钩子函数` ,这些钩子函数可以用装饰器的形式来使用。
## 全局钩子函数
全局钩子函数是指 `nonebot2` 针对其本身运行过程的钩子函数。
这些钩子函数是由其后端驱动 `driver`来运行的,故需要先获得全局 `driver` 对象:
```python
from nonebot import get_driver
driver=get_driver()
```
共分为五种函数:
### 启动准备
这个钩子函数会在 `nonebot2` 启动时运行。
```python
@driver.on_startup
async def do_something():
pass
```
### 终止处理
这个钩子函数会在 `nonebot2` 终止时运行。
```python
@driver.on_shutdown
async def do_something():
pass
```
### bot 连接处理
这个钩子函数会在 `bot` 通过 `websocket` 连接到 `nonebot2` 时运行。
```python
@driver.on_bot_connect
async def do_something(bot: Bot):
pass
```
### bot 断开处理
这个钩子函数会在 `bot` 断开与 `nonebot2``websocket` 连接时运行。
```python
@driver.on_bot_disconnect
async def do_something(bot: Bot):
pass
```
### bot api 调用钩子
这个钩子函数会在 `Bot` 调用 API 时运行。
```python
from nonebot.adapters import Bot
@Bot.on_calling_api
async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
pass
```
## 事件处理钩子
这些钩子函数指的是影响 `nonebot2` 进行 `事件处理` 的函数。
:::tip 提示
关于 `事件处理` 的流程,可以在[这里](./README)查阅。
:::
:::warning 注意
1.在钩子函数中,与 `matcher` 运行状态相关的函数将不可用,如 `matcher.finish()`
2.如果需要在钩子函数中打断整个对话的执行,请参考以下范例:
1.在事件处理钩子函数中,与 `matcher` 运行状态相关的函数将不可用,如 `matcher.finish()`
2.如果需要在事件处理钩子函数中打断整个对话的执行,请参考以下范例:
```python
from nonebot.exception import IgnoredException
@event_preprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
async def do_something(bot: Bot, event: Event, state: T_State):
raise IgnoredException("reason")
```
:::
## 事件预处理
共分为四种函数:
### 事件预处理
这个钩子函数会在 `Event` 上报到 `nonebot2` 时运行
```python
from nonebot.message import event_preprocessor
@event_preprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
async def do_something(bot: Bot, event: Event, state: T_State):
pass
```
## 事件后处理
### 事件后处理
这个钩子函数会在 `nonebot2` 处理 `Event` 后运行
```python
from nonebot.message import event_postprocessor
@event_postprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
async def do_something(bot: Bot, event: Event, state: T_State):
pass
```
## 运行预处理
### 运行预处理
这个钩子函数会在 `nonebot2`运行 `matcher` 前运行。
```python
from nonebot.message import run_preprocessor
@ -50,11 +138,14 @@ async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State)
pass
```
## 运行后处理
### 运行后处理
这个钩子函数会在 `nonebot2`运行 `matcher` 后运行。
```python
from nonebot.message import run_postprocessor
@run_postprocessor
async def do_something(matcher: Matcher, bot: Bot, event: Event, state: T_State):
async def do_something(matcher: Matcher, exception: Optional[Exception], bot: Bot, event: Event, state: T_State):
pass
```
```

View File

@ -19,6 +19,9 @@
* [nonebot.matcher](matcher.html)
* [nonebot.handler](handler.html)
* [nonebot.rule](rule.html)

View File

@ -27,6 +27,21 @@ Driver 对象
Config 配置对象
### `_call_api_hook`
* **类型**
`Set[T_CallingAPIHook]`
* **说明**
call_api 时执行的函数
### _abstract_ `__init__(connection_type, self_id, *, websocket=None)`
@ -93,7 +108,7 @@ Adapter 类型
* `headers: dict`: 请求头
* `body: Optional[dict]`: 请求数据WebSocket 连接该部分为空
* `body: Optional[bytes]`: 请求数据WebSocket 连接该部分为 None
@ -127,7 +142,26 @@ Adapter 类型
### _abstract async_ `call_api(api, **data)`
### _abstract async_ `_call_api(api, **data)`
* **说明**
`adapter` 实际调用 api 的逻辑实现函数,实现该方法以调用 api。
* **参数**
* `api: str`: API 名称
* `**data`: API 数据
### _async_ `call_api(api, **data)`
* **说明**
@ -142,6 +176,9 @@ Adapter 类型
* `api: str`: API 名称
* `self_id: Optional[str]`: 指定调用 API 的机器人
* `**data`: API 数据

View File

@ -129,6 +129,9 @@ sidebarDepth: 0
* `api: str`: API 名称
* `event: Optional[MessageEvent]`: Event 对象
* `**data: Any`: API 参数
@ -150,7 +153,7 @@ sidebarDepth: 0
### _async_ `send(event, message, at_sender=False, **kwargs)`
### _async_ `send(event, message, at_sender=False, webhook=None, secret=None, **kwargs)`
* **说明**
@ -171,6 +174,12 @@ sidebarDepth: 0
* `at_sender: bool`: 是否 @ 事件主体
* `webhook: Optional[str]`: 该条消息将调用的 webhook 地址。不传则将使用 sessionWebhook若其也不存在该条消息不发送使用自定义 webhook 时注意你设置的安全方式如加关键词IP地址加签等等。
* `secret: Optional[str]`: 如果你使用自定义的 webhook 地址,推荐使用加签方式对消息进行验证,将 机器人安全设置页面加签一栏下面显示的SEC开头的字符串 传入这个参数即可。
* `**kwargs`: 覆盖默认参数

111
docs/api/handler.md Normal file
View File

@ -0,0 +1,111 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.handler 模块
## 事件处理函数
该模块实现事件处理函数的封装,以实现动态参数等功能。
## _class_ `Handler`
基类:`object`
事件处理函数类
### `__init__(func)`
装饰事件处理函数以便根据动态参数运行
### `func`
* **类型**
`T_Handler`
* **说明**
事件处理函数
### `signature`
* **类型**
`inspect.Signature`
* **说明**
事件处理函数签名
### _property_ `bot_type`
* **类型**
`Union[Type["Bot"], inspect.Parameter.empty]`
* **说明**
事件处理函数接受的 Bot 对象类型
### _property_ `event_type`
* **类型**
`Optional[Union[Type[Event], inspect.Parameter.empty]]`
* **说明**
事件处理函数接受的 event 类型 / 不需要 event 参数
### _property_ `state_type`
* **类型**
`Optional[Union[T_State, inspect.Parameter.empty]]`
* **说明**
事件处理函数是否接受 state 参数
### _property_ `matcher_type`
* **类型**
`Optional[Union[Type["Matcher"], inspect.Parameter.empty]]`
* **说明**
事件处理函数是否接受 matcher 参数

View File

@ -7,7 +7,7 @@ sidebarDepth: 0
## 事件响应器
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行 对话 。
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话 。
## `matchers`
@ -202,7 +202,7 @@ sidebarDepth: 0
* **类型**
`Optional[T_ArgsParser]`
`Optional[T_TypeUpdater]`
@ -217,7 +217,7 @@ sidebarDepth: 0
* **类型**
`Optional[T_ArgsParser]`
`Optional[T_PermissionUpdater]`
@ -237,7 +237,7 @@ sidebarDepth: 0
* **类型**
`List[T_Handler]`
`List[Handler]`

View File

@ -25,38 +25,6 @@ sidebarDepth: 0
## _class_ `Export`
基类:`dict`
* **说明**
插件导出内容以使得其他插件可以获得。
* **示例**
```python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
```
## _class_ `Plugin`
基类:`object`
@ -82,15 +50,6 @@ def something_else():
* **说明**: 插件模块对象
### `matcher`
* **类型**: `Set[Type[Matcher]]`
* **说明**: 插件内定义的 `Matcher`
### `export`
@ -100,6 +59,15 @@ def something_else():
* **说明**: 插件内定义的导出内容
### _property_ `matcher`
* **类型**: `Set[Type[Matcher]]`
* **说明**: 插件内定义的 `Matcher`
## `on(type='', rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=False, state=None, state_factory=None)`
@ -121,7 +89,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -162,7 +130,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -206,7 +174,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -247,7 +215,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -288,7 +256,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -335,7 +303,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -382,7 +350,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -429,7 +397,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -481,7 +449,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -538,7 +506,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -590,7 +558,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -767,7 +735,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -808,7 +776,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -852,7 +820,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -893,7 +861,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -934,7 +902,7 @@ def something_else():
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -981,7 +949,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1028,7 +996,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1075,7 +1043,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1127,7 +1095,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1184,7 +1152,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1236,7 +1204,7 @@ def something_else():
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[T_Handler]]`: 事件处理函数列表
* `handlers: Optional[List[Union[T_Handler, Handler]]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
@ -1442,22 +1410,6 @@ def something_else():
## `export()`
* **说明**
获取插件的导出内容对象
* **返回**
* `Export`
## `require(name)`
@ -1478,3 +1430,51 @@ def something_else():
* `Optional[Export]`
## _class_ `Export`
基类:`dict`
* **说明**
插件导出内容以使得其他插件可以获得。
* **示例**
```python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
```
## `export()`
* **说明**
获取插件的导出内容对象
* **返回**
* `Export`

View File

@ -11,6 +11,10 @@
- [群机器人概述](https://developers.dingtalk.com/document/app/overview-of-group-robots)
- [开发企业内部机器人](https://developers.dingtalk.com/document/app/develop-enterprise-internal-robots)
钉钉官方机器人教程Java
- [开发一个钉钉机器人](https://developers.dingtalk.com/document/tutorial/create-a-robot)
## 安装 NoneBot 钉钉 适配器
```bash
@ -93,6 +97,58 @@ async def raw_handler(bot: DingBot, event: MessageEvent):
其他消息格式请查看 [钉钉适配器的 MessageSegment](https://github.com/nonebot/nonebot2/blob/dev/nonebot/adapters/ding/message.py#L8),里面封装了很多有关消息的方法,比如 `code`、`image`、`feedCard` 等。
## 发送到特定群聊
钉钉也支持通过 Webhook 的方式直接将消息推送到某个群聊([参考链接](https://developers.dingtalk.com/document/app/custom-robot-access/title-zob-eyu-qse)),你可以在机器人的设置中看到当前群的 Webhook 地址。
![机器人所在群的 Webhook 地址](./images/ding/webhook.png)
获取到Webhook地址后用户可以向这个地址发起HTTP POST 请求,即可实现给该钉钉群发送消息。
对于这种通过 Webhook 推送的消息钉钉需要开发者进行安全方面的设置目前有3种安全设置方式请根据需要选择一种如下
1. **自定义关键词:** 最多可以设置10个关键词消息中至少包含其中1个关键词才可以发送成功。
例如添加了一个自定义关键词:监控报警,则这个机器人所发送的消息,必须包含监控报警这个词,才能发送成功。
2. **加签:** 发送请求时带上验签的值,可以在机器人设置里看到密钥。
![加签密钥](./images/ding/jiaqian.png)
3. **IP地址** 设定后只有来自IP地址范围内的请求才会被正常处理。支持两种设置方式IP地址和IP地址段暂不支持IPv6地址白名单。
如果你选择 1/3 两种安全设置,你需要自己确认当前网络和发送的消息能被钉钉接受,然后使用 `bot.send` 的时候将 webhook 地址传入 webhook 参数即可。
如我设置了 `打卡` 为关键词:
```python
message = MessageSegment.text("打卡成功XXXXXX")
await hello.send(
message,
webhook=
"https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
)
```
对于第二种加签方式,你可以在 `bot.send` 的时候把 `secret` 参数传进去Nonebot 内部会自动帮你计算发送该消息的签名并发送,如:
这里的 `secret` 参数就是加签选项给出的那个密钥。
```python
message = MessageSegment.raw({
"msgtype": "text",
"text": {
"content": 'hello from webhook,一定要注意安全方式的鉴权哦,否则可能发送失败的'
},
})
message += MessageSegment.atDingtalkIds(event.senderId)
await hello.send(
message,
webhook="https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
secret="SECXXXXXXXXXXXXXXXXXXXXXXXXX",
)
```
然后就可以发送成功了。
![测试 Webhook 发送](images/ding/test_webhook.png)
## 创建机器人并连接
在钉钉官方文档 [「开发企业内部机器人 -> 步骤一:创建机器人应用」](https://developers.dingtalk.com/document/app/develop-enterprise-internal-robots/title-ufs-4gh-poh) 中有详细介绍,这里就省去创建的步骤,介绍一下如何连接上程序。

View File

@ -4,6 +4,5 @@
- 请千万注意事件处理器的优先级设定
- 在匹配规则中请勿使用耗时极长的函数
- 同一个用户可以**跨群**(**私聊**)继续他的事件处理(除非做出权限限制,将在后续介绍)
如果「指南」还不能满足你,前往 [进阶](../advanced/README.md) 查看更多的功能信息。

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -68,6 +68,7 @@ pip install . # 不推荐
```
## 安装适配器
适配器可以通过 `nb-cli` 在创建项目时根据你的选择自动安装,也可以自行使用 `pip` 安装
```bash
@ -99,6 +100,7 @@ nb plugin install xxx
- [NoneBot-Plugin-Docs](https://github.com/nonebot/nonebot2/tree/master/packages/nonebot-plugin-docs) 离线文档插件
- [NoneBot-Plugin-Test](https://github.com/nonebot/plugin-test) 本地机器人测试前端插件
- [NoneBot-Plugin-APScheduler](https://github.com/nonebot/plugin-apscheduler) 定时任务插件
- [NoneBot-Plugin-LocalStore](https://github.com/nonebot/plugin-localstore) 本地数据文件存储插件
- [NoneBot-Plugin-Sentry](https://github.com/cscs181/QQ-GitHub-Bot/tree/master/src/plugins/nonebot_plugin_sentry) Sentry 在线日志分析插件
- [NoneBot-Plugin-Status](https://github.com/cscs181/QQ-GitHub-Bot/tree/master/src/plugins/nonebot_plugin_status) 服务器状态查看插件

View File

@ -7,6 +7,7 @@ NoneBot Api Reference
- `nonebot.plugin <plugin.html>`_
- `nonebot.message <message.html>`_
- `nonebot.matcher <matcher.html>`_
- `nonebot.handler <handler.html>`_
- `nonebot.rule <rule.html>`_
- `nonebot.permission <permission.html>`_
- `nonebot.log <log.html>`_

13
docs_build/handler.rst Normal file
View File

@ -0,0 +1,13 @@
---
contentSidebar: true
sidebarDepth: 0
---
NoneBot.handler 模块
====================
.. automodule:: nonebot.handler
:members:
:private-members:
:special-members: __init__
:show-inheritance:

View File

@ -10,3 +10,8 @@ NoneBot.plugin 模块
:members:
:show-inheritance:
:special-members: __init__
.. automodule:: nonebot.plugin.export
:members:
:show-inheritance:
:special-members: __init__

View File

@ -6,21 +6,30 @@
"""
import abc
import asyncio
from copy import copy
from typing_extensions import Literal
from functools import reduce, partial
from dataclasses import dataclass, field
from typing import Any, Dict, Union, TypeVar, Mapping, Optional, Callable, Iterable, Iterator, Awaitable, TYPE_CHECKING
from typing import (Any, Set, Dict, Union, TypeVar, Mapping, Optional, Iterable,
Protocol, Awaitable, TYPE_CHECKING)
from pydantic import BaseModel
from nonebot.log import logger
from nonebot.utils import DataclassEncoder
from nonebot.typing import T_CallingAPIHook
if TYPE_CHECKING:
from nonebot.config import Config
from nonebot.drivers import Driver, WebSocket
class _ApiCall(Protocol):
def __call__(self, **kwargs: Any) -> Awaitable[Any]:
...
class Bot(abc.ABC):
"""
Bot 基类用于处理上报消息并提供 API 调用接口
@ -30,6 +39,11 @@ class Bot(abc.ABC):
"""Driver 对象"""
config: "Config"
"""Config 配置对象"""
_call_api_hook: Set[T_CallingAPIHook] = set()
"""
:类型: ``Set[T_CallingAPIHook]``
:说明: call_api 时执行的函数
"""
@abc.abstractmethod
def __init__(self,
@ -51,7 +65,7 @@ class Bot(abc.ABC):
self.websocket = websocket
"""Websocket 连接对象"""
def __getattr__(self, name: str) -> Callable[..., Awaitable[Any]]:
def __getattr__(self, name: str) -> _ApiCall:
return partial(self.call_api, name)
@property
@ -73,7 +87,7 @@ class Bot(abc.ABC):
@classmethod
@abc.abstractmethod
async def check_permission(cls, driver: "Driver", connection_type: str,
headers: dict, body: Optional[dict]) -> str:
headers: dict, body: Optional[bytes]) -> str:
"""
:说明:
@ -84,7 +98,7 @@ class Bot(abc.ABC):
* ``driver: Driver``: Driver 对象
* ``connection_type: str``: 连接类型
* ``headers: dict``: 请求头
* ``body: Optional[dict]``: 请求数据WebSocket 连接该部分为
* ``body: Optional[bytes]``: 请求数据WebSocket 连接该部分为 None
:返回:
@ -110,7 +124,20 @@ class Bot(abc.ABC):
raise NotImplementedError
@abc.abstractmethod
async def call_api(self, api: str, **data):
async def _call_api(self, api: str, **data) -> Any:
"""
:说明:
``adapter`` 实际调用 api 的逻辑实现函数实现该方法以调用 api
:参数:
* ``api: str``: API 名称
* ``**data``: API 数据
"""
raise NotImplementedError
async def call_api(self, api: str, **data: Any) -> Any:
"""
:说明:
@ -119,6 +146,7 @@ class Bot(abc.ABC):
:参数:
* ``api: str``: API 名称
* ``self_id: Optional[str]``: 指定调用 API 的机器人
* ``**data``: API 数据
:示例:
@ -128,11 +156,28 @@ class Bot(abc.ABC):
await bot.call_api("send_msg", message="hello world")
await bot.send_msg(message="hello world")
"""
raise NotImplementedError
coros = list(map(lambda x: x(self, api, data), self._call_api_hook))
if coros:
try:
logger.debug("Running CallingAPI hooks...")
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
"Running cancelled!</bg #f8bbd0></r>")
if "self_id" in data:
self_id = data.pop("self_id")
if self_id:
bot = self.driver.bots[str(self_id)]
return await bot._call_api(api, **data)
return await self._call_api(api, **data)
@abc.abstractmethod
async def send(self, event: "Event",
message: Union[str, "Message", "MessageSegment"], **kwargs):
async def send(self, event: "Event", message: Union[str, "Message",
"MessageSegment"],
**kwargs) -> Any:
"""
:说明:
@ -146,6 +191,11 @@ class Bot(abc.ABC):
"""
raise NotImplementedError
@classmethod
def on_calling_api(cls, func: T_CallingAPIHook) -> T_CallingAPIHook:
cls._call_api_hook.add(func)
return func
T_Message = TypeVar("T_Message", bound="Message")
T_MessageSegment = TypeVar("T_MessageSegment", bound="MessageSegment")

View File

@ -16,7 +16,7 @@ from typing import List, Optional, Callable
import uvicorn
from pydantic import BaseSettings
from fastapi.responses import Response
from fastapi import Body, status, Request, FastAPI, HTTPException
from fastapi import status, Request, FastAPI, HTTPException
from starlette.websockets import WebSocketDisconnect, WebSocket as FastAPIWebSocket
from nonebot.log import logger
@ -177,11 +177,11 @@ class Driver(BaseDriver):
**kwargs)
@overrides(BaseDriver)
async def _handle_http(self,
adapter: str,
request: Request,
data: dict = Body(...)):
if not isinstance(data, dict):
async def _handle_http(self, adapter: str, request: Request):
data = await request.body()
data_dict = json.loads(data.decode())
if not isinstance(data_dict, dict):
logger.warning("Data received is invalid")
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
@ -208,7 +208,7 @@ class Driver(BaseDriver):
bot = BotClass("http", x_self_id)
asyncio.create_task(bot.handle_message(data))
asyncio.create_task(bot.handle_message(data_dict))
return Response("", 204)
@overrides(BaseDriver)
@ -234,8 +234,9 @@ class Driver(BaseDriver):
return
if x_self_id in self._clients:
logger.warning("There's already a reverse websocket connection, "
f"<y>{adapter.upper()} Bot {x_self_id}</y> ignored.")
logger.opt(colors=True).warning(
"There's already a reverse websocket connection, "
f"<y>{adapter.upper()} Bot {x_self_id}</y> ignored.")
await ws.close(code=status.WS_1008_POLICY_VIOLATION)
return

193
nonebot/handler.py Normal file
View File

@ -0,0 +1,193 @@
"""
事件处理函数
===========
该模块实现事件处理函数的封装以实现动态参数等功能
"""
import inspect
from typing import Any, List, Dict, Type, Union, Optional, TYPE_CHECKING
from typing import ForwardRef, _eval_type # type: ignore
from nonebot.log import logger
from nonebot.typing import T_Handler, T_State
if TYPE_CHECKING:
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
class HandlerMeta(type):
if TYPE_CHECKING:
func: T_Handler
signature: inspect.Signature
bot_type: Type["Bot"]
event_type: Optional[Type["Event"]]
state_type: Optional[T_State]
matcher_type: Optional[Type["Matcher"]]
def __repr__(self) -> str:
return (f"<Handler {self.func.__name__}(bot: {self.bot_type}, "
f"event: {self.event_type}, state: {self.state_type}, "
f"matcher: {self.matcher_type})>")
def __str__(self) -> str:
return repr(self)
class Handler(metaclass=HandlerMeta):
"""事件处理函数类"""
def __init__(self, func: T_Handler):
"""装饰事件处理函数以便根据动态参数运行"""
self.func: T_Handler = func
"""
:类型: ``T_Handler``
:说明: 事件处理函数
"""
self.signature: inspect.Signature = self.get_signature()
"""
:类型: ``inspect.Signature``
:说明: 事件处理函数签名
"""
def __repr__(self) -> str:
return (f"<Handler {self.func.__name__}(bot: {self.bot_type}, "
f"event: {self.event_type}, state: {self.state_type}, "
f"matcher: {self.matcher_type})>")
def __str__(self) -> str:
return repr(self)
async def __call__(self, matcher: "Matcher", bot: "Bot", event: "Event",
state: T_State):
BotType = ((self.bot_type is not inspect.Parameter.empty) and
inspect.isclass(self.bot_type) and self.bot_type)
if BotType and not isinstance(bot, BotType):
logger.debug(
f"Matcher {matcher} bot type {type(bot)} not match annotation {BotType}, ignored"
)
return
EventType = ((self.event_type is not inspect.Parameter.empty) and
inspect.isclass(self.event_type) and self.event_type)
if EventType and not isinstance(event, EventType):
logger.debug(
f"Matcher {matcher} event type {type(event)} not match annotation {EventType}, ignored"
)
return
args = {"bot": bot, "event": event, "state": state, "matcher": matcher}
await self.func(
**{
k: v
for k, v in args.items()
if self.signature.parameters.get(k, None) is not None
})
@property
def bot_type(self) -> Union[Type["Bot"], inspect.Parameter.empty]:
"""
:类型: ``Union[Type["Bot"], inspect.Parameter.empty]``
:说明: 事件处理函数接受的 Bot 对象类型"""
return self.signature.parameters["bot"].annotation
@property
def event_type(
self) -> Optional[Union[Type["Event"], inspect.Parameter.empty]]:
"""
:类型: ``Optional[Union[Type[Event], inspect.Parameter.empty]]``
:说明: 事件处理函数接受的 event 类型 / 不需要 event 参数
"""
if "event" not in self.signature.parameters:
return None
return self.signature.parameters["event"].annotation
@property
def state_type(self) -> Optional[Union[T_State, inspect.Parameter.empty]]:
"""
:类型: ``Optional[Union[T_State, inspect.Parameter.empty]]``
:说明: 事件处理函数是否接受 state 参数
"""
if "state" not in self.signature.parameters:
return None
return self.signature.parameters["state"].annotation
@property
def matcher_type(
self) -> Optional[Union[Type["Matcher"], inspect.Parameter.empty]]:
"""
:类型: ``Optional[Union[Type["Matcher"], inspect.Parameter.empty]]``
:说明: 事件处理函数是否接受 matcher 参数
"""
if "matcher" not in self.signature.parameters:
return None
return self.signature.parameters["matcher"].annotation
def get_signature(self) -> inspect.Signature:
wrapped_signature = self._get_typed_signature()
signature = self._get_typed_signature(False)
self._check_params(signature)
self._check_bot_param(signature)
self._check_bot_param(wrapped_signature)
signature.parameters["bot"].replace(
annotation=wrapped_signature.parameters["bot"].annotation)
if "event" in wrapped_signature.parameters and "event" in signature.parameters:
signature.parameters["event"].replace(
annotation=wrapped_signature.parameters["event"].annotation)
return signature
def update_signature(
self, **kwargs: Union[None, Type["Bot"], Type["Event"], Type["Matcher"],
T_State, inspect.Parameter.empty]
) -> None:
params: List[inspect.Parameter] = []
for param in ["bot", "event", "state", "matcher"]:
sig = self.signature.parameters.get(param, None)
if param in kwargs:
sig = inspect.Parameter(param,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
annotation=kwargs[param])
if sig:
params.append(sig)
self.signature = inspect.Signature(params)
def _get_typed_signature(self,
follow_wrapped: bool = True) -> inspect.Signature:
signature = inspect.signature(self.func, follow_wrapped=follow_wrapped)
globalns = getattr(self.func, "__globals__", {})
typed_params = [
inspect.Parameter(
name=param.name,
kind=param.kind,
default=param.default,
annotation=param.annotation if follow_wrapped else
self._get_typed_annotation(param, globalns),
) for param in signature.parameters.values()
]
typed_signature = inspect.Signature(typed_params)
return typed_signature
def _get_typed_annotation(self, param: inspect.Parameter,
globalns: Dict[str, Any]) -> Any:
try:
if isinstance(param.annotation, str):
return _eval_type(ForwardRef(param.annotation), globalns,
globalns)
else:
return param.annotation
except Exception:
return param.annotation
def _check_params(self, signature: inspect.Signature):
if not set(signature.parameters.keys()) <= {
"bot", "event", "state", "matcher"
}:
raise ValueError(
"Handler param names must in `bot`/`event`/`state`/`matcher`")
def _check_bot_param(self, signature: inspect.Signature):
if not any(
param.name == "bot" for param in signature.parameters.values()):
raise ValueError("Handler missing parameter 'bot'")

View File

@ -77,8 +77,8 @@ default_format = (
"<c><u>{name}</u></c> | "
# "<c>{function}:{line}</c>| "
"{message}")
logger.add(sys.stdout,
colorize=True,
diagnose=False,
filter=default_filter,
format=default_format)
logger_id = logger.add(sys.stdout,
colorize=True,
diagnose=False,
filter=default_filter,
format=default_format)

View File

@ -2,21 +2,24 @@
事件响应器
==========
该模块实现事件响应器的创建与运行并提供一些快捷方法来帮助用户更好的与机器人进行 对话
该模块实现事件响应器的创建与运行并提供一些快捷方法来帮助用户更好的与机器人进行对话
"""
import inspect
from functools import wraps
from datetime import datetime
from contextvars import ContextVar
from collections import defaultdict
from typing import Type, List, Dict, Union, Mapping, Iterable, Callable, Optional, NoReturn, TYPE_CHECKING
from typing import (Any, Type, List, Dict, Union, Mapping, Iterable, Callable,
Optional, NoReturn, TYPE_CHECKING)
from nonebot.rule import Rule
from nonebot.log import logger
from nonebot.handler import Handler
from nonebot.permission import Permission, USER
from nonebot.typing import T_State, T_StateFactory, T_Handler, T_ArgsParser, T_TypeUpdater, T_PermissionUpdater
from nonebot.exception import PausedException, RejectedException, FinishedException, StopPropagation
from nonebot.typing import (T_State, T_StateFactory, T_Handler, T_ArgsParser,
T_TypeUpdater, T_PermissionUpdater)
from nonebot.exception import (PausedException, RejectedException,
FinishedException, StopPropagation)
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event, Message, MessageSegment
@ -31,11 +34,21 @@ current_event: ContextVar = ContextVar("current_event")
class MatcherMeta(type):
if TYPE_CHECKING:
module: Optional[str]
type: str
rule: Rule
permission: Permission
handlers: List[T_Handler]
priority: int
block: bool
temp: bool
expire_time: Optional[datetime]
def __repr__(self) -> str:
return (f"<Matcher from {self.module or 'unknow'}, " # type: ignore
f"type={self.type}, priority={self.priority}, " # type: ignore
f"temp={self.temp}>") # type: ignore
return (f"<Matcher from {self.module or 'unknow'}, "
f"type={self.type}, priority={self.priority}, "
f"temp={self.temp}>")
def __str__(self) -> str:
return repr(self)
@ -64,9 +77,9 @@ class Matcher(metaclass=MatcherMeta):
:类型: ``Permission``
:说明: 事件响应器触发权限
"""
handlers: List[T_Handler] = []
handlers: List[Handler] = []
"""
:类型: ``List[T_Handler]``
:类型: ``List[Handler]``
:说明: 事件响应器拥有的事件处理函数列表
"""
priority: int = 1
@ -108,12 +121,12 @@ class Matcher(metaclass=MatcherMeta):
"""
_default_type_updater: Optional[T_TypeUpdater] = None
"""
:类型: ``Optional[T_ArgsParser]``
:类型: ``Optional[T_TypeUpdater]``
:说明: 事件响应器类型更新函数
"""
_default_permission_updater: Optional[T_PermissionUpdater] = None
"""
:类型: ``Optional[T_ArgsParser]``
:类型: ``Optional[T_PermissionUpdater]``
:说明: 事件响应器权限更新函数
"""
@ -127,14 +140,15 @@ class Matcher(metaclass=MatcherMeta):
f"priority={self.priority}, temp={self.temp}>")
def __str__(self) -> str:
return self.__repr__()
return repr(self)
@classmethod
def new(cls,
type_: str = "",
rule: Optional[Rule] = None,
permission: Optional[Permission] = None,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[Union[List[T_Handler], List[Handler],
List[Union[T_Handler, Handler]]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -178,7 +192,9 @@ class Matcher(metaclass=MatcherMeta):
"permission":
permission or Permission(),
"handlers": [
cls.process_handler(handler) for handler in handlers
handler
if isinstance(handler, Handler) else Handler(handler)
for handler in handlers
] if handlers else [],
"temp":
temp,
@ -284,27 +300,11 @@ class Matcher(metaclass=MatcherMeta):
cls._default_permission_updater = func
return func
@staticmethod
def process_handler(handler: T_Handler) -> T_Handler:
signature = inspect.signature(handler, follow_wrapped=False)
bot = signature.parameters.get("bot")
event = signature.parameters.get("event")
state = signature.parameters.get("state")
matcher = signature.parameters.get("matcher")
if not bot:
raise ValueError("Handler missing parameter 'bot'")
handler.__params__ = {
"bot": bot.annotation,
"event": event.annotation if event else None,
"state": T_State if state else None,
"matcher": matcher.annotation if matcher else None
}
return handler
@classmethod
def append_handler(cls, handler: T_Handler) -> None:
# Process handler first
cls.handlers.append(cls.process_handler(handler))
def append_handler(cls, handler: T_Handler) -> Handler:
handler_ = Handler(handler)
cls.handlers.append(handler_)
return handler_
@classmethod
def handle(cls) -> Callable[[T_Handler], T_Handler]:
@ -339,23 +339,19 @@ class Matcher(metaclass=MatcherMeta):
async def _receive(bot: "Bot", event: "Event") -> NoReturn:
raise PausedException
cls.process_handler(_receive)
if cls.handlers:
# 已有前置handlers则接受一条新的消息否则视为接收初始消息
cls.append_handler(_receive)
receive_handler = cls.append_handler(_receive)
else:
receive_handler = None
def _decorator(func: T_Handler) -> T_Handler:
cls.process_handler(func)
if not cls.handlers or cls.handlers[-1] is not func:
cls.append_handler(func)
_receive.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or _receive.__params__["event"]
})
func_handler = cls.append_handler(func)
if receive_handler:
receive_handler.update_signature(
bot=func_handler.bot_type,
event=func_handler.event_type)
return func
@ -416,42 +412,27 @@ class Matcher(metaclass=MatcherMeta):
else:
state[state["_current_key"]] = str(event.get_message())
cls.append_handler(_key_getter)
cls.append_handler(_key_parser)
getter_handler = cls.append_handler(_key_getter)
parser_handler = cls.append_handler(_key_parser)
def _decorator(func: T_Handler) -> T_Handler:
if not hasattr(cls.handlers[-1], "__wrapped__"):
cls.process_handler(func)
parser = cls.handlers.pop()
@wraps(func)
async def wrapper(bot: "Bot", event: "Event", state: T_State,
matcher: Matcher):
await matcher.run_handler(parser, bot, event, state)
await matcher.run_handler(func, bot, event, state)
await parser(matcher, bot, event, state)
await func_handler(matcher, bot, event, state)
if "_current_key" in state:
del state["_current_key"]
cls.append_handler(wrapper)
func_handler = cls.append_handler(wrapper)
wrapper.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or wrapper.__params__["event"]
})
_key_getter.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or wrapper.__params__["event"]
})
_key_parser.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or wrapper.__params__["event"]
})
getter_handler.update_signature(bot=func_handler.bot_type,
event=func_handler.event_type)
parser_handler.update_signature(bot=func_handler.bot_type,
event=func_handler.event_type)
return func
@ -459,7 +440,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod
async def send(cls, message: Union[str, "Message", "MessageSegment"],
**kwargs):
**kwargs) -> Any:
"""
:说明:
@ -470,9 +451,9 @@ class Matcher(metaclass=MatcherMeta):
* ``message: Union[str, Message, MessageSegment]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数请参考对应 adapter bot 对象 api
"""
bot = current_bot.get()
bot: "Bot" = current_bot.get()
event = current_event.get()
await bot.send(event=event, message=message, **kwargs)
return await bot.send(event=event, message=message, **kwargs)
@classmethod
async def finish(cls,
@ -545,32 +526,6 @@ class Matcher(metaclass=MatcherMeta):
"""
self.block = True
async def run_handler(self, handler: T_Handler, bot: "Bot", event: "Event",
state: T_State):
if not hasattr(handler, "__params__"):
self.process_handler(handler)
params = getattr(handler, "__params__")
BotType = ((params["bot"] is not inspect.Parameter.empty) and
inspect.isclass(params["bot"]) and params["bot"])
if BotType and not isinstance(bot, BotType):
logger.debug(
f"Matcher {self} bot type {type(bot)} not match annotation {BotType}, ignored"
)
return
EventType = ((params["event"] is not inspect.Parameter.empty) and
inspect.isclass(params["event"]) and params["event"])
if EventType and not isinstance(event, EventType):
logger.debug(
f"Matcher {self} event type {type(event)} not match annotation {EventType}, ignored"
)
return
args = {"bot": bot, "event": event, "state": state, "matcher": self}
await handler(
**{k: v for k, v in args.items() if params[k] is not None})
# 运行handlers
async def run(self, bot: "Bot", event: "Event", state: T_State):
b_t = current_bot.set(bot)
@ -583,20 +538,22 @@ class Matcher(metaclass=MatcherMeta):
for _ in range(len(self.handlers)):
handler = self.handlers.pop(0)
await self.run_handler(handler, bot, event, state_)
await handler(self, bot, event, state_)
except RejectedException:
self.handlers.insert(0, handler) # type: ignore
if self._default_type_updater:
type_ = await self._default_type_updater(
bot, event, state, self.type)
updater = self.__class__._default_type_updater
if updater:
type_ = await updater(bot, event, state, self.type)
else:
type_ = "message"
if self._default_permission_updater:
permission = await self._default_permission_updater(
bot, event, state, self.permission)
updater = self.__class__._default_permission_updater
if updater:
permission = await updater(bot, event, state, self.permission)
else:
permission = USER(event.get_session_id(), perm=self.permission)
Matcher.new(type_,
Rule(),
permission,
@ -609,16 +566,18 @@ class Matcher(metaclass=MatcherMeta):
expire_time=datetime.now() +
bot.config.session_expire_timeout)
except PausedException:
if self._default_type_updater:
type_ = await self._default_type_updater(
bot, event, state, self.type)
updater = self.__class__._default_type_updater
if updater:
type_ = await updater(bot, event, state, self.type)
else:
type_ = "message"
if self._default_permission_updater:
permission = await self._default_permission_updater(
bot, event, state, self.permission)
updater = self.__class__._default_permission_updater
if updater:
permission = await updater(bot, event, state, self.permission)
else:
permission = USER(event.get_session_id(), perm=self.permission)
Matcher.new(type_,
Rule(),
permission,

View File

@ -205,7 +205,8 @@ async def handle_event(bot: "Bot", event: "Event"):
coros = list(map(lambda x: x(bot, event, state), _event_preprocessors))
if coros:
try:
logger.debug("Running PreProcessors...")
if show_log:
logger.debug("Running PreProcessors...")
await asyncio.gather(*coros)
except IgnoredException:
logger.opt(colors=True).info(
@ -240,11 +241,16 @@ async def handle_event(bot: "Bot", event: "Event"):
if not break_flag:
break_flag = True
logger.debug("Stop event propagation")
elif isinstance(result, Exception):
logger.opt(colors=True, exception=result).error(
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
)
coros = list(map(lambda x: x(bot, event, state), _event_postprocessors))
if coros:
try:
logger.debug("Running PostProcessors...")
if show_log:
logger.debug("Running PostProcessors...")
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(

View File

@ -8,17 +8,20 @@ import re
import json
from types import ModuleType
from dataclasses import dataclass
from collections import defaultdict
from contextvars import Context, ContextVar, copy_context
from typing import Any, Set, List, Dict, Type, Tuple, Union, Optional, TYPE_CHECKING
import tomlkit
from nonebot.log import logger
from nonebot.matcher import Matcher
from nonebot.handler import Handler
from nonebot.permission import Permission
from nonebot.typing import T_State, T_StateFactory, T_Handler, T_RuleChecker
from nonebot.rule import Rule, startswith, endswith, keyword, command, shell_command, ArgumentParser, regex
from .manager import PluginManager
from .export import Export, export, _export
from .manager import PluginManager, _current_plugin
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
@ -30,52 +33,7 @@ plugins: Dict[str, "Plugin"] = {}
"""
PLUGIN_NAMESPACE = "nonebot.loaded_plugins"
_export: ContextVar["Export"] = ContextVar("_export")
_tmp_matchers: ContextVar[Set[Type[Matcher]]] = ContextVar("_tmp_matchers")
class Export(dict):
"""
:说明:
插件导出内容以使得其他插件可以获得
:示例:
.. code-block:: python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
"""
def __call__(self, func, **kwargs):
self[func.__name__] = func
self.update(kwargs)
return func
def __setitem__(self, key, value):
super().__setitem__(key,
Export(value) if isinstance(value, dict) else value)
def __setattr__(self, name, value):
self[name] = Export(value) if isinstance(value, dict) else value
def __getattr__(self, name):
if name not in self:
self[name] = Export()
return self[name]
_plugin_matchers: Dict[str, Set[Type[Matcher]]] = defaultdict(set)
@dataclass(eq=False)
@ -91,23 +49,31 @@ class Plugin(object):
- **类型**: ``ModuleType``
- **说明**: 插件模块对象
"""
matcher: Set[Type[Matcher]]
"""
- **类型**: ``Set[Type[Matcher]]``
- **说明**: 插件内定义的 ``Matcher``
"""
export: Export
"""
- **类型**: ``Export``
- **说明**: 插件内定义的导出内容
"""
@property
def matcher(self) -> Set[Type[Matcher]]:
"""
- **类型**: ``Set[Type[Matcher]]``
- **说明**: 插件内定义的 ``Matcher``
"""
return _plugin_matchers[self.name]
def _store_matcher(matcher: Type[Matcher]):
plugin_name = matcher.module.split(".", maxsplit=1)[0]
_plugin_matchers[plugin_name].add(matcher)
def on(type: str = "",
rule: Optional[Union[Rule, T_RuleChecker]] = None,
permission: Optional[Permission] = None,
*,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -123,7 +89,7 @@ def on(type: str = "",
* ``type: str``: 事件响应器类型
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -141,16 +107,17 @@ def on(type: str = "",
priority=priority,
block=block,
handlers=handlers,
module=_current_plugin.get(),
default_state=state,
default_state_factory=state_factory)
_tmp_matchers.get().add(matcher)
_store_matcher(matcher)
return matcher
def on_metaevent(
rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -164,7 +131,7 @@ def on_metaevent(
:参数:
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -182,16 +149,17 @@ def on_metaevent(
priority=priority,
block=block,
handlers=handlers,
module=_current_plugin.get(),
default_state=state,
default_state_factory=state_factory)
_tmp_matchers.get().add(matcher)
_store_matcher(matcher)
return matcher
def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = None,
permission: Optional[Permission] = None,
*,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = True,
@ -206,7 +174,7 @@ def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = None,
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -224,15 +192,16 @@ def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = None,
priority=priority,
block=block,
handlers=handlers,
module=_current_plugin.get(),
default_state=state,
default_state_factory=state_factory)
_tmp_matchers.get().add(matcher)
_store_matcher(matcher)
return matcher
def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -246,7 +215,7 @@ def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = None,
:参数:
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -264,15 +233,16 @@ def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = None,
priority=priority,
block=block,
handlers=handlers,
module=_current_plugin.get(),
default_state=state,
default_state_factory=state_factory)
_tmp_matchers.get().add(matcher)
_store_matcher(matcher)
return matcher
def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
*,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -286,7 +256,7 @@ def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
:参数:
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -304,9 +274,10 @@ def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
priority=priority,
block=block,
handlers=handlers,
module=_current_plugin.get(),
default_state=state,
default_state_factory=state_factory)
_tmp_matchers.get().add(matcher)
_store_matcher(matcher)
return matcher
@ -323,7 +294,7 @@ def on_startswith(msg: str,
* ``msg: str``: 指定消息开头内容
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -350,7 +321,7 @@ def on_endswith(msg: str,
* ``msg: str``: 指定消息结尾内容
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -377,7 +348,7 @@ def on_keyword(keywords: Set[str],
* ``keywords: Set[str]``: 关键词列表
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -408,7 +379,7 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -457,7 +428,7 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]],
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
* ``parser: Optional[ArgumentParser]``: ``nonebot.rule.ArgumentParser`` 对象
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -489,7 +460,7 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]],
def on_regex(pattern: str,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = None,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
**kwargs) -> Type[Matcher]:
"""
:说明:
@ -504,7 +475,7 @@ def on_regex(pattern: str,
* ``flags: Union[int, re.RegexFlag]``: 正则匹配标志
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -619,7 +590,7 @@ class MatcherGroup:
* ``type: str``: 事件响应器类型
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -645,7 +616,7 @@ class MatcherGroup:
:参数:
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -673,7 +644,7 @@ class MatcherGroup:
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -700,7 +671,7 @@ class MatcherGroup:
:参数:
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -727,7 +698,7 @@ class MatcherGroup:
:参数:
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -756,7 +727,7 @@ class MatcherGroup:
* ``msg: str``: 指定消息开头内容
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -785,7 +756,7 @@ class MatcherGroup:
* ``msg: str``: 指定消息结尾内容
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -814,7 +785,7 @@ class MatcherGroup:
* ``keywords: Set[str]``: 关键词列表
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -849,7 +820,7 @@ class MatcherGroup:
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -889,7 +860,7 @@ class MatcherGroup:
* ``parser: Optional[ArgumentParser]``: ``nonebot.rule.ArgumentParser`` 对象
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -927,7 +898,7 @@ class MatcherGroup:
* ``flags: Union[int, re.RegexFlag]``: 正则匹配标志
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``handlers: Optional[List[Union[T_Handler, Handler]]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器仅执行一次
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
@ -950,18 +921,14 @@ def _load_plugin(manager: PluginManager, plugin_name: str) -> Optional[Plugin]:
if plugin_name.startswith("_"):
return None
_tmp_matchers.set(set())
_export.set(Export())
if plugin_name in plugins:
return None
try:
module = manager.load_plugin(plugin_name)
for m in _tmp_matchers.get():
m.module = plugin_name
plugin = Plugin(plugin_name, module, _tmp_matchers.get(), _export.get())
plugin = Plugin(plugin_name, module,
getattr(module, "__export__", Export()))
plugins[plugin_name] = plugin
logger.opt(
colors=True).info(f'Succeeded to import "<y>{plugin_name}</y>"')
@ -1032,39 +999,11 @@ def load_all_plugins(module_path: Set[str],
- ``Set[Plugin]``
"""
def _load_plugin(plugin_name: str) -> Optional[Plugin]:
if plugin_name.startswith("_"):
return None
_tmp_matchers.set(set())
_export.set(Export())
if plugin_name in plugins:
return None
try:
module = manager.load_plugin(plugin_name)
for m in _tmp_matchers.get():
m.module = plugin_name
plugin = Plugin(plugin_name, module, _tmp_matchers.get(),
_export.get())
plugins[plugin_name] = plugin
logger.opt(
colors=True).info(f'Succeeded to import "<y>{plugin_name}</y>"')
return plugin
except Exception as e:
logger.opt(colors=True, exception=e).error(
f'<r><bg #f8bbd0>Failed to import "{plugin_name}"</bg #f8bbd0></r>'
)
return None
loaded_plugins = set()
manager = PluginManager(PLUGIN_NAMESPACE, module_path, plugin_dir)
for plugin_name in manager.list_plugins():
context: Context = copy_context()
result = context.run(_load_plugin, plugin_name)
result = context.run(_load_plugin, manager, plugin_name)
if result:
loaded_plugins.add(result)
return loaded_plugins
@ -1168,19 +1107,6 @@ def get_loaded_plugins() -> Set[Plugin]:
return set(plugins.values())
def export() -> Export:
"""
:说明:
获取插件的导出内容对象
:返回:
- ``Export``
"""
return _export.get()
def require(name: str) -> Optional[Export]:
"""
:说明:

View File

@ -4,6 +4,7 @@ from contextvars import ContextVar
from typing import Any, Set, List, Dict, Type, Tuple, Union, Optional
from nonebot.matcher import Matcher
from nonebot.handler import Handler
from nonebot.permission import Permission
from nonebot.rule import Rule, ArgumentParser
from nonebot.typing import T_State, T_StateFactory, T_Handler, T_RuleChecker
@ -37,7 +38,7 @@ def on(type: str = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -49,7 +50,7 @@ def on(type: str = ...,
def on_metaevent(
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
*,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -61,7 +62,7 @@ def on_metaevent(
def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -72,7 +73,7 @@ def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
*,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -83,7 +84,7 @@ def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
*,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -97,7 +98,7 @@ def on_startswith(
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
*,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -110,7 +111,7 @@ def on_endswith(msg: str,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
*,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -123,7 +124,7 @@ def on_keyword(keywords: Set[str],
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
*,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -137,7 +138,7 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
*,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -156,10 +157,10 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]],
def on_regex(pattern: str,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
*,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -216,7 +217,7 @@ class CommandGroup:
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -230,7 +231,7 @@ class CommandGroup:
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -246,7 +247,7 @@ class CommandGroup:
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
parser: Optional[ArgumentParser] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -262,7 +263,7 @@ class MatcherGroup:
type: str = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -274,7 +275,7 @@ class MatcherGroup:
type: str = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -286,7 +287,7 @@ class MatcherGroup:
self,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -299,7 +300,7 @@ class MatcherGroup:
*,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
permission: Optional[Permission] = None,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = True,
@ -311,7 +312,7 @@ class MatcherGroup:
self,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -323,7 +324,7 @@ class MatcherGroup:
self,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = None,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -337,7 +338,7 @@ class MatcherGroup:
*,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -351,7 +352,7 @@ class MatcherGroup:
*,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -365,7 +366,7 @@ class MatcherGroup:
*,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -380,7 +381,7 @@ class MatcherGroup:
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -396,7 +397,7 @@ class MatcherGroup:
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
@ -409,9 +410,9 @@ class MatcherGroup:
pattern: str,
flags: Union[int, re.RegexFlag] = 0,
*,
rule: Optional[Rule] = ...,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,

60
nonebot/plugin/export.py Normal file
View File

@ -0,0 +1,60 @@
from contextvars import ContextVar
_export: ContextVar["Export"] = ContextVar("_export")
class Export(dict):
"""
:说明:
插件导出内容以使得其他插件可以获得
:示例:
.. code-block:: python
nonebot.export().default = "bar"
@nonebot.export()
def some_function():
pass
# this doesn't work before python 3.9
# use
# export = nonebot.export(); @export.sub
# instead
# See also PEP-614: https://www.python.org/dev/peps/pep-0614/
@nonebot.export().sub
def something_else():
pass
"""
def __call__(self, func, **kwargs):
self[func.__name__] = func
self.update(kwargs)
return func
def __setitem__(self, key, value):
super().__setitem__(key,
Export(value) if isinstance(value, dict) else value)
def __setattr__(self, name, value):
self[name] = Export(value) if isinstance(value, dict) else value
def __getattr__(self, name):
if name not in self:
self[name] = Export()
return self[name]
def export() -> Export:
"""
:说明:
获取插件的导出内容对象
:返回:
- ``Export``
"""
return _export.get()

View File

@ -5,9 +5,15 @@ import importlib
from hashlib import md5
from types import ModuleType
from collections import Counter
from contextvars import ContextVar
from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder
from typing import Set, List, Optional, Iterable
from importlib.machinery import PathFinder, SourceFileLoader
from .export import _export, Export
_current_plugin: ContextVar[Optional[str]] = ContextVar("_current_plugin",
default=None)
_internal_space = ModuleType(__name__ + "._internal")
_internal_space.__path__ = [] # type: ignore
@ -138,10 +144,12 @@ class PluginManager:
def load_plugin(self, name) -> ModuleType:
if name in self.plugins:
return importlib.import_module(name)
with self:
return importlib.import_module(name)
if "." in name:
raise ValueError("Plugin name cannot contain '.'")
with self:
return importlib.import_module(f"{self.namespace}.{name}")
@ -149,14 +157,15 @@ class PluginManager:
return [self.load_plugin(name) for name in self.list_plugins()]
def _rewrite_module_name(self, module_name) -> Optional[str]:
if module_name == self.namespace:
return self.internal_module.__name__
elif module_name.startswith(self.namespace + "."):
prefix = f"{self.internal_module.__name__}."
if module_name.startswith(self.namespace + "."):
path = module_name.split(".")
length = self.namespace.count(".") + 1
return f"{self.internal_module.__name__}.{'.'.join(path[length:])}"
return f"{prefix}{'.'.join(path[length:])}"
elif module_name in self.plugins or module_name.startswith(prefix):
return module_name
elif module_name in self.search_plugins():
return f"{self.internal_module.__name__}.{module_name}"
return f"{prefix}{module_name}"
return None
@ -169,13 +178,52 @@ class PluginFinder(MetaPathFinder):
manager = _manager_stack[index]
newname = manager._rewrite_module_name(fullname)
if newname:
spec = PathFinder.find_spec(newname,
list(manager.search_path),
target)
spec = PathFinder.find_spec(
newname, [*manager.search_path, *(path or [])], target)
if spec:
spec.loader = PluginLoader(manager, newname,
spec.origin)
return spec
index -= 1
return None
class PluginLoader(SourceFileLoader):
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
self.manager = manager
self.loaded = False
self._plugin_token = None
self._export_token = None
super().__init__(fullname, path)
def create_module(self, spec) -> Optional[ModuleType]:
if self.name in sys.modules:
self.loaded = True
return sys.modules[self.name]
prefix = self.manager.internal_module.__name__
plugin_name = self.name[len(prefix):] if self.name.startswith(
prefix) else self.name
self._plugin_token = _current_plugin.set(plugin_name.lstrip("."))
self._export_token = _export.set(Export())
# return None to use default module creation
return super().create_module(spec)
def exec_module(self, module: ModuleType) -> None:
if self.loaded:
return
# really need?
# setattr(module, "__manager__", self.manager)
if self._export_token:
setattr(module, "__export__", _export.get())
super().exec_module(module)
if self._plugin_token:
_current_plugin.reset(self._plugin_token)
if self._export_token:
_export.reset(self._export_token)
return
sys.meta_path.insert(0, PluginFinder())

View File

@ -17,7 +17,7 @@ async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
current_event_id = id(event)
event_id = _running_matcher.get(session_id, None)
if event_id and event_id != current_event_id:
raise IgnoredException("Annother matcher running")
raise IgnoredException("Another matcher running")
_running_matcher[session_id] = current_event_id

View File

@ -287,10 +287,15 @@ class ArgumentParser(ArgParser):
"""
def _print_message(self, message, file=None):
pass
old_message: str = getattr(self, "message", "")
if old_message:
old_message += "\n"
old_message += message
setattr(self, "message", old_message)
def exit(self, status=0, message=None):
raise ParserExit(status=status, message=message)
raise ParserExit(status=status,
message=message or getattr(self, "message", None))
def parse_args(self,
args: Optional[Sequence[str]] = None,

View File

@ -71,6 +71,7 @@ T_WebSocketDisconnectionHook = Callable[["Bot"], Awaitable[None]]
WebSocket 连接断开时执行的函数
"""
T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[None]]
T_EventPreProcessor = Callable[["Bot", "Event", T_State], Awaitable[None]]
"""

View File

@ -52,8 +52,12 @@ async def _check_reply(bot: "Bot", event: "Event"):
except ValueError:
return
msg_seg = event.message[index]
event.reply = Reply.parse_obj(await
bot.get_msg(message_id=msg_seg.data["id"]))
try:
event.reply = Reply.parse_obj(await
bot.get_msg(message_id=msg_seg.data["id"]
))
except Exception as e:
log("WARNING", f"Error when getting message reply info: {repr(e)}", e)
# ensure string comparation
if str(event.reply.sender.user_id) == str(event.self_id):
event.to_me = True
@ -244,7 +248,7 @@ class Bot(BaseBot):
@classmethod
@overrides(BaseBot)
async def check_permission(cls, driver: "Driver", connection_type: str,
headers: dict, body: Optional[dict]) -> str:
headers: dict, body: Optional[bytes]) -> str:
"""
:说明:
@ -271,14 +275,13 @@ class Bot(BaseBot):
if not x_signature:
log("WARNING", "Missing Signature Header")
raise RequestDenied(401, "Missing Signature")
sig = hmac.new(secret.encode("utf-8"),
json.dumps(body).encode(), "sha1").hexdigest()
sig = hmac.new(secret.encode("utf-8"), body, "sha1").hexdigest()
if x_signature != "sha1=" + sig:
log("WARNING", "Signature Header is invalid")
raise RequestDenied(403, "Signature is invalid")
access_token = cqhttp_config.access_token
if access_token and access_token != token:
if access_token and access_token != token and connection_type == "websocket":
log(
"WARNING", "Authorization Header is invalid"
if token else "Missing Authorization Header")
@ -329,32 +332,7 @@ class Bot(BaseBot):
)
@overrides(BaseBot)
async def call_api(self, api: str, **data) -> Any:
"""
:说明:
调用 CQHTTP 协议 API
:参数:
* ``api: str``: API 名称
* ``**data: Any``: API 参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
if "self_id" in data:
self_id = data.pop("self_id")
if self_id:
bot = self.driver.bots[str(self_id)]
return await bot.call_api(api, **data)
async def _call_api(self, api: str, **data) -> Any:
log("DEBUG", f"Calling API <y>{api}</y>")
if self.connection_type == "websocket":
seq = ResultStore.get_seq()
@ -397,6 +375,29 @@ class Bot(BaseBot):
except httpx.HTTPError:
raise NetworkError("HTTP request failed")
@overrides(BaseBot)
async def call_api(self, api: str, **data) -> Any:
"""
:说明:
调用 CQHTTP 协议 API
:参数:
* ``api: str``: API 名称
* ``**data: Any``: API 参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
return await super().call_api(api, **data)
@overrides(BaseBot)
async def send(self,
event: Event,

View File

@ -68,7 +68,8 @@ class Bot(BaseBot):
async def handle_message(self, message: dict):
...
async def call_api(self, api: str, **data) -> Any:
async def call_api(self, api: str, *, self_id: Optional[str],
**data) -> Any:
...
async def send(self, event: Event, message: Union[str, Message,

View File

@ -19,3 +19,4 @@ class Config(BaseModel):
class Config:
extra = "ignore"
allow_population_by_field_name = True

View File

@ -199,6 +199,10 @@ class GroupMessageEvent(MessageEvent):
if x.is_text() else f"<le>{escape_tag(str(x))}</le>",
self.message)) + '"')
@overrides(MessageEvent)
def get_session_id(self) -> str:
return f"group_{self.group_id}_{self.user_id}"
# Notice Events
class NoticeEvent(Event):

View File

@ -38,7 +38,8 @@ class MessageSegment(BaseMessageSegment):
@overrides(BaseMessageSegment)
def __radd__(self, other) -> "Message":
return Message(other) + self
return (MessageSegment.text(other)
if isinstance(other, str) else Message(other)) + self
@overrides(BaseMessageSegment)
def is_text(self) -> bool:
@ -211,6 +212,11 @@ class Message(BaseMessage):
CQHTTP 协议 Message 适配
"""
def __radd__(self, other: Union[str, MessageSegment,
"Message"]) -> "Message":
result = MessageSegment.text(other) if isinstance(other, str) else other
return super(Message, self).__radd__(result)
@staticmethod
@overrides(BaseMessage)
def _construct(

View File

@ -111,7 +111,7 @@ reference = "aliyun"
[[package]]
name = "httpx"
version = "0.17.0"
version = "0.17.1"
description = "The next generation HTTP client."
category = "main"
optional = false
@ -119,7 +119,7 @@ python-versions = ">=3.6"
[package.dependencies]
certifi = "*"
httpcore = ">=0.12.0,<0.13.0"
httpcore = ">=0.12.1,<0.13"
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
@ -167,7 +167,7 @@ reference = "aliyun"
[[package]]
name = "nonebot2"
version = "2.0.0-alpha.11"
version = "2.0.0-alpha.12"
description = "An asynchronous python bot framework."
category = "main"
optional = false
@ -226,8 +226,8 @@ reference = "aliyun"
[[package]]
name = "python-dotenv"
version = "0.15.0"
description = "Add .env support to your django/flask apps in development and deployments"
version = "0.16.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
python-versions = "*"
@ -418,7 +418,7 @@ reference = "aliyun"
[metadata]
lock-version = "1.1"
python-versions = "^3.7.3"
content-hash = "8110a56337b3ca1557700161e09ff2d74720b5897f0fac09c4ac5038495194d9"
content-hash = "b8ec196a78675b4098ab7509cbdbd311ffcbcf1ce8b625c589f1e95596801c71"
[metadata.files]
certifi = [
@ -460,8 +460,8 @@ httptools = [
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"},
]
httpx = [
{file = "httpx-0.17.0-py3-none-any.whl", hash = "sha256:fe19522f7b0861a1f6ac83306360bb5b7fb1ed64633a1a04a33f04102a1bea60"},
{file = "httpx-0.17.0.tar.gz", hash = "sha256:4f7ab2fef7f929c5531abd4f413b41ce2c820e3202f2eeee498f2d92b6849f8d"},
{file = "httpx-0.17.1-py3-none-any.whl", hash = "sha256:d379653bd457e8257eb0df99cb94557e4aac441b7ba948e333be969298cac272"},
{file = "httpx-0.17.1.tar.gz", hash = "sha256:cc2a55188e4b25272d2bcd46379d300f632045de4377682aa98a8a6069d55967"},
]
idna = [
{file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"},
@ -500,8 +500,8 @@ pygtrie = [
{file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"},
]
python-dotenv = [
{file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"},
{file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"},
{file = "python-dotenv-0.16.0.tar.gz", hash = "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4"},
{file = "python_dotenv-0.16.0-py2.py3-none-any.whl", hash = "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot-adapter-cqhttp"
version = "2.0.0a11.post2"
version = "2.0.0-alpha.12"
description = "OneBot(CQHTTP) adapter for nonebot2"
authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
@ -25,7 +25,7 @@ exclude = ["nonebot/__init__.py", "nonebot/adapters/__init__.py"]
[tool.poetry.dependencies]
python = "^3.7.3"
httpx = "^0.17.0"
nonebot2 = "^2.0.0-alpha.11"
nonebot2 = "^2.0.0-alpha.12"
[tool.poetry.dev-dependencies]
nonebot2 = { path = "../../", develop = true }

View File

@ -1,6 +1,8 @@
import hmac
import base64
import json
import urllib.parse
from datetime import datetime
import time
from typing import Any, Union, Optional, TYPE_CHECKING
import httpx
@ -10,7 +12,7 @@ from nonebot.message import handle_event
from nonebot.adapters import Bot as BaseBot
from nonebot.exception import RequestDenied
from .utils import log
from .utils import calc_hmac_base64, log
from .config import Config as DingConfig
from .message import Message, MessageSegment
from .exception import NetworkError, ApiNotAvailable, ActionFailed, SessionExpired
@ -20,7 +22,7 @@ if TYPE_CHECKING:
from nonebot.config import Config
from nonebot.drivers import Driver
SEND_BY_SESSION_WEBHOOK = "send_by_sessionWebhook"
SEND = "send"
class Bot(BaseBot):
@ -48,7 +50,7 @@ class Bot(BaseBot):
@classmethod
@overrides(BaseBot)
async def check_permission(cls, driver: "Driver", connection_type: str,
headers: dict, body: Optional[dict]) -> str:
headers: dict, body: Optional[bytes]) -> str:
"""
:说明:
@ -72,15 +74,13 @@ class Bot(BaseBot):
if not sign:
log("WARNING", "Missing Signature Header")
raise RequestDenied(400, "Missing `sign` Header")
string_to_sign = f"{timestamp}\n{secret}"
sig = hmac.new(secret.encode("utf-8"),
string_to_sign.encode("utf-8"), "sha256").digest()
if sign != base64.b64encode(sig).decode("utf-8"):
sign_base64 = calc_hmac_base64(str(timestamp), secret)
if sign != sign_base64.decode('utf-8'):
log("WARNING", "Signature Header is invalid")
raise RequestDenied(403, "Signature is invalid")
else:
log("WARNING", "Ding signature check ignored!")
return body["chatbotUserId"]
return json.loads(body.decode())["chatbotUserId"]
@overrides(BaseBot)
async def handle_message(self, message: dict):
@ -108,6 +108,66 @@ class Bot(BaseBot):
)
return
@overrides(BaseBot)
async def _call_api(self,
api: str,
event: Optional[MessageEvent] = None,
**data) -> Any:
if self.connection_type != "http":
log("ERROR", "Only support http connection.")
return
log("DEBUG", f"Calling API <y>{api}</y>")
params = {}
# 传入参数有 webhook则使用传入的 webhook
webhook = data.get("webhook")
if webhook:
secret = data.get("secret")
if secret:
# 有这个参数的时候再计算加签的值
timestamp = str(round(time.time() * 1000))
params["timestamp"] = timestamp
hmac_code_base64 = calc_hmac_base64(timestamp, secret)
sign = urllib.parse.quote_plus(hmac_code_base64)
params["sign"] = sign
else:
# webhook 不存在则使用 event 中的 sessionWebhook
if event:
# 确保 sessionWebhook 没有过期
if int(datetime.now().timestamp()) > int(
event.sessionWebhookExpiredTime / 1000):
raise SessionExpired
webhook = event.sessionWebhook
else:
raise ApiNotAvailable
headers = {}
message: Message = data.get("message", None)
if not message:
raise ValueError("Message not found")
try:
async with httpx.AsyncClient(headers=headers) as client:
response = await client.post(webhook,
params=params,
json=message._produce(),
timeout=self.config.api_timeout)
if 200 <= response.status_code < 300:
result = response.json()
if isinstance(result, dict):
if result.get("errcode") != 0:
raise ActionFailed(errcode=result.get("errcode"),
errmsg=result.get("errmsg"))
return result
raise NetworkError(f"HTTP request received unexpected "
f"status code: {response.status_code}")
except httpx.InvalidURL:
raise NetworkError("API root url invalid")
except httpx.HTTPError:
raise NetworkError("HTTP request failed")
@overrides(BaseBot)
async def call_api(self,
api: str,
@ -121,6 +181,7 @@ class Bot(BaseBot):
:参数:
* ``api: str``: API 名称
* ``event: Optional[MessageEvent]``: Event 对象
* ``**data: Any``: API 参数
:返回:
@ -132,59 +193,15 @@ class Bot(BaseBot):
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
if self.connection_type != "http":
log("ERROR", "Only support http connection.")
return
if "self_id" in data:
self_id = data.pop("self_id")
if self_id:
bot = self.driver.bots[str(self_id)]
return await bot.call_api(api, **data)
log("DEBUG", f"Calling API <y>{api}</y>")
if api == SEND_BY_SESSION_WEBHOOK:
if event:
# 确保 sessionWebhook 没有过期
if int(datetime.now().timestamp()) > int(
event.sessionWebhookExpiredTime / 1000):
raise SessionExpired
target = event.sessionWebhook
else:
raise ApiNotAvailable
headers = {}
message: Message = data.get("message", None)
if not message:
raise ValueError("Message not found")
try:
async with httpx.AsyncClient(headers=headers) as client:
response = await client.post(
target,
params={"access_token": self.ding_config.access_token},
json=message._produce(),
timeout=self.config.api_timeout)
if 200 <= response.status_code < 300:
result = response.json()
if isinstance(result, dict):
if result.get("errcode") != 0:
raise ActionFailed(errcode=result.get("errcode"),
errmsg=result.get("errmsg"))
return result
raise NetworkError(f"HTTP request received unexpected "
f"status code: {response.status_code}")
except httpx.InvalidURL:
raise NetworkError("API root url invalid")
except httpx.HTTPError:
raise NetworkError("HTTP request failed")
return await super().call_api(api, event=event, **data)
@overrides(BaseBot)
async def send(self,
event: MessageEvent,
message: Union[str, "Message", "MessageSegment"],
at_sender: bool = False,
webhook: Optional[str] = None,
secret: Optional[str] = None,
**kwargs) -> Any:
"""
:说明:
@ -196,6 +213,8 @@ class Bot(BaseBot):
* ``event: Event``: Event 对象
* ``message: Union[str, Message, MessageSegment]``: 要发送的消息
* ``at_sender: bool``: 是否 @ 事件主体
* ``webhook: Optional[str]``: 该条消息将调用的 webhook 地址不传则将使用 sessionWebhook若其也不存在该条消息不发送使用自定义 webhook 时注意你设置的安全方式如加关键词IP地址加签等等
* ``secret: Optional[str]``: 如果你使用自定义的 webhook 地址推荐使用加签方式对消息进行验证 `机器人安全设置页面加签一栏下面显示的SEC开头的字符串` 传入这个参数即可
* ``**kwargs``: 覆盖默认参数
:返回:
@ -213,6 +232,9 @@ class Bot(BaseBot):
at_sender = at_sender and bool(event.senderId)
params = {}
params["event"] = event
if webhook:
params["webhook"] = webhook
params["secret"] = secret
params.update(kwargs)
if at_sender and event.conversationType != ConversationType.private:
@ -222,4 +244,4 @@ class Bot(BaseBot):
else:
params["message"] = msg
return await self.call_api(SEND_BY_SESSION_WEBHOOK, **params)
return await self.call_api(SEND, **params)

View File

@ -17,3 +17,4 @@ class Config(BaseModel):
class Config:
extra = "ignore"
allow_population_by_field_name = True

View File

@ -143,3 +143,7 @@ class GroupMessageEvent(MessageEvent):
@overrides(MessageEvent)
def is_tome(self) -> bool:
return self.isInAtList
@overrides(MessageEvent)
def get_session_id(self) -> str:
return f"group_{self.conversationId}_{self.senderId}"

View File

@ -1,3 +1,17 @@
import hmac
import base64
import hashlib
from nonebot.utils import logger_wrapper
log = logger_wrapper("DING")
def calc_hmac_base64(timestamp: str, secret: str):
secret_enc = secret.encode('utf-8')
string_to_sign = '{}\n{}'.format(timestamp, secret)
string_to_sign_enc = string_to_sign.encode('utf-8')
hmac_code = hmac.new(secret_enc,
string_to_sign_enc,
digestmod=hashlib.sha256).digest()
return base64.b64encode(hmac_code)

View File

@ -111,7 +111,7 @@ reference = "aliyun"
[[package]]
name = "httpx"
version = "0.17.0"
version = "0.17.1"
description = "The next generation HTTP client."
category = "main"
optional = false
@ -119,7 +119,7 @@ python-versions = ">=3.6"
[package.dependencies]
certifi = "*"
httpcore = ">=0.12.0,<0.13.0"
httpcore = ">=0.12.1,<0.13"
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
@ -167,7 +167,7 @@ reference = "aliyun"
[[package]]
name = "nonebot2"
version = "2.0.0-alpha.11"
version = "2.0.0-alpha.12"
description = "An asynchronous python bot framework."
category = "main"
optional = false
@ -226,8 +226,8 @@ reference = "aliyun"
[[package]]
name = "python-dotenv"
version = "0.15.0"
description = "Add .env support to your django/flask apps in development and deployments"
version = "0.16.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
python-versions = "*"
@ -418,7 +418,7 @@ reference = "aliyun"
[metadata]
lock-version = "1.1"
python-versions = "^3.7.3"
content-hash = "8110a56337b3ca1557700161e09ff2d74720b5897f0fac09c4ac5038495194d9"
content-hash = "b8ec196a78675b4098ab7509cbdbd311ffcbcf1ce8b625c589f1e95596801c71"
[metadata.files]
certifi = [
@ -460,8 +460,8 @@ httptools = [
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"},
]
httpx = [
{file = "httpx-0.17.0-py3-none-any.whl", hash = "sha256:fe19522f7b0861a1f6ac83306360bb5b7fb1ed64633a1a04a33f04102a1bea60"},
{file = "httpx-0.17.0.tar.gz", hash = "sha256:4f7ab2fef7f929c5531abd4f413b41ce2c820e3202f2eeee498f2d92b6849f8d"},
{file = "httpx-0.17.1-py3-none-any.whl", hash = "sha256:d379653bd457e8257eb0df99cb94557e4aac441b7ba948e333be969298cac272"},
{file = "httpx-0.17.1.tar.gz", hash = "sha256:cc2a55188e4b25272d2bcd46379d300f632045de4377682aa98a8a6069d55967"},
]
idna = [
{file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"},
@ -500,8 +500,8 @@ pygtrie = [
{file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"},
]
python-dotenv = [
{file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"},
{file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"},
{file = "python-dotenv-0.16.0.tar.gz", hash = "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4"},
{file = "python_dotenv-0.16.0-py2.py3-none-any.whl", hash = "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot-adapter-ding"
version = "2.0.0a11.post2"
version = "2.0.0-alpha.12"
description = "Ding adapter for nonebot2"
authors = ["Artin <lengthmin@gmail.com>", "yanyongyu <yyy@nonebot.dev>"]
license = "MIT"
@ -25,7 +25,7 @@ exclude = ["nonebot/__init__.py", "nonebot/adapters/__init__.py"]
[tool.poetry.dependencies]
python = "^3.7.3"
httpx = "^0.17.0"
nonebot2 = "^2.0.0-alpha.11"
nonebot2 = "^2.0.0-alpha.12"
[tool.poetry.dev-dependencies]
nonebot2 = { path = "../../", develop = true }

View File

@ -178,7 +178,7 @@ class Bot(BaseBot):
@classmethod
@overrides(BaseBot)
async def check_permission(cls, driver: "Driver", connection_type: str,
headers: dict, body: Optional[dict]) -> str:
headers: dict, body: Optional[bytes]) -> str:
if connection_type == 'ws':
raise RequestDenied(
status_code=501,
@ -218,13 +218,17 @@ class Bot(BaseBot):
except Exception as e:
Log.error(f'Failed to handle message: {message}', e)
@overrides(BaseBot)
async def _call_api(self, api: str, **data) -> NoReturn:
raise NotImplementedError
@overrides(BaseBot)
async def call_api(self, api: str, **data) -> NoReturn:
"""
\:\:\: danger
由于Mirai的HTTP API特殊性, 该API暂时无法实现
\:\:\:
\:\:\: tip
你可以使用 ``MiraiBot.api`` 中提供的调用方法来代替
\:\:\:
@ -447,7 +451,7 @@ class Bot(BaseBot):
:说明:
使用此方法获取bot接收到的最老消息和最老各类事件
(不会从MiraiApiHttp消息记录中删除)
(不会从MiraiApiHttp消息记录中删除)
:参数:
@ -462,7 +466,7 @@ class Bot(BaseBot):
使用此方法获取bot接收到的最新消息和最新各类事件
(不会从MiraiApiHttp消息记录中删除)
:参数:
* ``count: int``: 获取消息和事件的数量
@ -599,7 +603,7 @@ class Bot(BaseBot):
"""
:说明:
使用此方法使Bot退出群聊
使用此方法使Bot退出群聊
:参数:

View File

@ -116,7 +116,8 @@ class WebsocketBot(Bot):
@classmethod
@overrides(Bot)
async def check_permission(cls, driver: "Driver", connection_type: str,
headers: dict, body: Optional[dict]) -> NoReturn:
headers: dict,
body: Optional[bytes]) -> NoReturn:
raise RequestDenied(
status_code=501,
reason=f'Connection {connection_type} not implented')
@ -127,7 +128,7 @@ class WebsocketBot(Bot):
"""
:说明:
注册该Adapter
注册该Adapter
:参数:

View File

@ -20,3 +20,4 @@ class Config(BaseModel):
class Config:
extra = Extra.ignore
allow_population_by_field_name = True

View File

@ -11,7 +11,7 @@ from nonebot.message import handle_event
from nonebot.utils import escape_tag, logger_wrapper
from .event import Event, GroupMessage, MessageEvent, MessageSource
from .message import MessageType
from .message import MessageType, MessageSegment
if TYPE_CHECKING:
from .bot import Bot
@ -64,7 +64,7 @@ class ActionFailed(exception.ActionFailed):
class InvalidArgument(exception.AdapterException):
"""
:说明:
调用API的参数出错
"""
@ -106,7 +106,7 @@ def argument_validation(function: _AnyCallable) -> _AnyCallable:
:说明:
通过函数签名中的类型注解来对传入参数进行运行时校验
会在参数出错时释放 ``InvalidArgument`` 异常
"""
function = validate_arguments(config={
@ -138,6 +138,8 @@ def process_at(bot: "Bot", event: GroupMessage) -> GroupMessage:
event.to_me = True
else:
event.message_chain.insert(0, at)
if not event.message_chain:
event.message_chain.append(MessageSegment.plain(''))
return event
@ -175,4 +177,4 @@ async def process_event(bot: "Bot", event: Event) -> None:
event = process_nick(bot, event)
event = process_at(bot, event)
event = process_reply(bot, event)
await handle_event(bot, event)
await handle_event(bot, event)

View File

@ -111,7 +111,7 @@ reference = "aliyun"
[[package]]
name = "httpx"
version = "0.17.0"
version = "0.17.1"
description = "The next generation HTTP client."
category = "main"
optional = false
@ -119,7 +119,7 @@ python-versions = ">=3.6"
[package.dependencies]
certifi = "*"
httpcore = ">=0.12.0,<0.13.0"
httpcore = ">=0.12.1,<0.13"
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
@ -167,7 +167,7 @@ reference = "aliyun"
[[package]]
name = "nonebot2"
version = "2.0.0-alpha.11"
version = "2.0.0-alpha.12"
description = "An asynchronous python bot framework."
category = "main"
optional = false
@ -226,8 +226,8 @@ reference = "aliyun"
[[package]]
name = "python-dotenv"
version = "0.15.0"
description = "Add .env support to your django/flask apps in development and deployments"
version = "0.16.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
python-versions = "*"
@ -418,7 +418,7 @@ reference = "aliyun"
[metadata]
lock-version = "1.1"
python-versions = "^3.7.3"
content-hash = "9eb7c46cddf1245508a34c00f5709d21415d02d7f8c356733cc20ad187f431f9"
content-hash = "5b8b3e27eccd897aa33fea94ba813b7d601c5656d52efed4401b398f6d68a677"
[metadata.files]
certifi = [
@ -460,8 +460,8 @@ httptools = [
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"},
]
httpx = [
{file = "httpx-0.17.0-py3-none-any.whl", hash = "sha256:fe19522f7b0861a1f6ac83306360bb5b7fb1ed64633a1a04a33f04102a1bea60"},
{file = "httpx-0.17.0.tar.gz", hash = "sha256:4f7ab2fef7f929c5531abd4f413b41ce2c820e3202f2eeee498f2d92b6849f8d"},
{file = "httpx-0.17.1-py3-none-any.whl", hash = "sha256:d379653bd457e8257eb0df99cb94557e4aac441b7ba948e333be969298cac272"},
{file = "httpx-0.17.1.tar.gz", hash = "sha256:cc2a55188e4b25272d2bcd46379d300f632045de4377682aa98a8a6069d55967"},
]
idna = [
{file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"},
@ -500,8 +500,8 @@ pygtrie = [
{file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"},
]
python-dotenv = [
{file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"},
{file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"},
{file = "python-dotenv-0.16.0.tar.gz", hash = "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4"},
{file = "python_dotenv-0.16.0-py2.py3-none-any.whl", hash = "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot-adapter-mirai"
version = "2.0.0a11.post2"
version = "2.0.0-alpha.12"
description = "Mirai Api HTTP adapter for nonebot2"
authors = ["Mix <admin@yami.im>", "yanyongyu <yyy@nonebot.dev>"]
license = "AGPL-3.0-or-later"
@ -26,7 +26,7 @@ exclude = ["nonebot/__init__.py", "nonebot/adapters/__init__.py"]
python = "^3.7.3"
httpx = "^0.17.0"
websockets = "^8.1"
nonebot2 = "^2.0.0-alpha.11"
nonebot2 = "^2.0.0-alpha.12"
[tool.poetry.dev-dependencies]
nonebot2 = { path = "../../", develop = true }

View File

@ -4,6 +4,17 @@ sidebar: auto
# 更新日志
## v2.0.0a12
- 分离 `handler``matcher`
- 修复 `cqhttp` secret 校验出错
- 修复 `pydantic 1.8` 导致的 `alias` 问题
- 修改 `cqhttp` `ding` `session id`,不再允许跨群
- 修改 `shell_command` 存储 message
- 修复 `cqhttp` 检查 reply 失败退出
- 新增 `call_api` hook 接口
- 优化 `import hook`
## v2.0.0a11
- 修改 `nonebot` 项目结构,分离所有 `adapter`

150
poetry.lock generated
View File

@ -3,7 +3,7 @@ name = "aiofiles"
version = "0.6.0"
description = "File support for asyncio."
category = "main"
optional = true
optional = false
python-versions = "*"
[package.source]
@ -235,7 +235,7 @@ reference = "aliyun"
[[package]]
name = "httpx"
version = "0.17.0"
version = "0.17.1"
description = "The next generation HTTP client."
category = "dev"
optional = false
@ -243,7 +243,7 @@ python-versions = ">=3.6"
[package.dependencies]
certifi = "*"
httpcore = ">=0.12.0,<0.13.0"
httpcore = ">=0.12.1,<0.13"
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
@ -389,7 +389,7 @@ reference = "aliyun"
[[package]]
name = "nonebot-adapter-cqhttp"
version = "2.0.0-alpha.11"
version = "2.0.0-alpha.12"
description = "OneBot(CQHTTP) adapter for nonebot2"
category = "dev"
optional = false
@ -398,7 +398,7 @@ develop = true
[package.dependencies]
httpx = "^0.17.0"
nonebot2 = "^2.0.0-alpha.11"
nonebot2 = "^2.0.0-alpha.12"
[package.source]
type = "directory"
@ -406,7 +406,7 @@ url = "packages/nonebot-adapter-cqhttp"
[[package]]
name = "nonebot-adapter-ding"
version = "2.0.0-alpha.11"
version = "2.0.0-alpha.12"
description = "Ding adapter for nonebot2"
category = "dev"
optional = false
@ -415,7 +415,7 @@ develop = true
[package.dependencies]
httpx = "^0.17.0"
nonebot2 = "^2.0.0-alpha.11"
nonebot2 = "^2.0.0-alpha.12"
[package.source]
type = "directory"
@ -423,7 +423,7 @@ url = "packages/nonebot-adapter-ding"
[[package]]
name = "nonebot-adapter-mirai"
version = "2.0.0-alpha.11"
version = "2.0.0-alpha.12"
description = "Mirai Api HTTP adapter for nonebot2"
category = "dev"
optional = false
@ -432,13 +432,31 @@ develop = true
[package.dependencies]
httpx = "^0.17.0"
nonebot2 = "^2.0.0-alpha.11"
nonebot2 = "^2.0.0-alpha.12"
websockets = "^8.1"
[package.source]
type = "directory"
url = "packages/nonebot-adapter-mirai"
[[package]]
name = "nonebot-plugin-test"
version = "0.2.0"
description = "Test frontend for nonebot v2+"
category = "dev"
optional = false
python-versions = ">=3.7,<4.0"
[package.dependencies]
aiofiles = ">=0.6.0,<0.7.0"
nonebot2 = ">=2.0.0-alpha.9,<3.0.0"
python-socketio = ">=4.6.1,<5.0.0"
[package.source]
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
[[package]]
name = "packaging"
version = "20.9"
@ -491,14 +509,14 @@ reference = "aliyun"
[[package]]
name = "pydash"
version = "4.9.3"
version = "5.0.0"
description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library."
category = "dev"
optional = false
python-versions = "*"
python-versions = ">=3.6"
[package.extras]
dev = ["coverage", "docformatter", "flake8", "invoke", "mock", "pylint", "pytest", "pytest-cov", "pytest-flake8", "pytest-pylint", "sphinx", "sphinx-rtd-theme", "tox", "twine", "wheel", "black", "flake8-black", "flake8-bugbear", "flake8-isort", "isort"]
dev = ["black", "coverage", "docformatter", "flake8", "flake8-black", "flake8-bugbear", "flake8-isort", "invoke", "isort", "pylint", "pytest", "pytest-cov", "pytest-flake8", "pytest-pylint", "sphinx", "sphinx-rtd-theme", "tox", "twine", "wheel"]
[package.source]
type = "legacy"
@ -507,7 +525,7 @@ reference = "aliyun"
[[package]]
name = "pygments"
version = "2.8.0"
version = "2.8.1"
description = "Pygments is a syntax highlighting package written in Python."
category = "dev"
optional = false
@ -546,8 +564,8 @@ reference = "aliyun"
[[package]]
name = "python-dotenv"
version = "0.15.0"
description = "Add .env support to your django/flask apps in development and deployments"
version = "0.16.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
python-versions = "*"
@ -560,6 +578,47 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
[[package]]
name = "python-engineio"
version = "3.14.2"
description = "Engine.IO server"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
six = ">=1.9.0"
[package.extras]
asyncio_client = ["aiohttp (>=3.4)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[package.source]
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
[[package]]
name = "python-socketio"
version = "4.6.1"
description = "Socket.IO server"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
python-engineio = ">=3.13.0,<4"
six = ">=1.9.0"
[package.extras]
asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[package.source]
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
[[package]]
name = "pytz"
version = "2021.1"
@ -655,6 +714,19 @@ type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
[[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[package.source]
type = "legacy"
url = "https://mirrors.aliyun.com/pypi/simple"
reference = "aliyun"
[[package]]
name = "sniffio"
version = "1.2.0"
@ -683,7 +755,7 @@ reference = "aliyun"
[[package]]
name = "sphinx"
version = "3.5.1"
version = "3.5.3"
description = "Python documentation generator"
category = "dev"
optional = false
@ -926,16 +998,16 @@ reference = "aliyun"
[[package]]
name = "urllib3"
version = "1.26.3"
version = "1.26.4"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
brotli = ["brotlipy (>=0.6.0)"]
[package.source]
type = "legacy"
@ -1083,7 +1155,7 @@ quart = ["Quart"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7.3"
content-hash = "f4e49d25ac5c37b3c7527935b52acf5d0d6d0261a645f4247120f9e28c5ca282"
content-hash = "9907ba758206e19d2c36493252f8b026db796ff4dbc67142824d86da31763919"
[metadata.files]
aiofiles = [
@ -1160,8 +1232,8 @@ httptools = [
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"},
]
httpx = [
{file = "httpx-0.17.0-py3-none-any.whl", hash = "sha256:fe19522f7b0861a1f6ac83306360bb5b7fb1ed64633a1a04a33f04102a1bea60"},
{file = "httpx-0.17.0.tar.gz", hash = "sha256:4f7ab2fef7f929c5531abd4f413b41ce2c820e3202f2eeee498f2d92b6849f8d"},
{file = "httpx-0.17.1-py3-none-any.whl", hash = "sha256:d379653bd457e8257eb0df99cb94557e4aac441b7ba948e333be969298cac272"},
{file = "httpx-0.17.1.tar.gz", hash = "sha256:cc2a55188e4b25272d2bcd46379d300f632045de4377682aa98a8a6069d55967"},
]
hypercorn = [
{file = "Hypercorn-0.11.2-py3-none-any.whl", hash = "sha256:8007c10f81566920f8ae12c0e26e146f94ca70506da964b5a727ad610aa1d821"},
@ -1248,6 +1320,10 @@ markupsafe = [
nonebot-adapter-cqhttp = []
nonebot-adapter-ding = []
nonebot-adapter-mirai = []
nonebot-plugin-test = [
{file = "nonebot-plugin-test-0.2.0.tar.gz", hash = "sha256:c9ee997c5c96160de4af02d10a7c6301b3fc4e942df7e70906df0534606ea23b"},
{file = "nonebot_plugin_test-0.2.0-py3-none-any.whl", hash = "sha256:75cd18cc282815a03250bb86c7d2a8d6a66a5064ac335bedc9a3e268a1e7dd13"},
]
packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
@ -1281,12 +1357,12 @@ pydantic = [
{file = "pydantic-1.8.1.tar.gz", hash = "sha256:26cf3cb2e68ec6c0cfcb6293e69fb3450c5fd1ace87f46b64f678b0d29eac4c3"},
]
pydash = [
{file = "pydash-4.9.3-py2.py3-none-any.whl", hash = "sha256:7851a2d749e70c02585ae4803b01c5e0f47b7ec9df9b84ccb16aac38cad2fdd2"},
{file = "pydash-4.9.3.tar.gz", hash = "sha256:d709e57b537b1aaf118f188da3ec6242a665090ecd7839b66f857ee3dc2bb006"},
{file = "pydash-5.0.0-py3-none-any.whl", hash = "sha256:0d87f879a3df4ad9389ab6d63c69eea078517d41541ddd5744cfcff3396e8543"},
{file = "pydash-5.0.0.tar.gz", hash = "sha256:845262df83b5411742e5f7f7dbfa5ed4d0ddac6d7d0a13c4375c6a3c40d4e8f4"},
]
pygments = [
{file = "Pygments-2.8.0-py3-none-any.whl", hash = "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"},
{file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"},
{file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"},
{file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"},
]
pygtrie = [
{file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"},
@ -1296,8 +1372,16 @@ pyparsing = [
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
python-dotenv = [
{file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"},
{file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"},
{file = "python-dotenv-0.16.0.tar.gz", hash = "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4"},
{file = "python_dotenv-0.16.0-py2.py3-none-any.whl", hash = "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec"},
]
python-engineio = [
{file = "python-engineio-3.14.2.tar.gz", hash = "sha256:eab4553f2804c1ce97054c8b22cf0d5a9ab23128075248b97e1a5b2f29553085"},
{file = "python_engineio-3.14.2-py2.py3-none-any.whl", hash = "sha256:5a9e6086d192463b04a1428ff1f85b6ba631bbb19d453b144ffc04f530542b84"},
]
python-socketio = [
{file = "python-socketio-4.6.1.tar.gz", hash = "sha256:cd1f5aa492c1eb2be77838e837a495f117e17f686029ebc03d62c09e33f4fa10"},
{file = "python_socketio-4.6.1-py2.py3-none-any.whl", hash = "sha256:5a21da53fdbdc6bb6c8071f40e13d100e0b279ad997681c2492478e06f370523"},
]
pytz = [
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
@ -1338,6 +1422,10 @@ rfc3986 = [
{file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"},
{file = "rfc3986-1.4.0.tar.gz", hash = "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d"},
]
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
sniffio = [
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
@ -1347,8 +1435,8 @@ snowballstemmer = [
{file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"},
]
sphinx = [
{file = "Sphinx-3.5.1-py3-none-any.whl", hash = "sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c"},
{file = "Sphinx-3.5.1.tar.gz", hash = "sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9"},
{file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"},
{file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"},
]
sphinx-markdown-builder = []
sphinxcontrib-applehelp = [
@ -1399,8 +1487,8 @@ untokenize = [
{file = "untokenize-0.1.1.tar.gz", hash = "md5:50d325dff09208c624cc603fad33bb0d"},
]
urllib3 = [
{file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
{file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
{file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"},
{file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"},
]
uvicorn = [
{file = "uvicorn-0.13.4-py3-none-any.whl", hash = "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524"},

Some files were not shown because too many files have changed in this diff Show More