Compare commits

...

68 Commits

Author SHA1 Message Date
Ju4tCode
1ee7c792e8 🔀 Merge pull request #26
Pre Release 2.0.0a3
2020-10-11 13:46:01 +08:00
yanyongyu
39a950ea80 📌 update lock file 2020-10-11 13:39:45 +08:00
yanyongyu
5bb41395d8 🔖 Pre Release 2.0.0a3 2020-10-11 13:19:20 +08:00
yanyongyu
a68ba09910 📝 Update docs 2020-10-11 13:17:40 +08:00
yanyongyu
b4e0034876 💡 add driver docstring 2020-10-10 23:40:01 +08:00
yanyongyu
e58174f6ec 📝 remove outdated docs 2020-10-09 20:26:39 +08:00
yanyongyu
1377f7337d 🚑 fix message segment setitem 2020-10-09 00:57:30 +08:00
yanyongyu
96ce29fd52 add optional dependence nonebot-test 2020-10-09 00:48:50 +08:00
yanyongyu
f164d85c5c 🐛 fix message segment getitem 2020-10-09 00:10:50 +08:00
yanyongyu
ce758a2231 📝 add nonebot quick import doc 2020-10-08 21:36:57 +08:00
yanyongyu
05c3a4b84f 💡 fix nickname docstring 2020-10-08 21:36:32 +08:00
yanyongyu
1d31e646ad 🔇 reduce log for meta event #21 2020-10-08 21:25:13 +08:00
yanyongyu
927a963d4f 🐛 fix nickname 2020-10-06 22:29:05 +08:00
yanyongyu
727eef9a34 💡 add matcher docstring 2020-10-06 17:03:05 +08:00
yanyongyu
9f8d009309 add matcher.send method 2020-10-06 16:24:04 +08:00
yanyongyu
734d3cd333 💡 update docstring for several modules 2020-10-06 02:08:48 +08:00
yanyongyu
2e989b5fe1 💡 add matcher docstring 2020-10-05 23:10:20 +08:00
yanyongyu
781d0cf654 ⚗️ rewrite echo and say builtin plugin 2020-10-04 18:10:01 +08:00
yanyongyu
0a11bd3e8e ✏️ fix cqhttp sub_type typo 2020-10-04 16:27:58 +08:00
yanyongyu
2574ef3e7a 💡 add message docstring 2020-10-03 18:18:43 +08:00
Ju4tCode
b80f0bf202 🔀 Merge pull request #19 2020-10-03 15:06:46 +08:00
yanyongyu
c7f1859d99 🐛 fix white space before plain text #18 2020-10-03 14:56:38 +08:00
yanyongyu
d71260ed68 🐛 fix group attrs 2020-10-02 00:28:53 +08:00
yanyongyu
3e4dc1a123 💡 add adapter docstring 2020-10-01 23:52:56 +08:00
Ju4tCode
a308f4d4ee 🔀 Merge pull request #16 from nonebot/dev
Pre Release 2.0.0a2
2020-10-01 01:31:40 +08:00
yanyongyu
1aac3d562d ⬆️ update poetry.lock file 2020-10-01 01:24:16 +08:00
Ju4tCode
5b9e86275a Merge branch 'master' into dev 2020-10-01 01:09:17 +08:00
yanyongyu
1016986663 🔖 Pre Release 2.0.0a2 2020-10-01 00:55:03 +08:00
yanyongyu
ef29280585 💡 add escape_tag docstring 2020-10-01 00:39:44 +08:00
yanyongyu
5b0083c0a8 👷 add ci for dev branch 2020-09-30 19:12:44 +08:00
Ju4tCode
d823b1733b 🔀 Merge pull request #15
fix #14 logger escape
2020-09-30 18:08:03 +08:00
yanyongyu
921acff09e 🐛 fix logger escape tag #14 2020-09-30 18:01:31 +08:00
yanyongyu
edc3aadbb5 💡 add cqhttp event 2020-09-29 23:10:29 +08:00
yanyongyu
f3986ace51 💡 add cqhttp docstring 2020-09-29 22:33:08 +08:00
yanyongyu
0eafedefa1 🏷️ update cqhttp.pyi 2020-09-29 22:32:15 +08:00
Ju4tCode
73e6062a1b 👷 update api docs ci 2020-09-29 22:29:51 +08:00
yanyongyu
00f22e5f05 🏷️ update pyi files 2020-09-28 12:45:55 +08:00
yanyongyu
35ed536cdf 📝 generate docs 2020-09-28 00:12:11 +08:00
yanyongyu
e13561c374 🏷️ add typing for plugin 2020-09-28 00:09:12 +08:00
yanyongyu
2921ba0120 add command alias/group test 2020-09-27 23:52:03 +08:00
yanyongyu
4e4d494ffd 📝 update README 2020-09-27 23:22:33 +08:00
Ju4tCode
b955024818 👷 update api docs ci 2020-09-27 19:19:28 +08:00
Ju4tCode
1952d93324 🔀 (Close #11)Merge pull request #13 2020-09-27 18:29:52 +08:00
yanyongyu
e0cba7c20c 🎨 add command group to entry file 2020-09-27 18:20:39 +08:00
yanyongyu
2042097f83 🎨 change args into optional 2020-09-27 18:05:13 +08:00
yanyongyu
3dc95b904f ⚗️ add command alias and group #11 2020-09-27 17:55:28 +08:00
yanyongyu
ecbe465232 ⚗️ add matcher group #11 2020-09-27 13:34:16 +08:00
yanyongyu
2e87c40434 🐛 fix error ws closing 2020-09-27 12:37:15 +08:00
yanyongyu
3467eb3c29 🚑 (Close #12)fix matcher key parser 2020-09-26 17:36:04 +08:00
yanyongyu
457fdfb057 🏷️ update cqhttp pyi 2020-09-26 16:33:57 +08:00
yanyongyu
44fef77288 🚑 fix matcher key parser 2020-09-26 16:33:30 +08:00
yanyongyu
bbeea86fd5 🏷️ update pyi 2020-09-26 14:40:08 +08:00
yanyongyu
8bd1aa0662 ✏️ fix regex typo 2020-09-26 13:04:18 +08:00
yanyongyu
8b09c0be35 💄 update messenger style 2020-09-25 01:00:28 +08:00
yanyongyu
dee86ae607 👽 update dependence due to cli published 2020-09-24 16:17:31 +08:00
Ju4tCode
f8231ec7e9 🔀 Merge pull request #10 from nonebot/dev 2020-09-24 00:37:21 +08:00
Ju4tCode
3bd9a9cc73 Update api_docs.yml 2020-09-24 00:30:50 +08:00
Ju4tCode
9171894a15 👷 update api docs ci 2020-09-24 00:22:35 +08:00
Ju4tCode
1d7180e6cd 👷 update api docs ci 2020-09-24 00:04:43 +08:00
Ju4tCode
1045e6a797 👷 update api docs ci 2020-09-23 23:42:06 +08:00
Ju4tCode
68795c36f2 👷 update api docs ci 2020-09-23 23:36:06 +08:00
Ju4tCode
2319ab7d20 👷 update api docs ci 2020-09-23 23:32:34 +08:00
Ju4tCode
541dce6c54 Update api_docs.yml 2020-09-23 23:14:03 +08:00
Ju4tCode
4306d12f83 👷 add api docs ci 2020-09-13 22:32:55 +08:00
Ju4tCode
b4d7fded7f 🔀 Merge pull request #9 from nonebot/dev 2020-09-08 17:01:15 +08:00
Ju4tCode
0a8a53a764 🔀 Merge pull request #8 from nonebot/dev 2020-08-30 22:21:14 +08:00
CodeCreator
2089c773ac 🔀 Merge pull request #6 from nonebot/dev
update cqhttp apis
2020-08-26 17:57:23 +08:00
CodeCreator
310aeb8447 Merge pull request #5 from nonebot/dev 2020-08-26 14:52:34 +08:00
71 changed files with 5844 additions and 569 deletions

41
.github/workflows/api_docs.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Build API Doc
on:
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
architecture: "x64"
- name: Install Dependences
run: |
python -m pip install --upgrade pip
pip install poetry
poetry install
- name: Build Doc
run: poetry run sphinx-build -M markdown ./docs_build ./build
- name: Copy Files
run: cp -r ./build/markdown/* ./docs/api/
- run: |
git config user.name nonebot
git config user.email nonebot@nonebot.dev
git add .
git diff-index --quiet HEAD || git commit -m ":memo: update api docs"
git remote -vv
git remote add target ${{github.event.pull_request.head.repo.clone_url}}
git push target HEAD:${{github.event.pull_request.head.ref}}

View File

@@ -1,10 +1,10 @@
<div align=center> <div align=center>
<img src="docs/.vuepress/public/logo.png" width="200" height="200"> <img src="./docs/.vuepress/public/logo.png" width="200" height="200">
# NoneBot # NoneBot
[![License](https://img.shields.io/github/license/richardchien/nonebot.svg)](LICENSE) [![License](https://img.shields.io/github/license/nonebot/nonebot2.svg)](LICENSE)
[![PyPI](https://img.shields.io/pypi/v/nonebot.svg)](https://pypi.python.org/pypi/nonebot) [![PyPI](https://img.shields.io/pypi/v/nonebot2.svg)](https://pypi.python.org/pypi/nonebot2)
![Python Version](https://img.shields.io/badge/python-3.7+-blue.svg) ![Python Version](https://img.shields.io/badge/python-3.7+-blue.svg)
![CQHTTP Version](https://img.shields.io/badge/cqhttp-11+-black.svg) ![CQHTTP Version](https://img.shields.io/badge/cqhttp-11+-black.svg)
[![QQ 群](https://img.shields.io/badge/qq%E7%BE%A4-768887710-orange.svg)](https://jq.qq.com/?_wv=1027&k=5OFifDh) [![QQ 群](https://img.shields.io/badge/qq%E7%BE%A4-768887710-orange.svg)](https://jq.qq.com/?_wv=1027&k=5OFifDh)
@@ -16,8 +16,6 @@
## 简介 ## 简介
**NoneBot2 尚在开发中**
NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。
除了起到解析消息的作用NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。 除了起到解析消息的作用NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。

View File

@@ -7,16 +7,10 @@
* [nonebot](nonebot.html) * [nonebot](nonebot.html)
* [nonebot.typing](typing.html)
* [nonebot.config](config.html) * [nonebot.config](config.html)
* [nonebot.sched](sched.html) * [nonebot.matcher](matcher.html)
* [nonebot.log](log.html)
* [nonebot.rule](rule.html) * [nonebot.rule](rule.html)
@@ -25,7 +19,28 @@
* [nonebot.permission](permission.html) * [nonebot.permission](permission.html)
* [nonebot.sched](sched.html)
* [nonebot.log](log.html)
* [nonebot.utils](utils.html) * [nonebot.utils](utils.html)
* [nonebot.typing](typing.html)
* [nonebot.exception](exception.html) * [nonebot.exception](exception.html)
* [nonebot.drivers](drivers/)
* [nonebot.drivers.fastapi](drivers/fastapi.html)
* [nonebot.adapters](adapters/)
* [nonebot.adapters.cqhttp](adapters/cqhttp.html)

View File

@@ -0,0 +1,323 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters 模块
## 协议适配基类
各协议请继承以下基类,并使用 `driver.register_adapter` 注册适配器
## _class_ `BaseBot`
基类:`abc.ABC`
Bot 基类。用于处理上报消息,并提供 API 调用接口。
### _abstract_ `__init__(driver, connection_type, config, self_id, *, websocket=None)`
* **参数**
* `driver: Driver`: Driver 对象
* `connection_type: str`: http 或者 websocket
* `config: Config`: Config 对象
* `self_id: str`: 机器人 ID
* `websocket: Optional[WebSocket]`: Websocket 连接对象
### `driver`
Driver 对象
### `connection_type`
连接类型
### `config`
Config 配置对象
### `self_id`
机器人 ID
### `websocket`
Websocket 连接对象
### _abstract property_ `type`
Adapter 类型
### _abstract async_ `handle_message(message)`
* **说明**
处理上报消息的函数,转换为 `Event` 事件后调用 `nonebot.message.handle_event` 进一步处理事件。
* **参数**
* `message: dict`: 收到的上报消息
### _abstract async_ `call_api(api, **data)`
* **说明**
调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
* **参数**
* `api: str`: API 名称
* `**data`: API 数据
* **示例**
```python
await bot.call_api("send_msg", data={"message": "hello world"})
await bot.send_msg(message="hello world")
```
### _abstract async_ `send(event, message, **kwargs)`
* **说明**
调用机器人基础发送消息接口
* **参数**
* `event: Event`: 上报事件
* `message: Union[str, Message, MessageSegment]`: 要发送的消息
* `**kwargs`
## _class_ `BaseEvent`
基类:`abc.ABC`
Event 基类。提供上报信息的关键信息,其余信息可从原始上报消息获取。
### `__init__(raw_event)`
* **参数**
* `raw_event: dict`: 原始上报消息
### _property_ `raw_event`
原始上报消息
### _abstract property_ `id`
事件 ID
### _abstract property_ `name`
事件名称
### _abstract property_ `self_id`
机器人 ID
### _abstract property_ `time`
事件发生时间
### _abstract property_ `type`
事件主类型
### _abstract property_ `detail_type`
事件详细类型
### _abstract property_ `sub_type`
事件子类型
### _abstract property_ `user_id`
触发事件的主体 ID
### _abstract property_ `group_id`
触发事件的主体群 ID
### _abstract property_ `to_me`
事件是否为发送给机器人的消息
### _abstract property_ `message`
消息内容
### _abstract property_ `reply`
回复的消息
### _abstract property_ `raw_message`
原始消息
### _abstract property_ `plain_text`
纯文本消息
### _abstract property_ `sender`
消息发送者信息
## _class_ `BaseMessageSegment`
基类:`abc.ABC`
消息段基类
### `type`
* 类型: `str`
* 说明: 消息段类型
### `data`
* 类型: `Dict[str, Union[str, list]]`
* 说明: 消息段数据
## _class_ `BaseMessage`
基类:`list`, `abc.ABC`
消息数组
### `__init__(message=None, *args, **kwargs)`
* **参数**
* `message: Union[str, dict, list, MessageSegment, Message]`: 消息内容
### `append(obj)`
* **说明**
添加一个消息段到消息数组末尾
* **参数**
* `obj: Union[str, MessageSegment]`: 要添加的消息段
### `extend(obj)`
* **说明**
拼接一个消息数组或多个消息段到消息数组末尾
* **参数**
* `obj: Union[Message, Iterable[MessageSegment]]`: 要添加的消息数组
### `reduce()`
* **说明**
缩减消息数组,即拼接相邻纯文本消息段
### `extract_plain_text()`
* **说明**
提取消息内纯文本消息

View File

@@ -0,0 +1,411 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters.cqhttp 模块
## CQHTTP (OneBot) v11 协议适配
协议详情请看: [CQHTTP](http://cqhttp.cc/) | [OneBot](https://github.com/howmanybots/onebot)
## `log(level, message)`
* **说明**
用于打印 CQHTTP 日志。
* **参数**
* `level: str`: 日志等级
* `message: str`: 日志信息
## `escape(s, *, escape_comma=True)`
* **说明**
对字符串进行 CQ 码转义。
* **参数**
* `s: str`: 需要转义的字符串
* `escape_comma: bool`: 是否转义逗号(`,`)。
## `unescape(s)`
* **说明**
对字符串进行 CQ 码去转义。
* **参数**
* `s: str`: 需要转义的字符串
## `_b2s(b)`
转换布尔值为字符串。
## _async_ `_check_reply(bot, event)`
* **说明**
检查消息中存在的回复,去除并赋值 `event.reply`, `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_at_me(bot, event)`
* **说明**
检查消息开头或结尾是否存在 @机器人,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_nickname(bot, event)`
* **说明**
检查消息开头是否存在,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_handle_api_result(result)`
* **说明**
处理 API 请求返回值。
* **参数**
* `result: Optional[Dict[str, Any]]`: API 返回数据
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ActionFailed`: API 调用失败
## _class_ `Bot`
基类:[`nonebot.adapters.BaseBot`](#None)
CQHTTP 协议 Bot 适配。继承属性参考 [BaseBot](./#class-basebot) 。
### _property_ `type`
* 返回: `"cqhttp"`
### _async_ `handle_message(message)`
* **说明**
调用 [_check_reply](#async-check-reply-bot-event), [_check_at_me](#check-at-me-bot-event), [_check_nickname](#check-nickname-bot-event) 处理事件并转换为 [Event](#class-event)
### _async_ `call_api(api, **data)`
* **说明**
调用 CQHTTP 协议 API
* **参数**
* `api: str`: API 名称
* `**data: Any`: API 参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
### _async_ `send(event, message, at_sender=False, **kwargs)`
* **说明**
根据 `event` 向触发事件的主体发送消息。
* **参数**
* `event: Event`: Event 对象
* `message: Union[str, Message, MessageSegment]`: 要发送的消息
* `at_sender: bool`: 是否 @ 事件主体
* `**kwargs`: 覆盖默认参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ValueError`: 缺少 `user_id`, `group_id`
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
## _class_ `Event`
基类:[`nonebot.adapters.BaseEvent`](#None)
CQHTTP 协议 Event 适配。继承属性参考 [BaseEvent](./#class-baseevent) 。
### _property_ `id`
* 类型: `Optional[int]`
* 说明: 事件/消息 ID
### _property_ `name`
* 类型: `str`
* 说明: 事件名称,由类型与 `.` 组合而成
### _property_ `self_id`
* 类型: `str`
* 说明: 机器人自身 ID
### _property_ `time`
* 类型: `int`
* 说明: 事件发生时间
### _property_ `type`
* 类型: `str`
* 说明: 事件类型
### _property_ `detail_type`
* 类型: `str`
* 说明: 事件详细类型
### _property_ `sub_type`
* 类型: `Optional[str]`
* 说明: 事件子类型
### _property_ `user_id`
* 类型: `Optional[int]`
* 说明: 事件主体 ID
### _property_ `group_id`
* 类型: `Optional[int]`
* 说明: 事件主体群 ID
### _property_ `to_me`
* 类型: `Optional[bool]`
* 说明: 消息是否与机器人相关
### _property_ `message`
* 类型: `Optional[Message]`
* 说明: 消息内容
### _property_ `reply`
* 类型: `Optional[dict]`
* 说明: 回复消息详情
### _property_ `raw_message`
* 类型: `Optional[str]`
* 说明: 原始消息
### _property_ `plain_text`
* 类型: `Optional[str]`
* 说明: 纯文本消息内容
### _property_ `sender`
* 类型: `Optional[dict]`
* 说明: 消息发送者信息
## _class_ `MessageSegment`
基类:[`nonebot.adapters.BaseMessageSegment`](#None)
## _class_ `Message`
基类:[`nonebot.adapters.BaseMessage`](#None)

View File

@@ -194,10 +194,10 @@ SUPER_USERS=[12345789]
### `nickname` ### `nickname`
* 类型: `Union[str, Set[str]]` * 类型: `Set[str]`
* 默认值: `""` * 默认值: `set()`
* 说明: * 说明:

View File

@@ -0,0 +1,37 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers 模块
## 后端驱动适配基类
各驱动请继承以下基类
## _class_ `BaseDriver`
基类:`abc.ABC`
Driver 基类。将后端框架封装,以满足适配器使用。
### `_adapters`
* **类型**
`Dict[str, Type[Bot]]`
* **说明**
已注册的适配器列表
### _abstract_ `__init__(env, config)`
Initialize self. See help(type(self)) for accurate signature.

View File

@@ -0,0 +1,16 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers.fastapi 模块
## _class_ `Driver`
基类:[`nonebot.drivers.BaseDriver`](#None)
### `__init__(env, config)`
Initialize self. See help(type(self)) for accurate signature.

View File

@@ -0,0 +1,485 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.matcher 模块
## 事件响应器
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行 对话 。
## `matchers`
* **类型**
`Dict[int, List[Type[Matcher]]]`
* **说明**
用于存储当前所有的事件响应器
## _class_ `Matcher`
基类:`object`
事件响应器类
### `module`
* **类型**
`Optional[str]`
* **说明**
事件响应器所在模块名称
### `type`
* **类型**
`str`
* **说明**
事件响应器类型
### `rule`
* **类型**
`Rule`
* **说明**
事件响应器匹配规则
### `permission`
* **类型**
`Permission`
* **说明**
事件响应器触发权限
### `priority`
* **类型**
`int`
* **说明**
事件响应器优先级
### `block`
* **类型**
`bool`
* **说明**
事件响应器是否阻止事件传播
### `temp`
* **类型**
`bool`
* **说明**
事件响应器是否为临时
### `expire_time`
* **类型**
`Optional[datetime]`
* **说明**
事件响应器过期时间点
### `_default_state`
* **类型**
`dict`
* **说明**
事件响应器默认状态
### `_default_parser`
* **类型**
`Optional[ArgsParser]`
* **说明**
事件响应器默认参数解析函数
### `__init__()`
实例化 Matcher 以便运行
### `handlers`
* **类型**
`List[Handler]`
* **说明**
事件响应器拥有的事件处理函数列表
### _classmethod_ `new(type_='', rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, module=None, default_state=None, expire_time=None)`
* **说明**
创建一个新的事件响应器,并存储至 [matchers](#matchers)
* **参数**
* `type_: str`: 事件响应器类型,与 `event.type` 一致时触发,空字符串表示任意
* `rule: Optional[Rule]`: 匹配规则
* `permission: Optional[Permission]`: 权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器,即触发一次后删除
* `priority: int`: 响应优先级
* `block: bool`: 是否阻止事件向更低优先级的响应器传播
* `module: Optional[str]`: 事件响应器所在模块名称
* `default_state: Optional[dict]`: 默认状态 `state`
* `expire_time: Optional[datetime]`: 事件响应器最终有效时间点,过时即被删除
* **返回**
* `Type[Matcher]`: 新的事件响应器类
### _async classmethod_ `check_perm(bot, event)`
* **说明**
检查是否满足触发权限
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: 上报事件
* **返回**
* `bool`: 是否满足权限
### _async classmethod_ `check_rule(bot, event, state)`
* **说明**
检查是否满足匹配规则
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: 上报事件
* `state: dict`: 当前状态
* **返回**
* `bool`: 是否满足匹配规则
### _classmethod_ `args_parser(func)`
* **说明**
装饰一个函数来更改当前事件响应器的默认参数解析函数
* **参数**
* `func: ArgsParser`: 参数解析函数
### _classmethod_ `handle()`
* **说明**
装饰一个函数来向事件响应器直接添加一个处理函数
* **参数**
*
### _classmethod_ `receive()`
* **说明**
装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
* **参数**
*
### _classmethod_ `got(key, prompt=None, args_parser=None)`
* **说明**
装饰一个函数来指示 NoneBot 当要获取的 `key` 不存在时接收用户新的一条消息并经过 `ArgsParser` 处理后再运行该函数,如果 `key` 已存在则直接继续运行
* **参数**
* `key: str`: 参数名
* `prompt: Optional[Union[str, Message, MessageSegment]]`: 在参数不存在时向用户发送的消息
* `args_parser: Optional[ArgsParser]`: 可选参数解析函数,空则使用默认解析函数
### _async classmethod_ `send(message)`
* **说明**
发送一条消息给当前交互用户
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
### _async classmethod_ `finish(message=None)`
* **说明**
发送一条消息给当前交互用户并结束当前事件响应器
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
### _async classmethod_ `pause(prompt=None)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
### _async classmethod_ `reject(prompt=None)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后重新运行当前处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
## _class_ `MatcherGroup`
基类:`object`
事件响应器组合,统一管理。用法同 `Matcher`
### `__init__(type_='', rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, module=None, default_state=None, expire_time=None)`
* **说明**
创建一个事件响应器组合,参数为默认值,与 `Matcher.new` 一致
### `matchers`
* **类型**
`List[Type[Matcher]]`
* **说明**
组内事件响应器列表
### `new(type_='', rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, module=None, default_state=None, expire_time=None)`
* **说明**
在组中创建一个新的事件响应器,参数留空则使用组合默认值
:::danger 警告
如果使用 handlers 参数覆盖组合默认值则该事件响应器不会随组合一起添加新的事件处理函数
:::

View File

@@ -5,6 +5,55 @@ sidebarDepth: 0
# NoneBot 模块 # NoneBot 模块
## 快捷导入
为方便使用,`nonebot` 模块从子模块导入了部分内容
* `on_message` => `nonebot.plugin.on_message`
* `on_notice` => `nonebot.plugin.on_notice`
* `on_request` => `nonebot.plugin.on_request`
* `on_metaevent` => `nonebot.plugin.on_metaevent`
* `on_startswith` => `nonebot.plugin.on_startswith`
* `on_endswith` => `nonebot.plugin.on_endswith`
* `on_command` => `nonebot.plugin.on_command`
* `on_regex` => `nonebot.plugin.on_regex`
* `on_regex` => `nonebot.plugin.on_regex`
* `on_regex` => `nonebot.plugin.on_regex`
* `CommandGroup` => `nonebot.plugin.CommandGroup`
* `load_plugin` => `nonebot.plugin.load_plugin`
* `load_plugins` => `nonebot.plugin.load_plugins`
* `load_builtin_plugins` => `nonebot.plugin.load_builtin_plugins`
* `get_loaded_plugins` => `nonebot.plugin.get_loaded_plugins`
## `get_driver()` ## `get_driver()`

View File

@@ -7,10 +7,10 @@ sidebarDepth: 0
## 规则 ## 规则
每个 `Matcher` 拥有一个 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
:::tip 提示 :::tip 提示
`RuleChecker` 既可以是 async function 也可以是 sync function `RuleChecker` 既可以是 async function 也可以是 sync function,但在最终会被 `nonebot.utils.run_sync` 转换为 async function
::: :::
@@ -120,3 +120,83 @@ Rule(async_function, run_sync(sync_function))
* `msg: str`: 消息结尾字符串 * `msg: str`: 消息结尾字符串
## `keyword(msg)`
* **说明**
匹配消息关键词
* **参数**
* `msg: str`: 关键词
## `command(command)`
* **说明**
命令形式匹配,根据配置里提供的 `command_start`, `command_sep` 判断消息是否为命令。
* **参数**
* `command: Tuples[str, ...]`: 命令内容
* **示例**
使用默认 `command_start`, `command_sep` 配置
命令 `("test",)` 可以匹配:`/test` 开头的消息
命令 `("test", "sub")` 可以匹配”`/test.sub` 开头的消息
:::tip 提示
命令内容与后续消息间无需空格!
:::
## `regex(regex, flags=0)`
* **说明**
根据正则表达式进行匹配
* **参数**
* `regex: str`: 正则表达式
* `flags: Union[int, re.RegexFlag]`: 正则标志
## `to_me()`
* **说明**
通过 `event.to_me` 判断消息是否是发送给机器人
* **参数**
* 无

View File

@@ -142,6 +142,22 @@ sidebarDepth: 0
## `MatcherGroup`
* **类型**
`MatcherGroup`
* **说明**
MatcherGroup 为 Matcher 的集合。可以共享 Handler。
## `Rule` ## `Rule`

View File

@@ -6,6 +6,29 @@ sidebarDepth: 0
# NoneBot.utils 模块 # NoneBot.utils 模块
## `escape_tag(s)`
* **说明**
用于记录带颜色日志时转义 `<tag>` 类型特殊标签
* **参数**
* `s: str`: 需要转义的字符串
* **返回**
* `str`
## `run_sync(func)` ## `run_sync(func)`

View File

@@ -20,11 +20,7 @@ NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人
## 它如何工作? ## 它如何工作?
NoneBot 的运行离不开 酷 Q 和 CQHTTP 插件。酷 Q 扮演着「无头 QQ 客户端」的角色,它进行实际的消息、通知、请求的接收和发送,当 酷 Q 收到消息时,它将这个消息包装为一个事件(通知和请求同理),并通过它自己的插件机制将事件传送给 CQHTTP 插件,后者再根据其配置中的 `post_url``ws_reverse_url` 等项来将事件发送至 NoneBot。 ~~未填坑~~
在 NoneBot 收到事件前,它底层的 aiocqhttp 实际已经先看到了事件aiocqhttp 根据事件的类型信息,通知到 NoneBot 的相应函数。特别地,对于消息类型的事件,还将消息内容转换成了 `aiocqhttp.message.Message` 类型,以便处理。
NoneBot 的事件处理函数收到通知后对于不同类型的事件再做相应的预处理和解析然后调用对应的插件并向其提供适合此类事件的会话Session对象。NoneBot 插件的编写者要做的,就是利用 Session 对象中提供的数据,在插件的处理函数中实现所需的功能。
## 特色 ## 特色

View File

@@ -159,7 +159,7 @@ weather = on_command("天气", rule=to_me(), permission=Permission(), priority=5
- `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器 - `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器
- `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器 - `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器
- `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器 - `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器
- `on_regax(pattern_str)` ~ `on("message", regax(pattern_str))`: 正则匹配处理器 - `on_regex(pattern_str)` ~ `on("message", regex(pattern_str))`: 正则匹配处理器
#### 匹配规则 rule #### 匹配规则 rule

View File

@@ -47,21 +47,13 @@
"title": "nonebot 模块", "title": "nonebot 模块",
"path": "nonebot" "path": "nonebot"
}, },
{
"title": "nonebot.typing 模块",
"path": "typing"
},
{ {
"title": "nonebot.config 模块", "title": "nonebot.config 模块",
"path": "config" "path": "config"
}, },
{ {
"title": "nonebot.sched 模块", "title": "nonebot.matcher 模块",
"path": "sched" "path": "matcher"
},
{
"title": "nonebot.log 模块",
"path": "log"
}, },
{ {
"title": "nonebot.rule 模块", "title": "nonebot.rule 模块",
@@ -71,13 +63,41 @@
"title": "nonebot.permission 模块", "title": "nonebot.permission 模块",
"path": "permission" "path": "permission"
}, },
{
"title": "nonebot.sched 模块",
"path": "sched"
},
{
"title": "nonebot.log 模块",
"path": "log"
},
{ {
"title": "nonebot.utils 模块", "title": "nonebot.utils 模块",
"path": "utils" "path": "utils"
}, },
{
"title": "nonebot.typing 模块",
"path": "typing"
},
{ {
"title": "nonebot.exception 模块", "title": "nonebot.exception 模块",
"path": "exception" "path": "exception"
},
{
"title": "nonebot.drivers 模块",
"path": "drivers/"
},
{
"title": "nonebot.drivers.fastapi 模块",
"path": "drivers/fastapi"
},
{
"title": "nonebot.adapters 模块",
"path": "adapters/"
},
{
"title": "nonebot.adapters.cqhttp 模块",
"path": "adapters/cqhttp"
} }
] ]
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="qq-chat"> <div class="qq-chat">
<v-app <v-app>
><v-main> <v-main>
<v-card class="elevation-6"> <v-card class="elevation-6">
<v-toolbar color="primary" dark dense flat> <v-toolbar color="primary" dark dense flat>
<v-row no-gutters> <v-row no-gutters>
@@ -139,19 +139,15 @@ export default {
default: () => [] default: () => []
} }
}, },
data: () => ({
wow: null
}),
methods: { methods: {
initWOW: function() { initWOW: function() {
this.wow = new WOW({ new WOW({
noxClass: "wow", noxClass: "wow",
animateClass: "animate__animated", animateClass: "animate__animated",
offset: 0, offset: 0,
mobile: true, mobile: true,
live: true live: true
}); }).init();
this.wow.init();
} }
}, },
mounted() { mounted() {
@@ -167,6 +163,7 @@ export default {
.chat { .chat {
min-height: 150px; min-height: 150px;
overflow-x: hidden;
} }
.chat-bg { .chat-bg {
background-color: #f3f6f9; background-color: #f3f6f9;
@@ -214,3 +211,9 @@ export default {
font-size: 12px; font-size: 12px;
} }
</style> </style>
<style>
.v-application--wrap {
min-height: 0 !important;
}
</style>

View File

@@ -88,21 +88,13 @@ module.exports = context => ({
title: "nonebot 模块", title: "nonebot 模块",
path: "nonebot" path: "nonebot"
}, },
{
title: "nonebot.typing 模块",
path: "typing"
},
{ {
title: "nonebot.config 模块", title: "nonebot.config 模块",
path: "config" path: "config"
}, },
{ {
title: "nonebot.sched 模块", title: "nonebot.matcher 模块",
path: "sched" path: "matcher"
},
{
title: "nonebot.log 模块",
path: "log"
}, },
{ {
title: "nonebot.rule 模块", title: "nonebot.rule 模块",
@@ -112,13 +104,41 @@ module.exports = context => ({
title: "nonebot.permission 模块", title: "nonebot.permission 模块",
path: "permission" path: "permission"
}, },
{
title: "nonebot.sched 模块",
path: "sched"
},
{
title: "nonebot.log 模块",
path: "log"
},
{ {
title: "nonebot.utils 模块", title: "nonebot.utils 模块",
path: "utils" path: "utils"
}, },
{
title: "nonebot.typing 模块",
path: "typing"
},
{ {
title: "nonebot.exception 模块", title: "nonebot.exception 模块",
path: "exception" path: "exception"
},
{
title: "nonebot.drivers 模块",
path: "drivers/"
},
{
title: "nonebot.drivers.fastapi 模块",
path: "drivers/fastapi"
},
{
title: "nonebot.adapters 模块",
path: "adapters/"
},
{
title: "nonebot.adapters.cqhttp 模块",
path: "adapters/cqhttp"
} }
] ]
} }

View File

@@ -1,3 +1,3 @@
[ [
"2.0.0a1" "2.0.0a3"
] ]

View File

@@ -7,16 +7,10 @@
* [nonebot](nonebot.html) * [nonebot](nonebot.html)
* [nonebot.typing](typing.html)
* [nonebot.config](config.html) * [nonebot.config](config.html)
* [nonebot.sched](sched.html) * [nonebot.matcher](matcher.html)
* [nonebot.log](log.html)
* [nonebot.rule](rule.html) * [nonebot.rule](rule.html)
@@ -25,7 +19,28 @@
* [nonebot.permission](permission.html) * [nonebot.permission](permission.html)
* [nonebot.sched](sched.html)
* [nonebot.log](log.html)
* [nonebot.utils](utils.html) * [nonebot.utils](utils.html)
* [nonebot.typing](typing.html)
* [nonebot.exception](exception.html) * [nonebot.exception](exception.html)
* [nonebot.drivers](drivers/)
* [nonebot.drivers.fastapi](drivers/fastapi.html)
* [nonebot.adapters](adapters/)
* [nonebot.adapters.cqhttp](adapters/cqhttp.html)

323
docs/api/adapters/README.md Normal file
View File

@@ -0,0 +1,323 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters 模块
## 协议适配基类
各协议请继承以下基类,并使用 `driver.register_adapter` 注册适配器
## _class_ `BaseBot`
基类:`abc.ABC`
Bot 基类。用于处理上报消息,并提供 API 调用接口。
### _abstract_ `__init__(driver, connection_type, config, self_id, *, websocket=None)`
* **参数**
* `driver: Driver`: Driver 对象
* `connection_type: str`: http 或者 websocket
* `config: Config`: Config 对象
* `self_id: str`: 机器人 ID
* `websocket: Optional[WebSocket]`: Websocket 连接对象
### `driver`
Driver 对象
### `connection_type`
连接类型
### `config`
Config 配置对象
### `self_id`
机器人 ID
### `websocket`
Websocket 连接对象
### _abstract property_ `type`
Adapter 类型
### _abstract async_ `handle_message(message)`
* **说明**
处理上报消息的函数,转换为 `Event` 事件后调用 `nonebot.message.handle_event` 进一步处理事件。
* **参数**
* `message: dict`: 收到的上报消息
### _abstract async_ `call_api(api, **data)`
* **说明**
调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
* **参数**
* `api: str`: API 名称
* `**data`: API 数据
* **示例**
```python
await bot.call_api("send_msg", data={"message": "hello world"})
await bot.send_msg(message="hello world")
```
### _abstract async_ `send(event, message, **kwargs)`
* **说明**
调用机器人基础发送消息接口
* **参数**
* `event: Event`: 上报事件
* `message: Union[str, Message, MessageSegment]`: 要发送的消息
* `**kwargs`
## _class_ `BaseEvent`
基类:`abc.ABC`
Event 基类。提供上报信息的关键信息,其余信息可从原始上报消息获取。
### `__init__(raw_event)`
* **参数**
* `raw_event: dict`: 原始上报消息
### _property_ `raw_event`
原始上报消息
### _abstract property_ `id`
事件 ID
### _abstract property_ `name`
事件名称
### _abstract property_ `self_id`
机器人 ID
### _abstract property_ `time`
事件发生时间
### _abstract property_ `type`
事件主类型
### _abstract property_ `detail_type`
事件详细类型
### _abstract property_ `sub_type`
事件子类型
### _abstract property_ `user_id`
触发事件的主体 ID
### _abstract property_ `group_id`
触发事件的主体群 ID
### _abstract property_ `to_me`
事件是否为发送给机器人的消息
### _abstract property_ `message`
消息内容
### _abstract property_ `reply`
回复的消息
### _abstract property_ `raw_message`
原始消息
### _abstract property_ `plain_text`
纯文本消息
### _abstract property_ `sender`
消息发送者信息
## _class_ `BaseMessageSegment`
基类:`abc.ABC`
消息段基类
### `type`
* 类型: `str`
* 说明: 消息段类型
### `data`
* 类型: `Dict[str, Union[str, list]]`
* 说明: 消息段数据
## _class_ `BaseMessage`
基类:`list`, `abc.ABC`
消息数组
### `__init__(message=None, *args, **kwargs)`
* **参数**
* `message: Union[str, dict, list, MessageSegment, Message]`: 消息内容
### `append(obj)`
* **说明**
添加一个消息段到消息数组末尾
* **参数**
* `obj: Union[str, MessageSegment]`: 要添加的消息段
### `extend(obj)`
* **说明**
拼接一个消息数组或多个消息段到消息数组末尾
* **参数**
* `obj: Union[Message, Iterable[MessageSegment]]`: 要添加的消息数组
### `reduce()`
* **说明**
缩减消息数组,即拼接相邻纯文本消息段
### `extract_plain_text()`
* **说明**
提取消息内纯文本消息

411
docs/api/adapters/cqhttp.md Normal file
View File

@@ -0,0 +1,411 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters.cqhttp 模块
## CQHTTP (OneBot) v11 协议适配
协议详情请看: [CQHTTP](http://cqhttp.cc/) | [OneBot](https://github.com/howmanybots/onebot)
## `log(level, message)`
* **说明**
用于打印 CQHTTP 日志。
* **参数**
* `level: str`: 日志等级
* `message: str`: 日志信息
## `escape(s, *, escape_comma=True)`
* **说明**
对字符串进行 CQ 码转义。
* **参数**
* `s: str`: 需要转义的字符串
* `escape_comma: bool`: 是否转义逗号(`,`)。
## `unescape(s)`
* **说明**
对字符串进行 CQ 码去转义。
* **参数**
* `s: str`: 需要转义的字符串
## `_b2s(b)`
转换布尔值为字符串。
## _async_ `_check_reply(bot, event)`
* **说明**
检查消息中存在的回复,去除并赋值 `event.reply`, `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_at_me(bot, event)`
* **说明**
检查消息开头或结尾是否存在 @机器人,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_nickname(bot, event)`
* **说明**
检查消息开头是否存在,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_handle_api_result(result)`
* **说明**
处理 API 请求返回值。
* **参数**
* `result: Optional[Dict[str, Any]]`: API 返回数据
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ActionFailed`: API 调用失败
## _class_ `Bot`
基类:[`nonebot.adapters.BaseBot`](#None)
CQHTTP 协议 Bot 适配。继承属性参考 [BaseBot](./#class-basebot) 。
### _property_ `type`
* 返回: `"cqhttp"`
### _async_ `handle_message(message)`
* **说明**
调用 [_check_reply](#async-check-reply-bot-event), [_check_at_me](#check-at-me-bot-event), [_check_nickname](#check-nickname-bot-event) 处理事件并转换为 [Event](#class-event)
### _async_ `call_api(api, **data)`
* **说明**
调用 CQHTTP 协议 API
* **参数**
* `api: str`: API 名称
* `**data: Any`: API 参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
### _async_ `send(event, message, at_sender=False, **kwargs)`
* **说明**
根据 `event` 向触发事件的主体发送消息。
* **参数**
* `event: Event`: Event 对象
* `message: Union[str, Message, MessageSegment]`: 要发送的消息
* `at_sender: bool`: 是否 @ 事件主体
* `**kwargs`: 覆盖默认参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ValueError`: 缺少 `user_id`, `group_id`
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
## _class_ `Event`
基类:[`nonebot.adapters.BaseEvent`](#None)
CQHTTP 协议 Event 适配。继承属性参考 [BaseEvent](./#class-baseevent) 。
### _property_ `id`
* 类型: `Optional[int]`
* 说明: 事件/消息 ID
### _property_ `name`
* 类型: `str`
* 说明: 事件名称,由类型与 `.` 组合而成
### _property_ `self_id`
* 类型: `str`
* 说明: 机器人自身 ID
### _property_ `time`
* 类型: `int`
* 说明: 事件发生时间
### _property_ `type`
* 类型: `str`
* 说明: 事件类型
### _property_ `detail_type`
* 类型: `str`
* 说明: 事件详细类型
### _property_ `sub_type`
* 类型: `Optional[str]`
* 说明: 事件子类型
### _property_ `user_id`
* 类型: `Optional[int]`
* 说明: 事件主体 ID
### _property_ `group_id`
* 类型: `Optional[int]`
* 说明: 事件主体群 ID
### _property_ `to_me`
* 类型: `Optional[bool]`
* 说明: 消息是否与机器人相关
### _property_ `message`
* 类型: `Optional[Message]`
* 说明: 消息内容
### _property_ `reply`
* 类型: `Optional[dict]`
* 说明: 回复消息详情
### _property_ `raw_message`
* 类型: `Optional[str]`
* 说明: 原始消息
### _property_ `plain_text`
* 类型: `Optional[str]`
* 说明: 纯文本消息内容
### _property_ `sender`
* 类型: `Optional[dict]`
* 说明: 消息发送者信息
## _class_ `MessageSegment`
基类:[`nonebot.adapters.BaseMessageSegment`](#None)
## _class_ `Message`
基类:[`nonebot.adapters.BaseMessage`](#None)

View File

@@ -194,10 +194,10 @@ SUPER_USERS=[12345789]
### `nickname` ### `nickname`
* 类型: `Union[str, Set[str]]` * 类型: `Set[str]`
* 默认值: `""` * 默认值: `set()`
* 说明: * 说明:

View File

@@ -0,0 +1,37 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers 模块
## 后端驱动适配基类
各驱动请继承以下基类
## _class_ `BaseDriver`
基类:`abc.ABC`
Driver 基类。将后端框架封装,以满足适配器使用。
### `_adapters`
* **类型**
`Dict[str, Type[Bot]]`
* **说明**
已注册的适配器列表
### _abstract_ `__init__(env, config)`
Initialize self. See help(type(self)) for accurate signature.

View File

@@ -0,0 +1,16 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers.fastapi 模块
## _class_ `Driver`
基类:[`nonebot.drivers.BaseDriver`](#None)
### `__init__(env, config)`
Initialize self. See help(type(self)) for accurate signature.

485
docs/api/matcher.md Normal file
View File

@@ -0,0 +1,485 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.matcher 模块
## 事件响应器
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行 对话 。
## `matchers`
* **类型**
`Dict[int, List[Type[Matcher]]]`
* **说明**
用于存储当前所有的事件响应器
## _class_ `Matcher`
基类:`object`
事件响应器类
### `module`
* **类型**
`Optional[str]`
* **说明**
事件响应器所在模块名称
### `type`
* **类型**
`str`
* **说明**
事件响应器类型
### `rule`
* **类型**
`Rule`
* **说明**
事件响应器匹配规则
### `permission`
* **类型**
`Permission`
* **说明**
事件响应器触发权限
### `priority`
* **类型**
`int`
* **说明**
事件响应器优先级
### `block`
* **类型**
`bool`
* **说明**
事件响应器是否阻止事件传播
### `temp`
* **类型**
`bool`
* **说明**
事件响应器是否为临时
### `expire_time`
* **类型**
`Optional[datetime]`
* **说明**
事件响应器过期时间点
### `_default_state`
* **类型**
`dict`
* **说明**
事件响应器默认状态
### `_default_parser`
* **类型**
`Optional[ArgsParser]`
* **说明**
事件响应器默认参数解析函数
### `__init__()`
实例化 Matcher 以便运行
### `handlers`
* **类型**
`List[Handler]`
* **说明**
事件响应器拥有的事件处理函数列表
### _classmethod_ `new(type_='', rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, module=None, default_state=None, expire_time=None)`
* **说明**
创建一个新的事件响应器,并存储至 [matchers](#matchers)
* **参数**
* `type_: str`: 事件响应器类型,与 `event.type` 一致时触发,空字符串表示任意
* `rule: Optional[Rule]`: 匹配规则
* `permission: Optional[Permission]`: 权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器,即触发一次后删除
* `priority: int`: 响应优先级
* `block: bool`: 是否阻止事件向更低优先级的响应器传播
* `module: Optional[str]`: 事件响应器所在模块名称
* `default_state: Optional[dict]`: 默认状态 `state`
* `expire_time: Optional[datetime]`: 事件响应器最终有效时间点,过时即被删除
* **返回**
* `Type[Matcher]`: 新的事件响应器类
### _async classmethod_ `check_perm(bot, event)`
* **说明**
检查是否满足触发权限
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: 上报事件
* **返回**
* `bool`: 是否满足权限
### _async classmethod_ `check_rule(bot, event, state)`
* **说明**
检查是否满足匹配规则
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: 上报事件
* `state: dict`: 当前状态
* **返回**
* `bool`: 是否满足匹配规则
### _classmethod_ `args_parser(func)`
* **说明**
装饰一个函数来更改当前事件响应器的默认参数解析函数
* **参数**
* `func: ArgsParser`: 参数解析函数
### _classmethod_ `handle()`
* **说明**
装饰一个函数来向事件响应器直接添加一个处理函数
* **参数**
*
### _classmethod_ `receive()`
* **说明**
装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
* **参数**
*
### _classmethod_ `got(key, prompt=None, args_parser=None)`
* **说明**
装饰一个函数来指示 NoneBot 当要获取的 `key` 不存在时接收用户新的一条消息并经过 `ArgsParser` 处理后再运行该函数,如果 `key` 已存在则直接继续运行
* **参数**
* `key: str`: 参数名
* `prompt: Optional[Union[str, Message, MessageSegment]]`: 在参数不存在时向用户发送的消息
* `args_parser: Optional[ArgsParser]`: 可选参数解析函数,空则使用默认解析函数
### _async classmethod_ `send(message)`
* **说明**
发送一条消息给当前交互用户
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
### _async classmethod_ `finish(message=None)`
* **说明**
发送一条消息给当前交互用户并结束当前事件响应器
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
### _async classmethod_ `pause(prompt=None)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
### _async classmethod_ `reject(prompt=None)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后重新运行当前处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
## _class_ `MatcherGroup`
基类:`object`
事件响应器组合,统一管理。用法同 `Matcher`
### `__init__(type_='', rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, module=None, default_state=None, expire_time=None)`
* **说明**
创建一个事件响应器组合,参数为默认值,与 `Matcher.new` 一致
### `matchers`
* **类型**
`List[Type[Matcher]]`
* **说明**
组内事件响应器列表
### `new(type_='', rule=None, permission=None, handlers=None, temp=False, priority=1, block=False, *, module=None, default_state=None, expire_time=None)`
* **说明**
在组中创建一个新的事件响应器,参数留空则使用组合默认值
:::danger 警告
如果使用 handlers 参数覆盖组合默认值则该事件响应器不会随组合一起添加新的事件处理函数
:::

View File

@@ -5,6 +5,55 @@ sidebarDepth: 0
# NoneBot 模块 # NoneBot 模块
## 快捷导入
为方便使用,`nonebot` 模块从子模块导入了部分内容
* `on_message` => `nonebot.plugin.on_message`
* `on_notice` => `nonebot.plugin.on_notice`
* `on_request` => `nonebot.plugin.on_request`
* `on_metaevent` => `nonebot.plugin.on_metaevent`
* `on_startswith` => `nonebot.plugin.on_startswith`
* `on_endswith` => `nonebot.plugin.on_endswith`
* `on_command` => `nonebot.plugin.on_command`
* `on_regex` => `nonebot.plugin.on_regex`
* `on_regex` => `nonebot.plugin.on_regex`
* `on_regex` => `nonebot.plugin.on_regex`
* `CommandGroup` => `nonebot.plugin.CommandGroup`
* `load_plugin` => `nonebot.plugin.load_plugin`
* `load_plugins` => `nonebot.plugin.load_plugins`
* `load_builtin_plugins` => `nonebot.plugin.load_builtin_plugins`
* `get_loaded_plugins` => `nonebot.plugin.get_loaded_plugins`
## `get_driver()` ## `get_driver()`

View File

@@ -7,10 +7,10 @@ sidebarDepth: 0
## 规则 ## 规则
每个 `Matcher` 拥有一个 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
:::tip 提示 :::tip 提示
`RuleChecker` 既可以是 async function 也可以是 sync function `RuleChecker` 既可以是 async function 也可以是 sync function,但在最终会被 `nonebot.utils.run_sync` 转换为 async function
::: :::
@@ -120,3 +120,83 @@ Rule(async_function, run_sync(sync_function))
* `msg: str`: 消息结尾字符串 * `msg: str`: 消息结尾字符串
## `keyword(msg)`
* **说明**
匹配消息关键词
* **参数**
* `msg: str`: 关键词
## `command(command)`
* **说明**
命令形式匹配,根据配置里提供的 `command_start`, `command_sep` 判断消息是否为命令。
* **参数**
* `command: Tuples[str, ...]`: 命令内容
* **示例**
使用默认 `command_start`, `command_sep` 配置
命令 `("test",)` 可以匹配:`/test` 开头的消息
命令 `("test", "sub")` 可以匹配”`/test.sub` 开头的消息
:::tip 提示
命令内容与后续消息间无需空格!
:::
## `regex(regex, flags=0)`
* **说明**
根据正则表达式进行匹配
* **参数**
* `regex: str`: 正则表达式
* `flags: Union[int, re.RegexFlag]`: 正则标志
## `to_me()`
* **说明**
通过 `event.to_me` 判断消息是否是发送给机器人
* **参数**
*

View File

@@ -142,6 +142,22 @@ sidebarDepth: 0
## `MatcherGroup`
* **类型**
`MatcherGroup`
* **说明**
MatcherGroup 为 Matcher 的集合。可以共享 Handler。
## `Rule` ## `Rule`

View File

@@ -6,6 +6,29 @@ sidebarDepth: 0
# NoneBot.utils 模块 # NoneBot.utils 模块
## `escape_tag(s)`
* **说明**
用于记录带颜色日志时转义 `<tag>` 类型特殊标签
* **参数**
* `s: str`: 需要转义的字符串
* **返回**
* `str`
## `run_sync(func)` ## `run_sync(func)`

View File

@@ -20,11 +20,7 @@ NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人
## 它如何工作? ## 它如何工作?
NoneBot 的运行离不开 酷 Q 和 CQHTTP 插件。酷 Q 扮演着「无头 QQ 客户端」的角色,它进行实际的消息、通知、请求的接收和发送,当 酷 Q 收到消息时,它将这个消息包装为一个事件(通知和请求同理),并通过它自己的插件机制将事件传送给 CQHTTP 插件,后者再根据其配置中的 `post_url``ws_reverse_url` 等项来将事件发送至 NoneBot。 ~~未填坑~~
在 NoneBot 收到事件前,它底层的 aiocqhttp 实际已经先看到了事件aiocqhttp 根据事件的类型信息,通知到 NoneBot 的相应函数。特别地,对于消息类型的事件,还将消息内容转换成了 `aiocqhttp.message.Message` 类型,以便处理。
NoneBot 的事件处理函数收到通知后对于不同类型的事件再做相应的预处理和解析然后调用对应的插件并向其提供适合此类事件的会话Session对象。NoneBot 插件的编写者要做的,就是利用 Session 对象中提供的数据,在插件的处理函数中实现所需的功能。
## 特色 ## 特色

View File

@@ -159,7 +159,7 @@ weather = on_command("天气", rule=to_me(), permission=Permission(), priority=5
- `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器 - `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器
- `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器 - `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器
- `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器 - `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器
- `on_regax(pattern_str)` ~ `on("message", regax(pattern_str))`: 正则匹配处理器 - `on_regex(pattern_str)` ~ `on("message", regex(pattern_str))`: 正则匹配处理器
#### 匹配规则 rule #### 匹配规则 rule

View File

@@ -3,11 +3,16 @@ NoneBot Api Reference
:模块索引: :模块索引:
- `nonebot <nonebot.html>`_ - `nonebot <nonebot.html>`_
- `nonebot.typing <typing.html>`_
- `nonebot.config <config.html>`_ - `nonebot.config <config.html>`_
- `nonebot.sched <sched.html>`_ - `nonebot.matcher <matcher.html>`_
- `nonebot.log <log.html>`_
- `nonebot.rule <rule.html>`_ - `nonebot.rule <rule.html>`_
- `nonebot.permission <permission.html>`_ - `nonebot.permission <permission.html>`_
- `nonebot.sched <sched.html>`_
- `nonebot.log <log.html>`_
- `nonebot.utils <utils.html>`_ - `nonebot.utils <utils.html>`_
- `nonebot.typing <typing.html>`_
- `nonebot.exception <exception.html>`_ - `nonebot.exception <exception.html>`_
- `nonebot.drivers <drivers/>`_
- `nonebot.drivers.fastapi <drivers/fastapi.html>`_
- `nonebot.adapters <adapters/>`_
- `nonebot.adapters.cqhttp <adapters/cqhttp.html>`_

View File

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

View File

@@ -0,0 +1,12 @@
---
contentSidebar: true
sidebarDepth: 0
---
NoneBot.adapters.cqhttp 模块
============================
.. automodule:: nonebot.adapters.cqhttp
:members:
:private-members:
:show-inheritance:

View File

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

View File

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

13
docs_build/matcher.rst Normal file
View File

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

View File

@@ -4,7 +4,7 @@ sidebarDepth: 0
--- ---
NoneBot.permission 模块 NoneBot.permission 模块
==================== =======================
.. automodule:: nonebot.permission .. automodule:: nonebot.permission
:members: :members:

View File

@@ -7,6 +7,7 @@ NoneBot.utils 模块
================== ==================
.. autofunction:: nonebot.utils.escape_tag
.. autodecorator:: nonebot.utils.run_sync .. autodecorator:: nonebot.utils.run_sync
.. autoclass:: nonebot.utils.DataclassEncoder .. autoclass:: nonebot.utils.DataclassEncoder
:show-inheritance: :show-inheritance:

View File

@@ -1,5 +1,27 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
快捷导入
========
为方便使用,``nonebot`` 模块从子模块导入了部分内容
- ``on_message`` => ``nonebot.plugin.on_message``
- ``on_notice`` => ``nonebot.plugin.on_notice``
- ``on_request`` => ``nonebot.plugin.on_request``
- ``on_metaevent`` => ``nonebot.plugin.on_metaevent``
- ``on_startswith`` => ``nonebot.plugin.on_startswith``
- ``on_endswith`` => ``nonebot.plugin.on_endswith``
- ``on_command`` => ``nonebot.plugin.on_command``
- ``on_regex`` => ``nonebot.plugin.on_regex``
- ``on_regex`` => ``nonebot.plugin.on_regex``
- ``on_regex`` => ``nonebot.plugin.on_regex``
- ``CommandGroup`` => ``nonebot.plugin.CommandGroup``
- ``load_plugin`` => ``nonebot.plugin.load_plugin``
- ``load_plugins`` => ``nonebot.plugin.load_plugins``
- ``load_builtin_plugins`` => ``nonebot.plugin.load_builtin_plugins``
- ``get_loaded_plugins`` => ``nonebot.plugin.get_loaded_plugins``
"""
import importlib import importlib
from nonebot.typing import Bot, Dict, Type, Union, Driver, Optional, NoReturn from nonebot.typing import Bot, Dict, Type, Union, Driver, Optional, NoReturn
@@ -109,6 +131,7 @@ def get_bots() -> Union[NoReturn, Dict[str, Bot]]:
from nonebot.sched import scheduler from nonebot.sched import scheduler
from nonebot.utils import escape_tag
from nonebot.config import Env, Config from nonebot.config import Env, Config
from nonebot.log import logger, default_filter from nonebot.log import logger, default_filter
from nonebot.adapters.cqhttp import Bot as CQBot from nonebot.adapters.cqhttp import Bot as CQBot
@@ -155,8 +178,8 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
_env_file=_env_file or f".env.{env.environment}") _env_file=_env_file or f".env.{env.environment}")
default_filter.level = "DEBUG" if config.debug else "INFO" default_filter.level = "DEBUG" if config.debug else "INFO"
logger.opt( logger.opt(colors=True).debug(
colors=True).debug(f"Loaded <y><b>Config</b></y>: {config.dict()}") f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}")
DriverClass: Type[Driver] = getattr( DriverClass: Type[Driver] = getattr(
importlib.import_module(config.driver), "Driver") importlib.import_module(config.driver), "Driver")
@@ -213,5 +236,5 @@ async def _start_scheduler():
from nonebot.plugin import on_message, on_notice, on_request, on_metaevent from nonebot.plugin import on_message, on_notice, on_request, on_metaevent
from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex, CommandGroup
from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins, get_loaded_plugins from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins, get_loaded_plugins

View File

@@ -1,5 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
协议适配基类
============
各协议请继承以下基类,并使用 ``driver.register_adapter`` 注册适配器
"""
import abc import abc
from functools import reduce, partial from functools import reduce, partial
@@ -11,6 +17,9 @@ from nonebot.typing import Any, Dict, Union, Optional, Callable, Iterable, Await
class BaseBot(abc.ABC): class BaseBot(abc.ABC):
"""
Bot 基类。用于处理上报消息,并提供 API 调用接口。
"""
@abc.abstractmethod @abc.abstractmethod
def __init__(self, def __init__(self,
@@ -19,12 +28,25 @@ class BaseBot(abc.ABC):
config: Config, config: Config,
self_id: str, self_id: str,
*, *,
websocket: WebSocket = None): websocket: Optional[WebSocket] = None):
"""
:参数:
* ``driver: Driver``: Driver 对象
* ``connection_type: str``: http 或者 websocket
* ``config: Config``: Config 对象
* ``self_id: str``: 机器人 ID
* ``websocket: Optional[WebSocket]``: Websocket 连接对象
"""
self.driver = driver self.driver = driver
"""Driver 对象"""
self.connection_type = connection_type self.connection_type = connection_type
"""连接类型"""
self.config = config self.config = config
"""Config 配置对象"""
self.self_id = self_id self.self_id = self_id
"""机器人 ID"""
self.websocket = websocket self.websocket = websocket
"""Websocket 连接对象"""
def __getattr__(self, name: str) -> Callable[..., Awaitable[Any]]: def __getattr__(self, name: str) -> Callable[..., Awaitable[Any]]:
return partial(self.call_api, name) return partial(self.call_api, name)
@@ -32,25 +54,61 @@ class BaseBot(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def type(self) -> str: def type(self) -> str:
"""Adapter 类型"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def handle_message(self, message: dict): async def handle_message(self, message: dict):
"""
:说明:
处理上报消息的函数,转换为 ``Event`` 事件后调用 ``nonebot.message.handle_event`` 进一步处理事件。
:参数:
* ``message: dict``: 收到的上报消息
"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def call_api(self, api: str, data: dict): async def call_api(self, api: str, **data):
"""
:说明:
调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
:参数:
* ``api: str``: API 名称
* ``**data``: API 数据
:示例:
.. code-block:: python
await bot.call_api("send_msg", data={"message": "hello world"})
await bot.send_msg(message="hello world")
"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def send(self, *args, **kwargs): async def send(self, event: "BaseEvent",
message: Union[str, "BaseMessage",
"BaseMessageSegment"], **kwargs):
"""
:说明:
调用机器人基础发送消息接口
:参数:
* ``event: Event``: 上报事件
* ``message: Union[str, Message, MessageSegment]``: 要发送的消息
* ``**kwargs``
"""
raise NotImplementedError raise NotImplementedError
# TODO: improve event
class BaseEvent(abc.ABC): class BaseEvent(abc.ABC):
"""
Event 基类。提供上报信息的关键信息,其余信息可从原始上报消息获取。
"""
def __init__(self, raw_event: dict): def __init__(self, raw_event: dict):
"""
:参数:
* ``raw_event: dict``: 原始上报消息
"""
self._raw_event = raw_event self._raw_event = raw_event
def __repr__(self) -> str: def __repr__(self) -> str:
@@ -58,31 +116,37 @@ class BaseEvent(abc.ABC):
@property @property
def raw_event(self) -> dict: def raw_event(self) -> dict:
"""原始上报消息"""
return self._raw_event return self._raw_event
@property @property
@abc.abstractmethod @abc.abstractmethod
def id(self) -> int: def id(self) -> int:
"""事件 ID"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def name(self) -> str: def name(self) -> str:
"""事件名称"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def self_id(self) -> str: def self_id(self) -> str:
"""机器人 ID"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def time(self) -> int: def time(self) -> int:
"""事件发生时间"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def type(self) -> str: def type(self) -> str:
"""事件主类型"""
raise NotImplementedError raise NotImplementedError
@type.setter @type.setter
@@ -93,6 +157,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def detail_type(self) -> str: def detail_type(self) -> str:
"""事件详细类型"""
raise NotImplementedError raise NotImplementedError
@detail_type.setter @detail_type.setter
@@ -103,6 +168,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def sub_type(self) -> Optional[str]: def sub_type(self) -> Optional[str]:
"""事件子类型"""
raise NotImplementedError raise NotImplementedError
@sub_type.setter @sub_type.setter
@@ -113,6 +179,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def user_id(self) -> Optional[int]: def user_id(self) -> Optional[int]:
"""触发事件的主体 ID"""
raise NotImplementedError raise NotImplementedError
@user_id.setter @user_id.setter
@@ -123,6 +190,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def group_id(self) -> Optional[int]: def group_id(self) -> Optional[int]:
"""触发事件的主体群 ID"""
raise NotImplementedError raise NotImplementedError
@group_id.setter @group_id.setter
@@ -133,6 +201,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def to_me(self) -> Optional[bool]: def to_me(self) -> Optional[bool]:
"""事件是否为发送给机器人的消息"""
raise NotImplementedError raise NotImplementedError
@to_me.setter @to_me.setter
@@ -143,6 +212,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def message(self) -> Optional[Message]: def message(self) -> Optional[Message]:
"""消息内容"""
raise NotImplementedError raise NotImplementedError
@message.setter @message.setter
@@ -153,6 +223,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def reply(self) -> Optional[dict]: def reply(self) -> Optional[dict]:
"""回复的消息"""
raise NotImplementedError raise NotImplementedError
@reply.setter @reply.setter
@@ -163,6 +234,7 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def raw_message(self) -> Optional[str]: def raw_message(self) -> Optional[str]:
"""原始消息"""
raise NotImplementedError raise NotImplementedError
@raw_message.setter @raw_message.setter
@@ -173,11 +245,13 @@ class BaseEvent(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def plain_text(self) -> Optional[str]: def plain_text(self) -> Optional[str]:
"""纯文本消息"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def sender(self) -> Optional[dict]: def sender(self) -> Optional[dict]:
"""消息发送者信息"""
raise NotImplementedError raise NotImplementedError
@sender.setter @sender.setter
@@ -188,8 +262,17 @@ class BaseEvent(abc.ABC):
@dataclass @dataclass
class BaseMessageSegment(abc.ABC): class BaseMessageSegment(abc.ABC):
"""消息段基类"""
type: str type: str
data: Dict[str, Union[str, list]] = field(default_factory=lambda: {}) """
- 类型: ``str``
- 说明: 消息段类型
"""
data: Dict[str, Any] = field(default_factory=lambda: {})
"""
- 类型: ``Dict[str, Union[str, list]]``
- 说明: 消息段数据
"""
@abc.abstractmethod @abc.abstractmethod
def __str__(self): def __str__(self):
@@ -199,14 +282,30 @@ class BaseMessageSegment(abc.ABC):
def __add__(self, other): def __add__(self, other):
raise NotImplementedError raise NotImplementedError
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
return setattr(self, key, value)
@classmethod
@abc.abstractmethod
def text(cls, text: str) -> "BaseMessageSegment":
return cls("text", {"text": text})
class BaseMessage(list, abc.ABC): class BaseMessage(list, abc.ABC):
"""消息数组"""
def __init__(self, def __init__(self,
message: Union[str, dict, list, BaseMessageSegment, message: Union[str, dict, list, BaseMessageSegment,
"BaseMessage"] = None, "BaseMessage"] = None,
*args, *args,
**kwargs): **kwargs):
"""
:参数:
* ``message: Union[str, dict, list, MessageSegment, Message]``: 消息内容
"""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if isinstance(message, (str, dict, list)): if isinstance(message, (str, dict, list)):
self.extend(self._construct(message)) self.extend(self._construct(message))
@@ -240,6 +339,12 @@ class BaseMessage(list, abc.ABC):
return result.__add__(self) return result.__add__(self)
def append(self, obj: Union[str, BaseMessageSegment]) -> "BaseMessage": def append(self, obj: Union[str, BaseMessageSegment]) -> "BaseMessage":
"""
:说明:
添加一个消息段到消息数组末尾
:参数:
* ``obj: Union[str, MessageSegment]``: 要添加的消息段
"""
if isinstance(obj, BaseMessageSegment): if isinstance(obj, BaseMessageSegment):
if obj.type == "text" and self and self[-1].type == "text": if obj.type == "text" and self and self[-1].type == "text":
self[-1].data["text"] += obj.data["text"] self[-1].data["text"] += obj.data["text"]
@@ -254,11 +359,21 @@ class BaseMessage(list, abc.ABC):
def extend( def extend(
self, obj: Union["BaseMessage", self, obj: Union["BaseMessage",
Iterable[BaseMessageSegment]]) -> "BaseMessage": Iterable[BaseMessageSegment]]) -> "BaseMessage":
"""
:说明:
拼接一个消息数组或多个消息段到消息数组末尾
:参数:
* ``obj: Union[Message, Iterable[MessageSegment]]``: 要添加的消息数组
"""
for segment in obj: for segment in obj:
self.append(segment) self.append(segment)
return self return self
def reduce(self) -> None: def reduce(self) -> None:
"""
:说明:
缩减消息数组,即拼接相邻纯文本消息段
"""
index = 0 index = 0
while index < len(self): while index < len(self):
if index > 0 and self[ if index > 0 and self[
@@ -269,8 +384,13 @@ class BaseMessage(list, abc.ABC):
index += 1 index += 1
def extract_plain_text(self) -> str: def extract_plain_text(self) -> str:
"""
:说明:
提取消息内纯文本消息
"""
def _concat(x: str, y: BaseMessageSegment) -> str: def _concat(x: str, y: BaseMessageSegment) -> str:
return f"{x} {y.data['text']}" if y.type == "text" else x return f"{x} {y.data['text']}" if y.type == "text" else x
return reduce(_concat, self, "") plain_text = reduce(_concat, self, "")
return plain_text[1:] if plain_text else plain_text

View File

@@ -28,14 +28,29 @@ from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
def log(level: str, message: str): def log(level: str, message: str):
"""
:说明:
用于打印 CQHTTP 日志。
:参数:
* ``level: str``: 日志等级
* ``message: str``: 日志信息
"""
return logger.opt(colors=True).log(level, "<m>CQHTTP</m> | " + message) return logger.opt(colors=True).log(level, "<m>CQHTTP</m> | " + message)
def escape(s: str, *, escape_comma: bool = True) -> str: def escape(s: str, *, escape_comma: bool = True) -> str:
""" """
:说明:
对字符串进行 CQ 码转义。 对字符串进行 CQ 码转义。
``escape_comma`` 参数控制是否转义逗号(``,``)。 :参数:
* ``s: str``: 需要转义的字符串
* ``escape_comma: bool``: 是否转义逗号(``,``)。
""" """
s = s.replace("&", "&amp;") \ s = s.replace("&", "&amp;") \
.replace("[", "&#91;") \ .replace("[", "&#91;") \
@@ -46,7 +61,15 @@ def escape(s: str, *, escape_comma: bool = True) -> str:
def unescape(s: str) -> str: def unescape(s: str) -> str:
"""对字符串进行 CQ 码去转义。""" """
:说明:
对字符串进行 CQ 码去转义。
:参数:
* ``s: str``: 需要转义的字符串
"""
return s.replace("&#44;", ",") \ return s.replace("&#44;", ",") \
.replace("&#91;", "[") \ .replace("&#91;", "[") \
.replace("&#93;", "]") \ .replace("&#93;", "]") \
@@ -54,10 +77,21 @@ def unescape(s: str) -> str:
def _b2s(b: Optional[bool]) -> Optional[str]: def _b2s(b: Optional[bool]) -> Optional[str]:
"""转换布尔值为字符串。"""
return b if b is None else str(b).lower() return b if b is None else str(b).lower()
async def _check_reply(bot: "Bot", event: "Event"): async def _check_reply(bot: "Bot", event: "Event"):
"""
:说明:
检查消息中存在的回复,去除并赋值 ``event.reply``, ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message": if event.type != "message":
return return
@@ -74,6 +108,16 @@ async def _check_reply(bot: "Bot", event: "Event"):
def _check_at_me(bot: "Bot", event: "Event"): def _check_at_me(bot: "Bot", event: "Event"):
"""
:说明:
检查消息开头或结尾是否存在 @机器人,去除并赋值 ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message": if event.type != "message":
return return
@@ -119,6 +163,16 @@ def _check_at_me(bot: "Bot", event: "Event"):
def _check_nickname(bot: "Bot", event: "Event"): def _check_nickname(bot: "Bot", event: "Event"):
"""
:说明:
检查消息开头是否存在,去除并赋值 ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message": if event.type != "message":
return return
@@ -128,13 +182,13 @@ def _check_nickname(bot: "Bot", event: "Event"):
first_text = first_msg_seg.data["text"] first_text = first_msg_seg.data["text"]
if bot.config.NICKNAME: if bot.config.nickname:
# check if the user is calling me with my nickname # check if the user is calling me with my nickname
if isinstance(bot.config.NICKNAME, str) or \ if isinstance(bot.config.nickname, str) or \
not isinstance(bot.config.NICKNAME, Iterable): not isinstance(bot.config.nickname, Iterable):
nicknames = (bot.config.NICKNAME,) nicknames = (bot.config.nickname,)
else: else:
nicknames = filter(lambda n: n, bot.config.NICKNAME) nicknames = filter(lambda n: n, bot.config.nickname)
nickname_regex = "|".join(nicknames) nickname_regex = "|".join(nicknames)
m = re.search(rf"^({nickname_regex})([\s,]*|$)", first_text, m = re.search(rf"^({nickname_regex})([\s,]*|$)", first_text,
re.IGNORECASE) re.IGNORECASE)
@@ -145,7 +199,25 @@ def _check_nickname(bot: "Bot", event: "Event"):
first_msg_seg.data["text"] = first_text[m.end():] first_msg_seg.data["text"] = first_text[m.end():]
def _handle_api_result(result: Optional[Dict[str, Any]]) -> Any: def _handle_api_result(
result: Optional[Dict[str, Any]]) -> Union[Any, NoReturn]:
"""
:说明:
处理 API 请求返回值。
:参数:
* ``result: Optional[Dict[str, Any]]``: API 返回数据
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``ActionFailed``: API 调用失败
"""
if isinstance(result, dict): if isinstance(result, dict):
if result.get("status") == "failed": if result.get("status") == "failed":
raise ActionFailed(retcode=result.get("retcode")) raise ActionFailed(retcode=result.get("retcode"))
@@ -183,6 +255,9 @@ class ResultStore:
class Bot(BaseBot): class Bot(BaseBot):
"""
CQHTTP 协议 Bot 适配。继承属性参考 `BaseBot <./#class-basebot>`_ 。
"""
def __init__(self, def __init__(self,
driver: Driver, driver: Driver,
@@ -190,7 +265,7 @@ class Bot(BaseBot):
config: Config, config: Config,
self_id: str, self_id: str,
*, *,
websocket: WebSocket = None): websocket: Optional[WebSocket] = None):
if connection_type not in ["http", "websocket"]: if connection_type not in ["http", "websocket"]:
raise ValueError("Unsupported connection type") raise ValueError("Unsupported connection type")
@@ -203,10 +278,18 @@ class Bot(BaseBot):
@property @property
@overrides(BaseBot) @overrides(BaseBot)
def type(self) -> str: def type(self) -> str:
"""
- 返回: ``"cqhttp"``
"""
return "cqhttp" return "cqhttp"
@overrides(BaseBot) @overrides(BaseBot)
async def handle_message(self, message: dict): async def handle_message(self, message: dict):
"""
:说明:
调用 `_check_reply <#async-check-reply-bot-event>`_, `_check_at_me <#check-at-me-bot-event>`_, `_check_nickname <#check-nickname-bot-event>`_ 处理事件并转换为 `Event <#class-event>`_
"""
if not message: if not message:
return return
@@ -230,6 +313,25 @@ class Bot(BaseBot):
@overrides(BaseBot) @overrides(BaseBot)
async def call_api(self, api: str, **data) -> Union[Any, NoReturn]: async def call_api(self, api: str, **data) -> Union[Any, NoReturn]:
"""
:说明:
调用 CQHTTP 协议 API
:参数:
* ``api: str``: API 名称
* ``**data: Any``: API 参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
if "self_id" in data: if "self_id" in data:
self_id = data.pop("self_id") self_id = data.pop("self_id")
if self_id: if self_id:
@@ -278,12 +380,36 @@ class Bot(BaseBot):
raise NetworkError("HTTP request failed") raise NetworkError("HTTP request failed")
@overrides(BaseBot) @overrides(BaseBot)
async def send(self, event: "Event", message: Union[str, "Message", async def send(self,
"MessageSegment"], event: "Event",
message: Union[str, "Message", "MessageSegment"],
at_sender: bool = False,
**kwargs) -> Union[Any, NoReturn]: **kwargs) -> Union[Any, NoReturn]:
"""
:说明:
根据 ``event`` 向触发事件的主体发送消息。
:参数:
* ``event: Event``: Event 对象
* ``message: Union[str, Message, MessageSegment]``: 要发送的消息
* ``at_sender: bool``: 是否 @ 事件主体
* ``**kwargs``: 覆盖默认参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``ValueError``: 缺少 ``user_id``, ``group_id``
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
msg = message if isinstance(message, Message) else Message(message) msg = message if isinstance(message, Message) else Message(message)
at_sender = kwargs.pop("at_sender", False) and bool(event.user_id) at_sender = at_sender and bool(event.user_id)
params = {} params = {}
if event.user_id: if event.user_id:
@@ -309,6 +435,9 @@ class Bot(BaseBot):
class Event(BaseEvent): class Event(BaseEvent):
"""
CQHTTP 协议 Event 适配。继承属性参考 `BaseEvent <./#class-baseevent>`_ 。
"""
def __init__(self, raw_event: dict): def __init__(self, raw_event: dict):
if "message" in raw_event: if "message" in raw_event:
@@ -319,11 +448,19 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def id(self) -> Optional[int]: def id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件/消息 ID
"""
return self._raw_event.get("message_id") or self._raw_event.get("flag") return self._raw_event.get("message_id") or self._raw_event.get("flag")
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def name(self) -> str: def name(self) -> str:
"""
- 类型: ``str``
- 说明: 事件名称,由类型与 ``.`` 组合而成
"""
n = self.type + "." + self.detail_type n = self.type + "." + self.detail_type
if self.sub_type: if self.sub_type:
n += "." + self.sub_type n += "." + self.sub_type
@@ -332,16 +469,28 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def self_id(self) -> str: def self_id(self) -> str:
"""
- 类型: ``str``
- 说明: 机器人自身 ID
"""
return str(self._raw_event["self_id"]) return str(self._raw_event["self_id"])
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def time(self) -> int: def time(self) -> int:
"""
- 类型: ``int``
- 说明: 事件发生时间
"""
return self._raw_event["time"] return self._raw_event["time"]
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def type(self) -> str: def type(self) -> str:
"""
- 类型: ``str``
- 说明: 事件类型
"""
return self._raw_event["post_type"] return self._raw_event["post_type"]
@type.setter @type.setter
@@ -352,6 +501,10 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def detail_type(self) -> str: def detail_type(self) -> str:
"""
- 类型: ``str``
- 说明: 事件详细类型
"""
return self._raw_event[f"{self.type}_type"] return self._raw_event[f"{self.type}_type"]
@detail_type.setter @detail_type.setter
@@ -362,9 +515,13 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def sub_type(self) -> Optional[str]: def sub_type(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 事件子类型
"""
return self._raw_event.get("sub_type") return self._raw_event.get("sub_type")
@type.setter @sub_type.setter
@overrides(BaseEvent) @overrides(BaseEvent)
def sub_type(self, value) -> None: def sub_type(self, value) -> None:
self._raw_event["sub_type"] = value self._raw_event["sub_type"] = value
@@ -372,6 +529,10 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def user_id(self) -> Optional[int]: def user_id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件主体 ID
"""
return self._raw_event.get("user_id") return self._raw_event.get("user_id")
@user_id.setter @user_id.setter
@@ -382,6 +543,10 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def group_id(self) -> Optional[int]: def group_id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件主体群 ID
"""
return self._raw_event.get("group_id") return self._raw_event.get("group_id")
@group_id.setter @group_id.setter
@@ -392,6 +557,10 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def to_me(self) -> Optional[bool]: def to_me(self) -> Optional[bool]:
"""
- 类型: ``Optional[bool]``
- 说明: 消息是否与机器人相关
"""
return self._raw_event.get("to_me") return self._raw_event.get("to_me")
@to_me.setter @to_me.setter
@@ -402,6 +571,10 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def message(self) -> Optional["Message"]: def message(self) -> Optional["Message"]:
"""
- 类型: ``Optional[Message]``
- 说明: 消息内容
"""
return self._raw_event.get("message") return self._raw_event.get("message")
@message.setter @message.setter
@@ -412,6 +585,10 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def reply(self) -> Optional[dict]: def reply(self) -> Optional[dict]:
"""
- 类型: ``Optional[dict]``
- 说明: 回复消息详情
"""
return self._raw_event.get("reply") return self._raw_event.get("reply")
@reply.setter @reply.setter
@@ -422,6 +599,10 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def raw_message(self) -> Optional[str]: def raw_message(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 原始消息
"""
return self._raw_event.get("raw_message") return self._raw_event.get("raw_message")
@raw_message.setter @raw_message.setter
@@ -432,11 +613,19 @@ class Event(BaseEvent):
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def plain_text(self) -> Optional[str]: def plain_text(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 纯文本消息内容
"""
return self.message and self.message.extract_plain_text() return self.message and self.message.extract_plain_text()
@property @property
@overrides(BaseEvent) @overrides(BaseEvent)
def sender(self) -> Optional[dict]: def sender(self) -> Optional[dict]:
"""
- 类型: ``Optional[dict]``
- 说明: 消息发送者信息
"""
return self._raw_event.get("sender") return self._raw_event.get("sender")
@sender.setter @sender.setter
@@ -448,9 +637,9 @@ class Event(BaseEvent):
class MessageSegment(BaseMessageSegment): class MessageSegment(BaseMessageSegment):
@overrides(BaseMessageSegment) @overrides(BaseMessageSegment)
def __init__(self, type: str, data: Dict[str, Union[str, list]]) -> None: def __init__(self, type: str, data: Dict[str, Any]) -> None:
if type == "text": if type == "text":
data["text"] = unescape(data["text"]) # type: ignore data["text"] = unescape(data["text"])
super().__init__(type=type, data=data) super().__init__(type=type, data=data)
@overrides(BaseMessageSegment) @overrides(BaseMessageSegment)

View File

@@ -1,15 +1,91 @@
import asyncio
from nonebot.config import Config
from nonebot.adapters import BaseBot from nonebot.adapters import BaseBot
from nonebot.typing import Any, Dict, List, Union, Message, Optional from nonebot.typing import Any, Dict, List, Union, Driver, Optional, NoReturn, WebSocket, Iterable
def log(level: str, message: str):
...
def escape(s: str, *, escape_comma: bool = ...) -> str:
...
def unescape(s: str) -> str:
...
def _b2s(b: Optional[bool]) -> Optional[str]:
...
async def _check_reply(bot: "Bot", event: "Event"):
...
def _check_at_me(bot: "Bot", event: "Event"):
...
def _check_nickname(bot: "Bot", event: "Event"):
...
def _handle_api_result(
result: Optional[Dict[str, Any]]) -> Union[Any, NoReturn]:
...
class ResultStore:
_seq: int = ...
_futures: Dict[int, asyncio.Future] = ...
@classmethod
def get_seq(cls) -> int:
...
@classmethod
def add_result(cls, result: Dict[str, Any]):
...
@classmethod
async def fetch(cls, seq: int, timeout: Optional[float]) -> Dict[str, Any]:
...
class Bot(BaseBot): class Bot(BaseBot):
def __init__(self,
driver: Driver,
connection_type: str,
config: Config,
self_id: str,
*,
websocket: WebSocket = None):
...
def type(self) -> str:
...
async def handle_message(self, message: dict):
...
async def call_api(self, api: str, **data) -> Union[Any, NoReturn]:
...
async def send(self, event: "Event", message: Union[str, "Message",
"MessageSegment"],
**kwargs) -> Union[Any, NoReturn]:
...
async def send_private_msg(self, async def send_private_msg(self,
*, *,
user_id: int, user_id: int,
message: Union[str, Message], message: Union[str, Message],
auto_escape: bool = False, auto_escape: bool = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -28,8 +104,8 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
message: Union[str, Message], message: Union[str, Message],
auto_escape: bool = False, auto_escape: bool = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -46,12 +122,12 @@ class Bot(BaseBot):
async def send_msg(self, async def send_msg(self,
*, *,
message_type: Optional[str] = None, message_type: Optional[str] = ...,
user_id: Optional[int] = None, user_id: Optional[int] = ...,
group_id: Optional[int] = None, group_id: Optional[int] = ...,
message: Union[str, Message], message: Union[str, Message],
auto_escape: bool = False, auto_escape: bool = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -71,7 +147,7 @@ class Bot(BaseBot):
async def delete_msg(self, async def delete_msg(self,
*, *,
message_id: int, message_id: int,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -87,7 +163,7 @@ class Bot(BaseBot):
async def get_msg(self, async def get_msg(self,
*, *,
message_id: int, message_id: int,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -103,7 +179,7 @@ class Bot(BaseBot):
async def get_forward_msg(self, async def get_forward_msg(self,
*, *,
id: int, id: int,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -119,8 +195,8 @@ class Bot(BaseBot):
async def send_like(self, async def send_like(self,
*, *,
user_id: int, user_id: int,
times: int = 1, times: int = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -138,8 +214,8 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
user_id: int, user_id: int,
reject_add_request: bool = False, reject_add_request: bool = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -158,8 +234,8 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
user_id: int, user_id: int,
duration: int = 30 * 60, duration: int = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -177,11 +253,10 @@ class Bot(BaseBot):
async def set_group_anonymous_ban(self, async def set_group_anonymous_ban(self,
*, *,
group_id: int, group_id: int,
anonymous: Optional[Dict[str, anonymous: Optional[Dict[str, Any]] = ...,
Any]] = None, anonymous_flag: Optional[str] = ...,
anonymous_flag: Optional[str] = None, duration: int = ...,
duration: int = 30 * 60, self_id: Optional[int] = ...) -> None:
self_id: Optional[int] = None) -> None:
""" """
:说明: :说明:
@@ -200,8 +275,8 @@ class Bot(BaseBot):
async def set_group_whole_ban(self, async def set_group_whole_ban(self,
*, *,
group_id: int, group_id: int,
enable: bool = True, enable: bool = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -219,8 +294,8 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
user_id: int, user_id: int,
enable: bool = True, enable: bool = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -238,8 +313,8 @@ class Bot(BaseBot):
async def set_group_anonymous(self, async def set_group_anonymous(self,
*, *,
group_id: int, group_id: int,
enable: bool = True, enable: bool = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -257,8 +332,8 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
user_id: int, user_id: int,
card: str = "", card: str = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -277,7 +352,7 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
group_name: str, group_name: str,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -294,8 +369,8 @@ class Bot(BaseBot):
async def set_group_leave(self, async def set_group_leave(self,
*, *,
group_id: int, group_id: int,
is_dismiss: bool = False, is_dismiss: bool = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -313,9 +388,9 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
user_id: int, user_id: int,
special_title: str = "", special_title: str = ...,
duration: int = -1, duration: int = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -334,9 +409,9 @@ class Bot(BaseBot):
async def set_friend_add_request(self, async def set_friend_add_request(self,
*, *,
flag: str, flag: str,
approve: bool = True, approve: bool = ...,
remark: str = "", remark: str = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -355,9 +430,9 @@ class Bot(BaseBot):
*, *,
flag: str, flag: str,
sub_type: str, sub_type: str,
approve: bool = True, approve: bool = ...,
reason: str = "", reason: str = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -375,7 +450,7 @@ class Bot(BaseBot):
async def get_login_info(self, async def get_login_info(self,
*, *,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -387,12 +462,11 @@ class Bot(BaseBot):
""" """
... ...
async def get_stranger_info( async def get_stranger_info(self,
self,
*, *,
user_id: int, user_id: int,
no_cache: bool = False, no_cache: bool = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -408,7 +482,7 @@ class Bot(BaseBot):
async def get_friend_list(self, async def get_friend_list(self,
*, *,
self_id: Optional[int] = None self_id: Optional[int] = ...
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
:说明: :说明:
@@ -424,8 +498,8 @@ class Bot(BaseBot):
async def get_group_info(self, async def get_group_info(self,
*, *,
group_id: int, group_id: int,
no_cache: bool = False, no_cache: bool = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -441,7 +515,7 @@ class Bot(BaseBot):
async def get_group_list(self, async def get_group_list(self,
*, *,
self_id: Optional[int] = None self_id: Optional[int] = ...
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
:说明: :说明:
@@ -459,8 +533,8 @@ class Bot(BaseBot):
*, *,
group_id: int, group_id: int,
user_id: int, user_id: int,
no_cache: bool = False, no_cache: bool = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -479,7 +553,7 @@ class Bot(BaseBot):
self, self,
*, *,
group_id: int, group_id: int,
self_id: Optional[int] = None) -> List[Dict[str, Any]]: self_id: Optional[int] = ...) -> List[Dict[str, Any]]:
""" """
:说明: :说明:
@@ -492,12 +566,12 @@ class Bot(BaseBot):
""" """
... ...
async def get_group_honor_info( async def get_group_honor_info(self,
self,
*, *,
group_id: int, group_id: int,
type: str = "all", type: str = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...
) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -513,8 +587,8 @@ class Bot(BaseBot):
async def get_cookies(self, async def get_cookies(self,
*, *,
domain: str = "", domain: str = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -529,7 +603,7 @@ class Bot(BaseBot):
async def get_csrf_token(self, async def get_csrf_token(self,
*, *,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -543,8 +617,8 @@ class Bot(BaseBot):
async def get_credentials(self, async def get_credentials(self,
*, *,
domain: str = "", domain: str = ...,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -561,7 +635,7 @@ class Bot(BaseBot):
*, *,
file: str, file: str,
out_format: str, out_format: str,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -578,7 +652,7 @@ class Bot(BaseBot):
async def get_image(self, async def get_image(self,
*, *,
file: str, file: str,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -593,7 +667,7 @@ class Bot(BaseBot):
async def can_send_image(self, async def can_send_image(self,
*, *,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -607,7 +681,7 @@ class Bot(BaseBot):
async def can_send_record(self, async def can_send_record(self,
*, *,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -621,7 +695,7 @@ class Bot(BaseBot):
async def get_status(self, async def get_status(self,
*, *,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -635,7 +709,7 @@ class Bot(BaseBot):
async def get_version_info(self, async def get_version_info(self,
*, *,
self_id: Optional[int] = None) -> Dict[str, Any]: self_id: Optional[int] = ...) -> Dict[str, Any]:
""" """
:说明: :说明:
@@ -649,8 +723,8 @@ class Bot(BaseBot):
async def set_restart(self, async def set_restart(self,
*, *,
delay: int = 0, delay: int = ...,
self_id: Optional[int] = None) -> None: self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -663,7 +737,7 @@ class Bot(BaseBot):
""" """
... ...
async def clean_cache(self, *, self_id: Optional[int] = None) -> None: async def clean_cache(self, *, self_id: Optional[int] = ...) -> None:
""" """
:说明: :说明:
@@ -674,3 +748,242 @@ class Bot(BaseBot):
* ``self_id``: 机器人 QQ 号 * ``self_id``: 机器人 QQ 号
""" """
... ...
class Event:
def __init__(self, raw_event: dict):
...
@property
def id(self) -> Optional[int]:
...
@property
def name(self) -> str:
...
@property
def self_id(self) -> str:
...
@property
def time(self) -> int:
...
@property
def type(self) -> str:
...
@type.setter
def type(self, value) -> None:
...
@property
def detail_type(self) -> str:
...
@detail_type.setter
def detail_type(self, value) -> None:
...
@property
def sub_type(self) -> Optional[str]:
...
@sub_type.setter
def sub_type(self, value) -> None:
...
@property
def user_id(self) -> Optional[int]:
...
@user_id.setter
def user_id(self, value) -> None:
...
@property
def group_id(self) -> Optional[int]:
...
@group_id.setter
def group_id(self, value) -> None:
...
@property
def to_me(self) -> Optional[bool]:
...
@to_me.setter
def to_me(self, value) -> None:
...
@property
def message(self) -> Optional["Message"]:
...
@message.setter
def message(self, value) -> None:
...
@property
def reply(self) -> Optional[dict]:
...
@reply.setter
def reply(self, value) -> None:
...
@property
def raw_message(self) -> Optional[str]:
...
@raw_message.setter
def raw_message(self, value) -> None:
...
@property
def plain_text(self) -> Optional[str]:
...
@property
def sender(self) -> Optional[dict]:
...
@sender.setter
def sender(self, value) -> None:
...
class MessageSegment:
def __init__(self, type: str, data: Dict[str, Any]) -> None:
...
def __str__(self):
...
def __add__(self, other) -> "Message":
...
@staticmethod
def anonymous(ignore_failure: Optional[bool] = ...) -> "MessageSegment":
...
@staticmethod
def at(user_id: Union[int, str]) -> "MessageSegment":
...
@staticmethod
def contact_group(group_id: int) -> "MessageSegment":
...
@staticmethod
def contact_user(user_id: int) -> "MessageSegment":
...
@staticmethod
def dice() -> "MessageSegment":
...
@staticmethod
def face(id_: int) -> "MessageSegment":
...
@staticmethod
def forward(id_: str) -> "MessageSegment":
...
@staticmethod
def image(file: str,
type_: Optional[str] = ...,
cache: bool = ...,
proxy: bool = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def json(data: str) -> "MessageSegment":
...
@staticmethod
def location(latitude: float,
longitude: float,
title: Optional[str] = ...,
content: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def music(type_: str, id_: int) -> "MessageSegment":
...
@staticmethod
def music_custom(url: str,
audio: str,
title: str,
content: Optional[str] = ...,
img_url: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def node(id_: int) -> "MessageSegment":
...
@staticmethod
def node_custom(user_id: int, nickname: str,
content: Union[str, "Message"]) -> "MessageSegment":
...
@staticmethod
def poke(type_: str, id_: str) -> "MessageSegment":
...
@staticmethod
def record(file: str,
magic: Optional[bool] = ...,
cache: Optional[bool] = ...,
proxy: Optional[bool] = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def reply(id_: int) -> "MessageSegment":
...
@staticmethod
def rps() -> "MessageSegment":
...
@staticmethod
def shake() -> "MessageSegment":
...
@staticmethod
def share(url: str = ...,
title: str = ...,
content: Optional[str] = ...,
img_url: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def text(text: str) -> "MessageSegment":
...
@staticmethod
def video(file: str,
cache: Optional[bool] = ...,
proxy: Optional[bool] = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def xml(data: str) -> "MessageSegment":
...
class Message:
@staticmethod
def _construct(msg: Union[str, dict, list]) -> Iterable[MessageSegment]:
...

View File

@@ -211,10 +211,10 @@ class Config(BaseConfig):
SUPER_USERS=[12345789] SUPER_USERS=[12345789]
""" """
nickname: Union[str, Set[str]] = "" nickname: Set[str] = set()
""" """
- 类型: ``Union[str, Set[str]]`` - 类型: ``Set[str]``
- 默认值: ``""`` - 默认值: ``set()``
- 说明: - 说明:
机器人昵称。 机器人昵称。
""" """

View File

@@ -1,5 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
后端驱动适配基类
===============
各驱动请继承以下基类
"""
import abc import abc
@@ -9,7 +15,15 @@ from nonebot.typing import Bot, Dict, Type, Union, Optional, Callable
class BaseDriver(abc.ABC): class BaseDriver(abc.ABC):
"""
Driver 基类。将后端框架封装,以满足适配器使用。
"""
_adapters: Dict[str, Type[Bot]] = {} _adapters: Dict[str, Type[Bot]] = {}
"""
:类型: ``Dict[str, Type[Bot]]``
:说明: 已注册的适配器列表
"""
@abc.abstractmethod @abc.abstractmethod
def __init__(self, env: Env, config: Config): def __init__(self, env: Env, config: Config):

View File

@@ -200,8 +200,8 @@ class Driver(BaseDriver):
websocket=ws) websocket=ws)
else: else:
logger.warning("Unknown adapter") logger.warning("Unknown adapter")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, await ws.close(code=status.WS_1008_POLICY_VIOLATION)
detail="adapter not found") return
await ws.accept() await ws.accept()
self._clients[x_self_id] = bot self._clients[x_self_id] = bot

View File

@@ -1,5 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
事件响应器
==========
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行 对话 。
"""
from nonebot.log import logger from nonebot.log import logger
import typing import typing
@@ -16,6 +22,10 @@ from nonebot.typing import Bot, Event, Handler, Message, ArgsParser, MessageSegm
from nonebot.exception import PausedException, RejectedException, FinishedException from nonebot.exception import PausedException, RejectedException, FinishedException
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list) matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
"""
:类型: ``Dict[int, List[Type[Matcher]]]``
:说明: 用于存储当前所有的事件响应器
"""
current_bot: ContextVar = ContextVar("current_bot") current_bot: ContextVar = ContextVar("current_bot")
current_event: ContextVar = ContextVar("current_event") current_event: ContextVar = ContextVar("current_event")
@@ -32,22 +42,65 @@ class MatcherMeta(type):
class Matcher(metaclass=MatcherMeta): class Matcher(metaclass=MatcherMeta):
"""`Matcher`类 """事件响应器类"""
"""
module: Optional[str] = None module: Optional[str] = None
"""
:类型: ``Optional[str]``
:说明: 事件响应器所在模块名称
"""
type: str = "" type: str = ""
"""
:类型: ``str``
:说明: 事件响应器类型
"""
rule: Rule = Rule() rule: Rule = Rule()
"""
:类型: ``Rule``
:说明: 事件响应器匹配规则
"""
permission: Permission = Permission() permission: Permission = Permission()
"""
:类型: ``Permission``
:说明: 事件响应器触发权限
"""
handlers: List[Handler] = [] handlers: List[Handler] = []
temp: bool = False """
expire_time: Optional[datetime] = None :类型: ``List[Handler]``
:说明: 事件响应器拥有的事件处理函数列表
"""
priority: int = 1 priority: int = 1
"""
:类型: ``int``
:说明: 事件响应器优先级
"""
block: bool = False block: bool = False
"""
:类型: ``bool``
:说明: 事件响应器是否阻止事件传播
"""
temp: bool = False
"""
:类型: ``bool``
:说明: 事件响应器是否为临时
"""
expire_time: Optional[datetime] = None
"""
:类型: ``Optional[datetime]``
:说明: 事件响应器过期时间点
"""
_default_state: dict = {} _default_state: dict = {}
"""
:类型: ``dict``
:说明: 事件响应器默认状态
"""
_default_parser: Optional[ArgsParser] = None _default_parser: Optional[ArgsParser] = None
"""
:类型: ``Optional[ArgsParser]``
:说明: 事件响应器默认参数解析函数
"""
def __init__(self): def __init__(self):
"""实例化 Matcher 以便运行 """实例化 Matcher 以便运行
@@ -65,9 +118,9 @@ class Matcher(metaclass=MatcherMeta):
@classmethod @classmethod
def new(cls, def new(cls,
type_: str = "", type_: str = "",
rule: Rule = Rule(), rule: Optional[Rule] = None,
permission: Permission = Permission(), permission: Optional[Permission] = None,
handlers: Optional[list] = None, handlers: Optional[List[Handler]] = None,
temp: bool = False, temp: bool = False,
priority: int = 1, priority: int = 1,
block: bool = False, block: bool = False,
@@ -75,18 +128,30 @@ class Matcher(metaclass=MatcherMeta):
module: Optional[str] = None, module: Optional[str] = None,
default_state: Optional[dict] = None, default_state: Optional[dict] = None,
expire_time: Optional[datetime] = None) -> Type["Matcher"]: expire_time: Optional[datetime] = None) -> Type["Matcher"]:
"""创建新的 Matcher """
:说明:
Returns: 创建一个新的事件响应器,并存储至 `matchers <#matchers>`_
Type["Matcher"]: 新的 Matcher 类 :参数:
* ``type_: str``: 事件响应器类型,与 ``event.type`` 一致时触发,空字符串表示任意
* ``rule: Optional[Rule]``: 匹配规则
* ``permission: Optional[Permission]``: 权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器,即触发一次后删除
* ``priority: int``: 响应优先级
* ``block: bool``: 是否阻止事件向更低优先级的响应器传播
* ``module: Optional[str]``: 事件响应器所在模块名称
* ``default_state: Optional[dict]``: 默认状态 ``state``
* ``expire_time: Optional[datetime]``: 事件响应器最终有效时间点,过时即被删除
:返回:
- ``Type[Matcher]``: 新的事件响应器类
""" """
NewMatcher = type( NewMatcher = type(
"Matcher", (Matcher,), { "Matcher", (Matcher,), {
"module": module, "module": module,
"type": type_, "type": type_,
"rule": rule, "rule": rule or Rule(),
"permission": permission, "permission": permission or Permission(),
"handlers": handlers or [], "handlers": handlers or [],
"temp": temp, "temp": temp,
"expire_time": expire_time, "expire_time": expire_time,
@@ -101,29 +166,51 @@ class Matcher(metaclass=MatcherMeta):
@classmethod @classmethod
async def check_perm(cls, bot: Bot, event: Event) -> bool: async def check_perm(cls, bot: Bot, event: Event) -> bool:
"""
:说明:
检查是否满足触发权限
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: 上报事件
:返回:
- ``bool``: 是否满足权限
"""
return await cls.permission(bot, event) return await cls.permission(bot, event)
@classmethod @classmethod
async def check_rule(cls, bot: Bot, event: Event, state: dict) -> bool: async def check_rule(cls, bot: Bot, event: Event, state: dict) -> bool:
"""检查 Matcher 的 Rule 是否成立 """
:说明:
Args: 检查是否满足匹配规则
event (Event): 消息事件 :参数:
* ``bot: Bot``: Bot 对象
Returns: * ``event: Event``: 上报事件
bool: 条件成立与否 * ``state: dict``: 当前状态
:返回:
- ``bool``: 是否满足匹配规则
""" """
return (event.type == (cls.type or event.type) and return (event.type == (cls.type or event.type) and
await cls.rule(bot, event, state)) await cls.rule(bot, event, state))
@classmethod @classmethod
def args_parser(cls, func: ArgsParser) -> ArgsParser: def args_parser(cls, func: ArgsParser) -> ArgsParser:
"""
:说明:
装饰一个函数来更改当前事件响应器的默认参数解析函数
:参数:
* ``func: ArgsParser``: 参数解析函数
"""
cls._default_parser = func cls._default_parser = func
return func return func
@classmethod @classmethod
def handle(cls) -> Callable[[Handler], Handler]: def handle(cls) -> Callable[[Handler], Handler]:
"""直接处理消息事件""" """
:说明:
装饰一个函数来向事件响应器直接添加一个处理函数
:参数:
* 无
"""
def _decorator(func: Handler) -> Handler: def _decorator(func: Handler) -> Handler:
cls.handlers.append(func) cls.handlers.append(func)
@@ -133,7 +220,12 @@ class Matcher(metaclass=MatcherMeta):
@classmethod @classmethod
def receive(cls) -> Callable[[Handler], Handler]: def receive(cls) -> Callable[[Handler], Handler]:
"""接收一条新消息并处理""" """
:说明:
装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数
:参数:
* 无
"""
async def _receive(bot: Bot, event: Event, state: dict) -> NoReturn: async def _receive(bot: Bot, event: Event, state: dict) -> NoReturn:
raise PausedException raise PausedException
@@ -154,20 +246,31 @@ class Matcher(metaclass=MatcherMeta):
def got( def got(
cls, cls,
key: str, key: str,
prompt: Optional[str] = None, prompt: Optional[Union[str, Message, MessageSegment]] = None,
args_parser: Optional[ArgsParser] = None args_parser: Optional[ArgsParser] = None
) -> Callable[[Handler], Handler]: ) -> Callable[[Handler], Handler]:
"""
:说明:
装饰一个函数来指示 NoneBot 当要获取的 ``key`` 不存在时接收用户新的一条消息并经过 ``ArgsParser`` 处理后再运行该函数,如果 ``key`` 已存在则直接继续运行
:参数:
* ``key: str``: 参数名
* ``prompt: Optional[Union[str, Message, MessageSegment]]``: 在参数不存在时向用户发送的消息
* ``args_parser: Optional[ArgsParser]``: 可选参数解析函数,空则使用默认解析函数
"""
async def _key_getter(bot: Bot, event: Event, state: dict): async def _key_getter(bot: Bot, event: Event, state: dict):
if key not in state:
state["_current_key"] = key state["_current_key"] = key
if key not in state:
if prompt: if prompt:
await bot.send(event=event, message=prompt) await bot.send(event=event, message=prompt)
raise PausedException raise PausedException
else:
state["_skip_key"] = True
async def _key_parser(bot: Bot, event: Event, state: dict): async def _key_parser(bot: Bot, event: Event, state: dict):
# if key in state: if key in state and state.get("_skip_key"):
# return del state["_skip_key"]
return
parser = args_parser or cls._default_parser parser = args_parser or cls._default_parser
if parser: if parser:
await parser(bot, event, state) await parser(bot, event, state)
@@ -185,6 +288,8 @@ class Matcher(metaclass=MatcherMeta):
async def wrapper(bot: Bot, event: Event, state: dict): async def wrapper(bot: Bot, event: Event, state: dict):
await parser(bot, event, state) await parser(bot, event, state)
await func(bot, event, state) await func(bot, event, state)
if "_current_key" in state:
del state["_current_key"]
cls.handlers.append(wrapper) cls.handlers.append(wrapper)
@@ -192,15 +297,33 @@ class Matcher(metaclass=MatcherMeta):
return _decorator return _decorator
@classmethod
async def send(cls, message: Union[str, Message, MessageSegment]):
"""
:说明:
发送一条消息给当前交互用户
:参数:
* ``message: Union[str, Message, MessageSegment]``: 消息内容
"""
bot = current_bot.get()
event = current_event.get()
await bot.send(event=event, message=message)
@classmethod @classmethod
async def finish( async def finish(
cls, cls,
prompt: Optional[Union[str, Message, message: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn: MessageSegment]] = None) -> NoReturn:
bot: Bot = current_bot.get() """
event: Event = current_event.get() :说明:
if prompt: 发送一条消息给当前交互用户并结束当前事件响应器
await bot.send(event=event, message=prompt) :参数:
* ``message: Union[str, Message, MessageSegment]``: 消息内容
"""
bot = current_bot.get()
event = current_event.get()
if message:
await bot.send(event=event, message=message)
raise FinishedException raise FinishedException
@classmethod @classmethod
@@ -208,8 +331,14 @@ class Matcher(metaclass=MatcherMeta):
cls, cls,
prompt: Optional[Union[str, Message, prompt: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn: MessageSegment]] = None) -> NoReturn:
bot: Bot = current_bot.get() """
event: Event = current_event.get() :说明:
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
:参数:
* ``prompt: Union[str, Message, MessageSegment]``: 消息内容
"""
bot = current_bot.get()
event = current_event.get()
if prompt: if prompt:
await bot.send(event=event, message=prompt) await bot.send(event=event, message=prompt)
raise PausedException raise PausedException
@@ -219,8 +348,14 @@ class Matcher(metaclass=MatcherMeta):
cls, cls,
prompt: Optional[Union[str, Message, prompt: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn: MessageSegment]] = None) -> NoReturn:
bot: Bot = current_bot.get() """
event: Event = current_event.get() :说明:
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后重新运行当前处理函数
:参数:
* ``prompt: Union[str, Message, MessageSegment]``: 消息内容
"""
bot = current_bot.get()
event = current_event.get()
if prompt: if prompt:
await bot.send(event=event, message=prompt) await bot.send(event=event, message=prompt)
raise RejectedException raise RejectedException
@@ -273,3 +408,196 @@ class Matcher(metaclass=MatcherMeta):
logger.info(f"Matcher {self} running complete") logger.info(f"Matcher {self} running complete")
current_bot.reset(b_t) current_bot.reset(b_t)
current_event.reset(e_t) current_event.reset(e_t)
class MatcherGroup:
"""事件响应器组合,统一管理。用法同 ``Matcher``"""
def __init__(self,
type_: str = "",
rule: Optional[Rule] = None,
permission: Optional[Permission] = None,
handlers: Optional[list] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
*,
module: Optional[str] = None,
default_state: Optional[dict] = None,
expire_time: Optional[datetime] = None):
"""
:说明:
创建一个事件响应器组合,参数为默认值,与 ``Matcher.new`` 一致
"""
self.matchers: List[Type[Matcher]] = []
"""
:类型: ``List[Type[Matcher]]``
:说明: 组内事件响应器列表
"""
self.type = type_
self.rule = rule or Rule()
self.permission = permission or Permission()
self.handlers = handlers
self.temp = temp
self.priority = priority
self.block = block
self.module = module
self.expire_time = expire_time
self._default_state = default_state
self._default_parser: Optional[ArgsParser] = None
def __repr__(self) -> str:
return (
f"<MatcherGroup from {self.module or 'unknow'}, type={self.type}, "
f"priority={self.priority}, temp={self.temp}>")
def __str__(self) -> str:
return self.__repr__()
def new(self,
type_: str = "",
rule: Optional[Rule] = None,
permission: Optional[Permission] = None,
handlers: Optional[list] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
*,
module: Optional[str] = None,
default_state: Optional[dict] = None,
expire_time: Optional[datetime] = None) -> Type[Matcher]:
"""
:说明:
在组中创建一个新的事件响应器,参数留空则使用组合默认值
\:\:\:danger 警告
如果使用 handlers 参数覆盖组合默认值则该事件响应器不会随组合一起添加新的事件处理函数
\:\:\:
"""
matcher = Matcher.new(type_=type_ or self.type,
rule=self.rule & rule,
permission=permission or self.permission,
handlers=handlers or self.handlers,
temp=temp or self.temp,
priority=priority or self.priority,
block=block or self.block,
module=module or self.module,
default_state=default_state or
self._default_state,
expire_time=expire_time or self.expire_time)
self.matchers.append(matcher)
return matcher
def args_parser(self, func: ArgsParser) -> ArgsParser:
for matcher in self.matchers:
matcher.args_parser(func)
return func
def handle(self) -> Callable[[Handler], Handler]:
def _decorator(func: Handler) -> Handler:
self.handlers.append(func)
return func
return _decorator
def receive(self) -> Callable[[Handler], Handler]:
async def _receive(bot: Bot, event: Event, state: dict) -> NoReturn:
raise PausedException
if self.handlers:
# 已有前置handlers则接受一条新的消息否则视为接收初始消息
self.handlers.append(_receive)
def _decorator(func: Handler) -> Handler:
if not self.handlers or self.handlers[-1] is not func:
self.handlers.append(func)
return func
return _decorator
def got(
self,
key: str,
prompt: Optional[str] = None,
args_parser: Optional[ArgsParser] = None
) -> Callable[[Handler], Handler]:
async def _key_getter(bot: Bot, event: Event, state: dict):
state["_current_key"] = key
if key not in state:
if prompt:
await bot.send(event=event, message=prompt)
raise PausedException
else:
state["_skip_key"] = True
async def _key_parser(bot: Bot, event: Event, state: dict):
if key in state and state.get("_skip_key"):
del state["_skip_key"]
return
parser = args_parser or self._default_parser
if parser:
await parser(bot, event, state)
else:
state[state["_current_key"]] = str(event.message)
self.handlers.append(_key_getter)
self.handlers.append(_key_parser)
def _decorator(func: Handler) -> Handler:
if not hasattr(self.handlers[-1], "__wrapped__"):
parser = self.handlers.pop()
@wraps(func)
async def wrapper(bot: Bot, event: Event, state: dict):
await parser(bot, event, state)
await func(bot, event, state)
if "_current_key" in state:
del state["_current_key"]
self.handlers.append(wrapper)
return func
return _decorator
async def send(self, message: Union[str, Message, MessageSegment]):
bot = current_bot.get()
event = current_event.get()
await bot.send(event=event, message=message)
async def finish(
self,
message: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn:
bot = current_bot.get()
event = current_event.get()
if message:
await bot.send(event=event, message=message)
raise FinishedException
async def pause(
self,
prompt: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn:
bot = current_bot.get()
event = current_event.get()
if prompt:
await bot.send(event=event, message=prompt)
raise PausedException
async def reject(
self,
prompt: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn:
bot = current_bot.get()
event = current_event.get()
if prompt:
await bot.send(event=event, message=prompt)
raise RejectedException

View File

@@ -6,6 +6,7 @@ from datetime import datetime
from nonebot.log import logger from nonebot.log import logger
from nonebot.rule import TrieRule from nonebot.rule import TrieRule
from nonebot.utils import escape_tag
from nonebot.matcher import matchers from nonebot.matcher import matchers
from nonebot.typing import Set, Type, Union, NoReturn from nonebot.typing import Set, Type, Union, NoReturn
from nonebot.typing import Bot, Event, Matcher, PreProcessor from nonebot.typing import Bot, Event, Matcher, PreProcessor
@@ -64,14 +65,16 @@ async def handle_event(bot: Bot, event: Event):
log_msg += f"@[群:{event.group_id}]:" log_msg += f"@[群:{event.group_id}]:"
log_msg += ' "' + "".join( log_msg += ' "' + "".join(
map(lambda x: str(x) if x.type == "text" else f"<le>{x!s}</le>", map(
lambda x: escape_tag(str(x))
if x.type == "text" else f"<le>{escape_tag(str(x))}</le>",
event.message)) + '"' # type: ignore event.message)) + '"' # type: ignore
elif event.type == "notice": elif event.type == "notice":
log_msg += f"Notice {event.raw_event}" log_msg += f"Notice {event.raw_event}"
elif event.type == "request": elif event.type == "request":
log_msg += f"Request {event.raw_event}" log_msg += f"Request {event.raw_event}"
elif event.type == "meta_event": elif event.type == "meta_event":
log_msg += f"MetaEvent {event.raw_event}" log_msg += f"MetaEvent {event.detail_type}"
logger.opt(colors=True).info(log_msg) logger.opt(colors=True).info(log_msg)
coros = [] coros = []

View File

@@ -14,7 +14,7 @@
import asyncio import asyncio
from nonebot.utils import run_sync from nonebot.utils import run_sync
from nonebot.typing import Bot, Event, Union, NoReturn, Callable, Awaitable, PermissionChecker from nonebot.typing import Bot, Event, Union, NoReturn, Optional, Callable, Awaitable, PermissionChecker
class Permission: class Permission:
@@ -53,10 +53,13 @@ class Permission:
def __and__(self, other) -> NoReturn: def __and__(self, other) -> NoReturn:
raise RuntimeError("And operation between Permissions is not allowed.") raise RuntimeError("And operation between Permissions is not allowed.")
def __or__(self, other: Union["Permission", def __or__(
PermissionChecker]) -> "Permission": self, other: Optional[Union["Permission",
PermissionChecker]]) -> "Permission":
checkers = self.checkers.copy() checkers = self.checkers.copy()
if isinstance(other, Permission): if other is None:
return self
elif isinstance(other, Permission):
checkers |= other.checkers checkers |= other.checkers
elif asyncio.iscoroutinefunction(other): elif asyncio.iscoroutinefunction(other):
checkers.add(other) # type: ignore checkers.add(other) # type: ignore

View File

@@ -9,9 +9,9 @@ from dataclasses import dataclass
from importlib._bootstrap import _load from importlib._bootstrap import _load
from nonebot.log import logger from nonebot.log import logger
from nonebot.matcher import Matcher
from nonebot.permission import Permission from nonebot.permission import Permission
from nonebot.typing import Handler, RuleChecker from nonebot.typing import Handler, RuleChecker
from nonebot.matcher import Matcher, MatcherGroup
from nonebot.rule import Rule, startswith, endswith, command, regex from nonebot.rule import Rule, startswith, endswith, command, regex
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
@@ -27,8 +27,8 @@ class Plugin(object):
matcher: Set[Type[Matcher]] matcher: Set[Type[Matcher]]
def on(rule: Union[Rule, RuleChecker] = Rule(), def on(rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Permission = Permission(), permission: Optional[Permission] = None,
*, *,
handlers: Optional[List[Handler]] = None, handlers: Optional[List[Handler]] = None,
temp: bool = False, temp: bool = False,
@@ -37,7 +37,7 @@ def on(rule: Union[Rule, RuleChecker] = Rule(),
state: Optional[dict] = None) -> Type[Matcher]: state: Optional[dict] = None) -> Type[Matcher]:
matcher = Matcher.new("", matcher = Matcher.new("",
Rule() & rule, Rule() & rule,
permission, permission or Permission(),
temp=temp, temp=temp,
priority=priority, priority=priority,
block=block, block=block,
@@ -47,7 +47,7 @@ def on(rule: Union[Rule, RuleChecker] = Rule(),
return matcher return matcher
def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(), def on_metaevent(rule: Optional[Union[Rule, RuleChecker]] = None,
*, *,
handlers: Optional[List[Handler]] = None, handlers: Optional[List[Handler]] = None,
temp: bool = False, temp: bool = False,
@@ -66,8 +66,8 @@ def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(),
return matcher return matcher
def on_message(rule: Union[Rule, RuleChecker] = Rule(), def on_message(rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Permission = Permission(), permission: Optional[Permission] = None,
*, *,
handlers: Optional[List[Handler]] = None, handlers: Optional[List[Handler]] = None,
temp: bool = False, temp: bool = False,
@@ -76,7 +76,7 @@ def on_message(rule: Union[Rule, RuleChecker] = Rule(),
state: Optional[dict] = None) -> Type[Matcher]: state: Optional[dict] = None) -> Type[Matcher]:
matcher = Matcher.new("message", matcher = Matcher.new("message",
Rule() & rule, Rule() & rule,
permission, permission or Permission(),
temp=temp, temp=temp,
priority=priority, priority=priority,
block=block, block=block,
@@ -86,7 +86,7 @@ def on_message(rule: Union[Rule, RuleChecker] = Rule(),
return matcher return matcher
def on_notice(rule: Union[Rule, RuleChecker] = Rule(), def on_notice(rule: Optional[Union[Rule, RuleChecker]] = None,
*, *,
handlers: Optional[List[Handler]] = None, handlers: Optional[List[Handler]] = None,
temp: bool = False, temp: bool = False,
@@ -105,7 +105,7 @@ def on_notice(rule: Union[Rule, RuleChecker] = Rule(),
return matcher return matcher
def on_request(rule: Union[Rule, RuleChecker] = Rule(), def on_request(rule: Optional[Union[Rule, RuleChecker]] = None,
*, *,
handlers: Optional[List[Handler]] = None, handlers: Optional[List[Handler]] = None,
temp: bool = False, temp: bool = False,
@@ -125,27 +125,23 @@ def on_request(rule: Union[Rule, RuleChecker] = Rule(),
def on_startswith(msg: str, def on_startswith(msg: str,
rule: Optional[Union[Rule, RuleChecker]] = None, rule: Optional[Optional[Union[Rule, RuleChecker]]] = None,
permission: Permission = Permission(),
**kwargs) -> Type[Matcher]: **kwargs) -> Type[Matcher]:
return on_message(startswith(msg) & return on_message(startswith(msg) & rule, **kwargs) if rule else on_message(
rule, permission, **kwargs) if rule else on_message( startswith(msg), **kwargs)
startswith(msg), permission, **kwargs)
def on_endswith(msg: str, def on_endswith(msg: str,
rule: Optional[Union[Rule, RuleChecker]] = None, rule: Optional[Optional[Union[Rule, RuleChecker]]] = None,
permission: Permission = Permission(),
**kwargs) -> Type[Matcher]: **kwargs) -> Type[Matcher]:
return on_message(endswith(msg) & return on_message(endswith(msg) & rule, **kwargs) if rule else on_message(
rule, permission, **kwargs) if rule else on_message( startswith(msg), **kwargs)
startswith(msg), permission, **kwargs)
def on_command(cmd: Union[str, Tuple[str, ...]], def on_command(cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = None, rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Permission = Permission(), aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
**kwargs) -> Type[Matcher]: **kwargs) -> Union[Type[Matcher], MatcherGroup]:
if isinstance(cmd, str): if isinstance(cmd, str):
cmd = (cmd,) cmd = (cmd,)
@@ -157,20 +153,28 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
handlers = kwargs.pop("handlers", []) handlers = kwargs.pop("handlers", [])
handlers.insert(0, _strip_cmd) handlers.insert(0, _strip_cmd)
return on_message( if aliases:
command(cmd) & aliases = set(map(lambda x: (x,) if isinstance(x, str) else x, aliases))
rule, permission, handlers=handlers, **kwargs) if rule else on_message( group = MatcherGroup("message",
command(cmd), permission, handlers=handlers, **kwargs) Rule() & rule,
handlers=handlers,
**kwargs)
for cmd_ in [cmd, *aliases]:
_tmp_matchers.add(group.new(rule=command(cmd_)))
return group
else:
return on_message(command(cmd) & rule, handlers=handlers, **
kwargs) if rule else on_message(
command(cmd), handlers=handlers, **kwargs)
def on_regex(pattern: str, def on_regex(pattern: str,
flags: Union[int, re.RegexFlag] = 0, flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = None, rule: Optional[Rule] = None,
permission: Permission = Permission(),
**kwargs) -> Type[Matcher]: **kwargs) -> Type[Matcher]:
return on_message(regex(pattern, flags) & return on_message(regex(pattern, flags) &
rule, permission, **kwargs) if rule else on_message( rule, **kwargs) if rule else on_message(
regex(pattern, flags), permission, **kwargs) regex(pattern, flags), **kwargs)
def load_plugin(module_path: str) -> Optional[Plugin]: def load_plugin(module_path: str) -> Optional[Plugin]:
@@ -234,3 +238,21 @@ def load_builtin_plugins():
def get_loaded_plugins() -> Set[Plugin]: def get_loaded_plugins() -> Set[Plugin]:
return set(plugins.values()) return set(plugins.values())
class CommandGroup:
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
self.basecmd = (cmd,) if isinstance(cmd, str) else cmd
if "aliases" in kwargs:
del kwargs["aliases"]
self.base_kwargs = kwargs
def command(self, cmd: Union[str, Tuple[str, ...]],
**kwargs) -> Union[Type[Matcher], MatcherGroup]:
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
cmd = self.basecmd + sub_cmd
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
return on_command(cmd, **final_kwargs)

180
nonebot/plugin.pyi Normal file
View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
from typing import overload
from nonebot.typing import Rule, Matcher, Handler, Permission, RuleChecker, MatcherGroup
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
plugins: Dict[str, "Plugin"] = ...
_tmp_matchers: Set[Type[Matcher]] = ...
class Plugin(object):
name: str
module: ModuleType
matcher: Set[Type[Matcher]]
def on(rule: Optional[Union[Rule, RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_metaevent(rule: Optional[Union[Rule, RuleChecker]] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_message(rule: Optional[Union[Rule, RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_notice(rule: Optional[Union[Rule, RuleChecker]] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_request(rule: Optional[Union[Rule, RuleChecker]] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_startswith(msg: str,
rule: Optional[Optional[Union[Rule, RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_endswith(msg: str,
rule: Optional[Optional[Union[Rule, RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
@overload
def on_command(cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
aliases: None = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
@overload
def on_command(cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
aliases: Set[Union[str, Tuple[str, ...]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> MatcherGroup:
...
def on_regex(pattern: str,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def load_plugin(module_path: str) -> Optional[Plugin]:
...
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
...
def load_builtin_plugins():
...
def get_loaded_plugins() -> Set[Plugin]:
...
class CommandGroup:
def __init__(self,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...):
...
def command(
self,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
aliases: Set[Union[str, Tuple[str, ...]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Union[Type[Matcher], MatcherGroup]:
...

View File

@@ -1,10 +1,34 @@
from functools import reduce
from nonebot.rule import to_me from nonebot.rule import to_me
from nonebot.plugin import on_command from nonebot.plugin import on_command
from nonebot.typing import Bot, Event from nonebot.permission import SUPERUSER
from nonebot.typing import Bot, Event, MessageSegment
say = on_command("say", to_me()) say = on_command("say", to_me(), permission=SUPERUSER)
@say.handle() @say.handle()
async def repeat(bot: Bot, event: Event, state: dict): async def say_unescape(bot: Bot, event: Event, state: dict):
await bot.send(message=event.message, event=event) Message = event.message.__class__
def _unescape(message: Message, segment: MessageSegment):
if segment.type == "text":
return message.append(segment.data["text"])
return message.append(segment)
message = reduce(_unescape, event.message, Message()) # type: ignore
await bot.send(message=message, event=event)
echo = on_command("echo", to_me())
@echo.handle()
async def echo_escape(bot: Bot, event: Event, state: dict):
Message = event.message.__class__
MessageSegment = event.message[0].__class__
message = Message().append( # type: ignore
MessageSegment.text(str(event.message)))
await bot.send(message=message, event=event)

View File

@@ -4,10 +4,10 @@
规则 规则
==== ====
每个 ``Matcher`` 拥有一个 ``Rule`` ,其中是 **异步** ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。 每个事件响应器 ``Matcher`` 拥有一个匹配规则 ``Rule`` ,其中是 **异步** ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。
\:\:\:tip 提示 \:\:\:tip 提示
``RuleChecker`` 既可以是 async function 也可以是 sync function ``RuleChecker`` 既可以是 async function 也可以是 sync function但在最终会被 ``nonebot.utils.run_sync`` 转换为 async function
\:\:\: \:\:\:
""" """
@@ -20,7 +20,7 @@ from pygtrie import CharTrie
from nonebot import get_driver from nonebot import get_driver
from nonebot.log import logger from nonebot.log import logger
from nonebot.utils import run_sync from nonebot.utils import run_sync
from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, Callable, Awaitable, RuleChecker from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, Optional, Callable, Awaitable, RuleChecker
class Rule: class Rule:
@@ -68,9 +68,11 @@ class Rule:
*map(lambda c: c(bot, event, state), self.checkers)) *map(lambda c: c(bot, event, state), self.checkers))
return all(results) return all(results)
def __and__(self, other: Union["Rule", RuleChecker]) -> "Rule": def __and__(self, other: Optional[Union["Rule", RuleChecker]]) -> "Rule":
checkers = self.checkers.copy() checkers = self.checkers.copy()
if isinstance(other, Rule): if other is None:
return self
elif isinstance(other, Rule):
checkers |= other.checkers checkers |= other.checkers
elif asyncio.iscoroutinefunction(other): elif asyncio.iscoroutinefunction(other):
checkers.add(other) # type: ignore checkers.add(other) # type: ignore
@@ -183,6 +185,12 @@ def endswith(msg: str) -> Rule:
def keyword(msg: str) -> Rule: def keyword(msg: str) -> Rule:
"""
:说明:
匹配消息关键词
:参数:
* ``msg: str``: 关键词
"""
async def _keyword(bot: Bot, event: Event, state: dict) -> bool: async def _keyword(bot: Bot, event: Event, state: dict) -> bool:
return bool(event.plain_text and msg in event.plain_text) return bool(event.plain_text and msg in event.plain_text)
@@ -191,6 +199,22 @@ def keyword(msg: str) -> Rule:
def command(command: Tuple[str, ...]) -> Rule: def command(command: Tuple[str, ...]) -> Rule:
"""
:说明:
命令形式匹配,根据配置里提供的 ``command_start``, ``command_sep`` 判断消息是否为命令。
:参数:
* ``command: Tuples[str, ...]``: 命令内容
:示例:
使用默认 ``command_start``, ``command_sep`` 配置
命令 ``("test",)`` 可以匹配:``/test`` 开头的消息
命令 ``("test", "sub")`` 可以匹配”``/test.sub`` 开头的消息
\:\:\:tip 提示
命令内容与后续消息间无需空格!
\:\:\:
"""
config = get_driver().config config = get_driver().config
command_start = config.command_start command_start = config.command_start
command_sep = config.command_sep command_sep = config.command_sep
@@ -208,6 +232,14 @@ def command(command: Tuple[str, ...]) -> Rule:
def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule: def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
"""
:说明:
根据正则表达式进行匹配
:参数:
* ``regex: str``: 正则表达式
* ``flags: Union[int, re.RegexFlag]``: 正则标志
"""
pattern = re.compile(regex, flags) pattern = re.compile(regex, flags)
async def _regex(bot: Bot, event: Event, state: dict) -> bool: async def _regex(bot: Bot, event: Event, state: dict) -> bool:
@@ -217,6 +249,12 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
def to_me() -> Rule: def to_me() -> Rule:
"""
:说明:
通过 ``event.to_me`` 判断消息是否是发送给机器人
:参数:
* 无
"""
async def _to_me(bot: Bot, event: Event, state: dict) -> bool: async def _to_me(bot: Bot, event: Event, state: dict) -> bool:
return bool(event.to_me) return bool(event.to_me)

View File

@@ -28,10 +28,10 @@ from typing import Union, TypeVar, Optional, Iterable, Callable, Awaitable
# import some modules needed when checking types # import some modules needed when checking types
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.rule import Rule as RuleClass from nonebot.rule import Rule as RuleClass
from nonebot.matcher import Matcher as MatcherClass
from nonebot.drivers import BaseDriver, BaseWebSocket from nonebot.drivers import BaseDriver, BaseWebSocket
from nonebot.permission import Permission as PermissionClass from nonebot.permission import Permission as PermissionClass
from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
from nonebot.matcher import Matcher as MatcherClass, MatcherGroup as MatcherGroupClass
def overrides(InterfaceClass: object): def overrides(InterfaceClass: object):
@@ -112,6 +112,14 @@ Matcher = TypeVar("Matcher", bound="MatcherClass")
Matcher 即响应事件的处理类。通过 Rule 判断是否响应事件,运行 Handler。 Matcher 即响应事件的处理类。通过 Rule 判断是否响应事件,运行 Handler。
""" """
MatcherGroup = TypeVar("MatcherGroup", bound="MatcherGroupClass")
"""
:类型: ``MatcherGroup``
:说明:
MatcherGroup 为 Matcher 的集合。可以共享 Handler。
"""
Rule = TypeVar("Rule", bound="RuleClass") Rule = TypeVar("Rule", bound="RuleClass")
""" """
:类型: ``Rule`` :类型: ``Rule``

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re
import json import json
import asyncio import asyncio
import dataclasses import dataclasses
@@ -9,6 +10,18 @@ from functools import wraps, partial
from nonebot.typing import Any, Callable, Awaitable, overrides from nonebot.typing import Any, Callable, Awaitable, overrides
def escape_tag(s: str) -> str:
"""
:说明:
用于记录带颜色日志时转义 ``<tag>`` 类型特殊标签
:参数:
* ``s: str``: 需要转义的字符串
:返回:
- ``str``
"""
return re.sub(r"</?((?:[fb]g\s)?[^<>\s]*)>", r"\\\g<0>", s)
def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
""" """
:说明: :说明:

1507
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "nonebot2" name = "nonebot2"
version = "2.0.0a1" version = "2.0.0a3"
description = "An asynchronous python bot framework." description = "An asynchronous python bot framework."
authors = ["yanyongyu <yanyongyu_1@126.com>"] authors = ["yanyongyu <yanyongyu_1@126.com>"]
license = "MIT" license = "MIT"
@@ -31,8 +31,8 @@ fastapi = "^0.58.1"
uvicorn = "^0.11.5" uvicorn = "^0.11.5"
pydantic = { extras = ["dotenv"], version = "^1.6.1" } pydantic = { extras = ["dotenv"], version = "^1.6.1" }
apscheduler = { version = "^3.6.3", optional = true } apscheduler = { version = "^3.6.3", optional = true }
# nonebot-test = { version = "^0.1.0", optional = true } nonebot-test = { version = "^0.1.0", optional = true }
# nb-cli = { version="^0.1.0", optional = true } nb-cli = { version="^0.1.0", optional = true }
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
yapf = "^0.30.0" yapf = "^0.30.0"
@@ -40,8 +40,8 @@ sphinx = "^3.1.1"
sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" } sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" }
[tool.poetry.extras] [tool.poetry.extras]
# cli = ["nb-cli"] cli = ["nb-cli"]
# test = ["nonebot-test"] test = ["nonebot-test"]
scheduler = ["apscheduler"] scheduler = ["apscheduler"]
full = ["nb-cli", "nonebot-test", "scheduler"] full = ["nb-cli", "nonebot-test", "scheduler"]

View File

@@ -3,6 +3,9 @@ HOST=0.0.0.0
PORT=2333 PORT=2333
DEBUG=true DEBUG=true
SUPERUSERS=[123123123]
NICKNAME=["bot"]
COMMAND_START=["", "/", "#"] COMMAND_START=["", "/", "#"]
COMMAND_SEP=["/", "."] COMMAND_SEP=["/", "."]

View File

@@ -0,0 +1,6 @@
from nonebot.rule import to_me
from nonebot import CommandGroup
test = CommandGroup("test", rule=to_me())
from . import commands

View File

@@ -0,0 +1,11 @@
from nonebot.typing import Bot, Event
from nonebot.permission import GROUP_OWNER
from . import test
test_1 = test.command("1", aliases={"test"}, permission=GROUP_OWNER)
@test_1.handle()
async def test1(bot: Bot, event: Event, state: dict):
await test_1.finish(event.raw_message)

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from nonebot.rule import to_me
from nonebot.typing import Event
from nonebot.plugin import on_message
from nonebot.adapters.cqhttp import Bot
test_message = on_message(to_me(), state={"default": 1})
@test_message.handle()
async def test_handler(bot: Bot, event: Event, state: dict):
print("[*] Test Matcher Received:", event)
state["event"] = event
await bot.send(message="Received", event=event)
@test_message.receive()
async def test_receive(bot: Bot, event: Event, state: dict):
print("[*] Test Matcher Received next time:", event)
print("[*] Current State:", state)