Compare commits

...

125 Commits

Author SHA1 Message Date
Ju4tCode
70c400da7f 🔀 Merge pull request #43
Pre Release 2.0.0a4
2020-11-04 13:46:05 +08:00
yanyongyu
2a5cfe712f 📝 update doc version 2020-11-04 13:41:52 +08:00
yanyongyu
533a045622 ⬆️ bump version and update dependency 2020-11-04 13:27:49 +08:00
Ju4tCode
93aa1791c9 🔀 Merge pull request #42
Fix: Plugin publish template
2020-11-02 20:25:28 +08:00
yanyongyu
06c76cc096 👷 fix plugin template 2020-11-02 20:20:41 +08:00
Ju4tCode
04df0e5f51 🔀 Merge pull request #41 2020-11-02 19:39:55 +08:00
yanyongyu
f99e029567 📌 update poetry lock file 2020-11-02 19:33:45 +08:00
yanyongyu
71ee9aee21 🚧 update plugin store 2020-11-02 19:07:53 +08:00
yanyongyu
d9ea95c67e 🚑 fix message segment get 2020-11-01 19:20:18 +08:00
yanyongyu
af7f42ac60 📝 update plugin store page 2020-11-01 18:21:31 +08:00
yanyongyu
15e59a1778 👷 update plugin template 2020-11-01 18:21:10 +08:00
yanyongyu
22962d55e1 👷 create plugin issue template 2020-11-01 16:18:57 +08:00
yanyongyu
70c7927006 💡 add docstring for cqhttp message 2020-10-30 16:49:31 +08:00
yanyongyu
89c3aa38a6 🎨 allow multi value for keyword rule 2020-10-30 16:26:04 +08:00
Ju4tCode
2eb330b8a8 🔀 Merge pull request #39 2020-10-30 15:57:31 +08:00
yanyongyu
383c0031a5 📝 update plugin 2020-10-30 01:59:45 +08:00
yanyongyu
06f20281a5 🔇 remove meta event logs #21 2020-10-29 17:06:07 +08:00
yanyongyu
a40f1a453c 📝 replace say with echo due to superuser perm 2020-10-29 16:27:20 +08:00
Ju4tCode
5d9f354209 🔀 Merge pull request #37 2020-10-28 13:49:53 +08:00
Ju4tCode
c3bd8ebf57 🚧 change result store 2020-10-28 13:45:54 +08:00
yanyongyu
c9c615c8cb 👷 try pull request target 2020-10-28 13:42:20 +08:00
Ju4tCode
47f491039c 💡 update regex docstring 2020-10-28 13:23:48 +08:00
yanyongyu
dc5c35a9ed 💡 update command docstrinrg 2020-10-28 13:17:41 +08:00
yanyongyu
c636186321 👷 update api doc action 2020-10-28 13:05:51 +08:00
Ju4tCode
aafc3dd060 🔀 Merge pull request #38 2020-10-28 12:52:27 +08:00
ayayaneru
38469611a4 Update README 2020-10-28 10:01:33 +08:00
rkroom
59a8bd8c97 add matched_string when rule regex
当使用正则匹配消息成功时,向state添加matched_string以保存匹配到的内容供接下来使用。
2020-10-26 17:18:26 +08:00
Ju4tCode
50cfd3c9b5 🔀 Merge pull request #36
Issue35:修改鉴权限制
2020-10-25 14:48:00 +08:00
yanyongyu
5d08d53c27 👽 update auth header 2020-10-25 14:42:46 +08:00
Ju4tCode
4f6f99146c 🔀 Merge pull request #33
修改 Command 实现逻辑
2020-10-22 22:36:23 +08:00
yanyongyu
32388d070d 🎨 improve command implementation 2020-10-22 22:08:19 +08:00
yanyongyu
d9f8bf38c6 🐛 fix matcher group send 2020-10-22 00:00:29 +08:00
yanyongyu
4e2b74af75 🚧 add plugin store page 2020-10-21 00:55:23 +08:00
yanyongyu
c2c28cebf5 👽 update nonebot theme 2020-10-21 00:54:09 +08:00
yanyongyu
24141b1a4b 🎨 remove shebang line #31 2020-10-20 22:40:16 +08:00
Ju4tCode
c85e50d73d 👷 Add release draft config 2020-10-20 17:28:51 +08:00
Ju4tCode
444156766a 👷 Update release drafter 2020-10-20 17:27:41 +08:00
yanyongyu
308824d0b1 🐛 fix matcher group args parser 2020-10-20 00:01:26 +08:00
yanyongyu
c3ed962837 add site cache to improve performance #29 2020-10-19 17:49:04 +08:00
Ju4tCode
2c13303d89 👷 Update issue templates 2020-10-19 00:00:11 +08:00
Ju4tCode
1304b94b61 🔀 Merge pull request #28
Update vuepress theme
2020-10-18 03:05:42 -05:00
yanyongyu
8ca677c271 👽 fix home link 2020-10-18 15:34:14 +08:00
yanyongyu
7c17412106 💡 add plugin docstring 2020-10-18 15:04:45 +08:00
yanyongyu
26207f762b 🏷️ fix class inherit in pyi 2020-10-18 15:02:56 +08:00
yanyongyu
8db70dbb0c 👽 fix theme smooth scroll 2020-10-18 02:30:01 +08:00
yanyongyu
1b6924a104 👽 update lock file 2020-10-18 02:04:32 +08:00
yanyongyu
c5a30a8a79 use custom theme 2020-10-18 01:39:34 +08:00
yanyongyu
56c9c24dc6 add kwargs support for matcher send/finish/pause/reject 2020-10-17 19:50:25 +08:00
yanyongyu
9ad629841b 📝 add uninstall nonebot 1.x tip 2020-10-16 17:15:40 +08:00
yanyongyu
78433bdae9 🚑 restore markdown 2020-10-16 16:08:51 +08:00
yanyongyu
5f44212faa 🚑 restore div 2020-10-16 16:07:04 +08:00
yanyongyu
df517b6b36 📝 use p instead of div 2020-10-16 16:03:49 +08:00
yanyongyu
6afe9b6f4f 📝 Update Readme (fix #27) 2020-10-16 15:59:34 +08:00
yanyongyu
1d79ac232f 🐛 fix missing param for on event 2020-10-16 15:12:15 +08:00
yanyongyu
0a64959973 💡 add driver docstring 2020-10-16 01:10:46 +08:00
yanyongyu
1e4b058681 👽 update repo link 2020-10-11 14:51:37 +08:00
yanyongyu
3c5d06a2de 📝 add todo tag 2020-10-11 13:49:58 +08:00
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
104 changed files with 9735 additions and 1641 deletions

33
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@@ -0,0 +1,33 @@
---
name: Bug report
about: Create a bug report to help us improve
title: 'Bug: Something went wrong'
labels: bug
assignees: ''
---
**描述问题:**
A clear and concise description of what the bug is.
**如何复现?**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**期望的结果**
A clear and concise description of what you expected to happen.
**环境信息:**
- OS: [e.g. Linux]
- Python Version: [e.g. 3.8]
- Nonebot Version [e.g. 2.0.0]
**截图**
If applicable, add screenshots to help explain your problem.

View File

@@ -0,0 +1,16 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'Feature: Something you want'
labels: enhancement
assignees: ''
---
**是否在使用中遇到某些问题而需要新的特性?请描述:**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**描述你所需要的特性:**
A clear and concise description of what you want to happen.

View File

@@ -0,0 +1,36 @@
---
name: Plugin Publish
about: Publish your plugin to nonebot homepage and nb-cli
title: "Plugin: blabla 的插件"
labels: Plugin
assignees: ""
---
**你的插件名称:**
例:复读机
**简短描述插件功能:**
例:复读群友的消息
**插件 import 使用的名称**
nonebot-plugin-example
**插件 install 使用的名称**
例 1nonebot-plugin-example
通过 pypi 安装
> 请事先发布插件到[pypi](https://pypi.org/)
例 2git+https://github.com/nonebot/nonebot-plugin-example
从 github 仓库安装
**插件项目仓库/主页链接**
nonebot/nonebot2默认 github )或其他链接

31
.github/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name-template: 'v$RESOLVED_VERSION 🌈'
tag-template: 'v$RESOLVED_VERSION'
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
label: 'chore'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
template: |
## Changes
$CHANGES

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

@@ -0,0 +1,42 @@
name: Build API Doc
on:
pull_request_target:
types: [ opened, synchronize, reopened ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
token: ${{ secrets.GH_TOKEN }}
- 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}}

14
.github/workflows/release-draft.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Release Drafter
on:
push:
branches:
- master
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,22 +1,47 @@
<div align=center> <p align="center">
<img src="docs/.vuepress/public/logo.png" width="200" height="200"> <a href="https://v2.nonebot.dev/"><img src="https://raw.githubusercontent.com/nonebot/nonebot2/master/docs/.vuepress/public/logo.png" width="200" height="200" alt="nonebot"></a>
</p>
<div align="center">
# NoneBot # NoneBot
[![License](https://img.shields.io/github/license/richardchien/nonebot.svg)](LICENSE) _✨ Python 异步机器人框架 ✨_
[![PyPI](https://img.shields.io/pypi/v/nonebot.svg)](https://pypi.python.org/pypi/nonebot)
![Python Version](https://img.shields.io/badge/python-3.7+-blue.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)
[![Telegram](https://img.shields.io/badge/telegram-chat-blue.svg)](https://t.me/cqhttp)
[![QQ 版本发布群](https://img.shields.io/badge/%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83%E7%BE%A4-218529254-green.svg)](https://jq.qq.com/?_wv=1027&k=5Nl0zhE)
[![Telegram 版本发布频道](https://img.shields.io/badge/%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83%E9%A2%91%E9%81%93-join-green.svg)](https://t.me/cqhttp_release)
</div> </div>
## 简介 <p align="center">
<a href="https://raw.githubusercontent.com/nonebot/nonebot2/master/LICENSE">
<img src="https://img.shields.io/github/license/nonebot/nonebot2.svg" alt="license">
</a>
<a href="https://pypi.python.org/pypi/nonebot2">
<img src="https://img.shields.io/pypi/v/nonebot2.svg" alt="pypi">
</a>
<img src="https://img.shields.io/badge/python-3.7+-blue.svg" alt="python">
<img src="https://img.shields.io/badge/cqhttp-11+-black.svg" alt="cqhttp"><br />
<a href="https://jq.qq.com/?_wv=1027&k=5OFifDh">
<img src="https://img.shields.io/badge/qq%E7%BE%A4-768887710-orange.svg" alt="QQ Chat">
</a>
<a href="https://t.me/cqhttp">
<img src="https://img.shields.io/badge/telegram-chat-blue.svg" alt="Telegram Chat">
</a>
<a href="https://jq.qq.com/?_wv=1027&k=5Nl0zhE">
<img src="https://img.shields.io/badge/%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83%E7%BE%A4-218529254-green.svg" alt="QQ Release">
</a>
<a href="https://t.me/cqhttp_release">
<img src="https://img.shields.io/badge/版本发布频道-join-green.svg" alt="Telegram Release">
</a>
</p>
**NoneBot2 尚在开发中** <p align="center">
<a href="https://v2.nonebot.dev/">文档</a>
·
<a href="https://v2.nonebot.dev/guide/installation.html">安装</a>
·
<a href="https://v2.nonebot.dev/guide/getting-started.html">开始使用</a>
</p>
## 简介
NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。
@@ -28,6 +53,11 @@ NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人
需要注意的是NoneBot 仅支持 Python 3.7+ 及 CQHTTP(OneBot) 插件 v11+。 需要注意的是NoneBot 仅支持 Python 3.7+ 及 CQHTTP(OneBot) 插件 v11+。
此外NoneBot2 还有可配套使用的额外脚手架/框架:
- [NB-CLI](https://github.com/nonebot/nb-cli)
- [NoneBot-Test](https://github.com/nonebot/nonebot-test)
## 文档 ## 文档
文档目前尚未完成「API」部分由 sphinx 自动生成,你可以在 [这里](https://v2.nonebot.dev/) 查看。 文档目前尚未完成「API」部分由 sphinx 自动生成,你可以在 [这里](https://v2.nonebot.dev/) 查看。

View File

@@ -1,122 +0,0 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.rule 模块
## 规则
每个 `Matcher` 拥有一个 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
:::tip 提示
`RuleChecker` 既可以是 async function 也可以是 sync function
:::
## _class_ `Rule`
基类:`object`
* **说明**
`Matcher` 规则类,当事件传递时,在 `Matcher` 运行前进行检查。
* **示例**
```python
Rule(async_function) & sync_function
# 等价于
from nonebot.utils import run_sync
Rule(async_function, run_sync(sync_function))
```
### `__init__(*checkers)`
* **参数**
* `*checkers: Callable[[Bot, Event, dict], Awaitable[bool]]`: **异步** RuleChecker
### `checkers`
* **说明**
存储 `RuleChecker`
* **类型**
* `Set[Callable[[Bot, Event, dict], Awaitable[bool]]]`
### _async_ `__call__(bot, event, state)`
* **说明**
检查是否符合所有规则
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
* `state: dict`: 当前 State
* **返回**
* `bool`
## `startswith(msg)`
* **说明**
匹配消息开头
* **参数**
* `msg: str`: 消息开头字符串
## `endswith(msg)`
* **说明**
匹配消息结尾
* **参数**
* `msg: str`: 消息结尾字符串

View File

@@ -7,16 +7,13 @@
* [nonebot](nonebot.html) * [nonebot](nonebot.html)
* [nonebot.typing](typing.html)
* [nonebot.config](config.html) * [nonebot.config](config.html)
* [nonebot.sched](sched.html) * [nonebot.plugin](plugin.html)
* [nonebot.log](log.html) * [nonebot.matcher](matcher.html)
* [nonebot.rule](rule.html) * [nonebot.rule](rule.html)
@@ -25,7 +22,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,415 @@
---
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)
CQHTTP 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。
## _class_ `Message`
基类:[`nonebot.adapters.BaseMessage`](#None)
CQHTTP 协议 Message 适配。

View File

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

View File

@@ -0,0 +1,246 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers 模块
## 后端驱动适配基类
各驱动请继承以下基类
## _class_ `BaseDriver`
基类:`abc.ABC`
Driver 基类。将后端框架封装,以满足适配器使用。
### `_adapters`
* **类型**
`Dict[str, Type[Bot]]`
* **说明**
已注册的适配器列表
### _abstract_ `__init__(env, config)`
* **参数**
* `env: Env`: 包含环境信息的 Env 对象
* `config: Config`: 包含配置信息的 Config 对象
### `env`
* **类型**
`str`
* **说明**
环境名称
### `config`
* **类型**
`Config`
* **说明**
配置对象
### `_clients`
* **类型**
`Dict[str, Bot]`
* **说明**
已连接的 Bot
### _classmethod_ `register_adapter(name, adapter)`
* **说明**
注册一个协议适配器
* **参数**
* `name: str`: 适配器名称,用于在连接时进行识别
* `adapter: Type[Bot]`: 适配器 Class
### _abstract property_ `type`
驱动类型名称
### _abstract property_ `server_app`
驱动 APP 对象
### _abstract property_ `asgi`
驱动 ASGI 对象
### _abstract property_ `logger`
驱动专属 logger 日志记录器
### _property_ `bots`
* **类型**
`Dict[str, Bot]`
* **说明**
获取当前所有已连接的 Bot
### _abstract_ `on_startup(func)`
注册一个在驱动启动时运行的函数
### _abstract_ `on_shutdown(func)`
注册一个在驱动停止时运行的函数
### _abstract_ `run(host=None, port=None, *args, **kwargs)`
* **说明**
启动驱动框架
* **参数**
* `host: Optional[str]`: 驱动绑定 IP
* `post: Optional[int]`: 驱动绑定端口
* `*args`
* `**kwargs`
### _abstract async_ `_handle_http()`
用于处理 HTTP 类型请求的函数
### _abstract async_ `_handle_ws_reverse()`
用于处理 WebSocket 类型请求的函数
## _class_ `BaseWebSocket`
基类:`object`
WebSocket 连接封装,统一接口方便外部调用。
### _abstract_ `__init__(websocket)`
* **参数**
* `websocket: Any`: WebSocket 连接对象
### _property_ `websocket`
WebSocket 连接对象
### _abstract property_ `closed`
* **类型**
`bool`
* **说明**
连接是否已经关闭
### _abstract async_ `accept()`
接受 WebSocket 连接请求
### _abstract async_ `close(code)`
关闭 WebSocket 连接请求
### _abstract async_ `receive()`
接收一条 WebSocket 信息
### _abstract async_ `send(data)`
发送一条 WebSocket 信息

View File

@@ -0,0 +1,125 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers.fastapi 模块
## FastAPI 驱动适配
后端使用方法请参考: [FastAPI 文档](https://fastapi.tiangolo.com/)
## _class_ `Driver`
基类:[`nonebot.drivers.BaseDriver`](#None)
FastAPI 驱动框架
### `__init__(env, config)`
* **参数**
* `env: Env`: 包含环境信息的 Env 对象
* `config: Config`: 包含配置信息的 Config 对象
### _property_ `type`
驱动名称: `fastapi`
### _property_ `server_app`
`FastAPI APP` 对象
### _property_ `asgi`
`FastAPI APP` 对象
### _property_ `logger`
fastapi 使用的 logger
### `on_startup(func)`
参考文档: [Events](https://fastapi.tiangolo.com/advanced/events/#startup-event)
### `on_shutdown(func)`
参考文档: [Events](https://fastapi.tiangolo.com/advanced/events/#startup-event)
### `run(host=None, port=None, *, app=None, **kwargs)`
使用 `uvicorn` 启动 FastAPI
### _async_ `_handle_http(adapter, data=Body(Ellipsis), x_self_id=Header(None), x_signature=Header(None), auth=Depends(get_auth_bearer))`
用于处理 HTTP 类型请求的函数
### _async_ `_handle_ws_reverse(adapter, websocket, x_self_id=Header(None), auth=Depends(get_auth_bearer))`
用于处理 WebSocket 类型请求的函数
## _class_ `WebSocket`
基类:[`nonebot.drivers.BaseWebSocket`](#None)
### `__init__(websocket)`
* **参数**
* `websocket: Any`: WebSocket 连接对象
### _property_ `closed`
* **类型**
`bool`
* **说明**
连接是否已经关闭
### _async_ `accept()`
接受 WebSocket 连接请求
### _async_ `close(code=1000)`
关闭 WebSocket 连接请求
### _async_ `receive()`
接收一条 WebSocket 信息
### _async_ `send(data)`
发送一条 WebSocket 信息

View File

@@ -0,0 +1,497 @@
---
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, **kwargs)`
* **说明**
发送一条消息给当前交互用户
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
### _async classmethod_ `finish(message=None, **kwargs)`
* **说明**
发送一条消息给当前交互用户并结束当前事件响应器
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
### _async classmethod_ `pause(prompt=None, **kwargs)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
### _async classmethod_ `reject(prompt=None, **kwargs)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后重新运行当前处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
## _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

@@ -0,0 +1,629 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.plugin 模块
## 插件
为 NoneBot 插件开发提供便携的定义函数。
## `plugins`
* **类型**
`Dict[str, Plugin]`
* **说明**
已加载的插件
## _class_ `Plugin`
基类:`object`
存储插件信息
### `name`
* **类型**: `str`
* **说明**: 插件名称,使用 文件/文件夹 名称作为插件名
### `module`
* **类型**: `ModuleType`
* **说明**: 插件模块对象
### `matcher`
* **类型**: `Set[Type[Matcher]]`
* **说明**: 插件内定义的 `Matcher`
## `on(type='', rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个基础事件响应器,可自定义类型。
* **参数**
* `type: str`: 事件响应器类型
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_metaevent(rule=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个元事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_message(rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=True, state=None)`
* **说明**
注册一个消息事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_notice(rule=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个通知事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_request(rule=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个请求事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_startswith(msg, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息的\*\*文本部分\*\*以指定内容开头时响应。
* **参数**
* `msg: str`: 指定消息开头内容
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_endswith(msg, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息的\*\*文本部分\*\*以指定内容结尾时响应。
* **参数**
* `msg: str`: 指定消息结尾内容
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_keyword(keywords, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
* **参数**
* `keywords: Set[str]`: 关键词列表
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_command(cmd, rule=None, aliases=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息以指定命令开头时响应。
命令匹配规则参考: [命令形式匹配](rule.html#command-command)
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 指定命令内容
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `aliases: Optional[Set[Union[str, Tuple[str, ...]]]]`: 命令别名
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_regex(pattern, flags=0, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
命令匹配规则参考: [正则匹配](rule.html#regex-regex-flags-0)
* **参数**
* `pattern: str`: 正则表达式
* `flags: Union[int, re.RegexFlag]`: 正则匹配标志
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## _class_ `CommandGroup`
基类:`object`
命令组,用于声明一组有相同名称前缀的命令。
### `__init__(cmd, **kwargs)`
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 命令前缀
* `**kwargs`: 其他传递给 `on_command` 的参数默认值,参考 [on_command](#on-command-cmd-rule-none-aliases-none-kwargs)
### `basecmd`
* **类型**: `Tuple[str, ...]`
* **说明**: 命令前缀
### `base_kwargs`
* **类型**: `Dict[str, Any]`
* **说明**: 其他传递给 `on_command` 的参数默认值
### `command(cmd, **kwargs)`
* **说明**
注册一个新的命令。
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 命令前缀
* `**kwargs`: 其他传递给 `on_command` 的参数,将会覆盖命令组默认值
* **返回**
* `Type[Matcher]`
## `load_plugin(module_path)`
* **说明**
使用 `importlib` 加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
* **参数**
* `module_path: str`: 插件名称 `path.to.your.plugin`
* **返回**
* `Optional[Plugin]`
## `load_plugins(*plugin_dir)`
* **说明**
导入目录下多个插件,以 `_` 开头的插件不会被导入!
* **参数**
* `*plugin_dir: str`: 插件路径
* **返回**
* `Set[Plugin]`
## `load_builtin_plugins()`
* **说明**
导入 NoneBot 内置插件
* **返回**
* `Plugin`
## `get_loaded_plugins()`
* **说明**
获取当前已导入的插件。
* **返回**
* `Set[Plugin]`

210
archive/2.0.0a4/api/rule.md Normal file
View File

@@ -0,0 +1,210 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.rule 模块
## 规则
每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
:::tip 提示
`RuleChecker` 既可以是 async function 也可以是 sync function但在最终会被 `nonebot.utils.run_sync` 转换为 async function
:::
## _class_ `Rule`
基类:`object`
* **说明**
`Matcher` 规则类,当事件传递时,在 `Matcher` 运行前进行检查。
* **示例**
```python
Rule(async_function) & sync_function
# 等价于
from nonebot.utils import run_sync
Rule(async_function, run_sync(sync_function))
```
### `__init__(*checkers)`
* **参数**
* `*checkers: Callable[[Bot, Event, dict], Awaitable[bool]]`: **异步** RuleChecker
### `checkers`
* **说明**
存储 `RuleChecker`
* **类型**
* `Set[Callable[[Bot, Event, dict], Awaitable[bool]]]`
### _async_ `__call__(bot, event, state)`
* **说明**
检查是否符合所有规则
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
* `state: dict`: 当前 State
* **返回**
* `bool`
## `startswith(msg)`
* **说明**
匹配消息开头
* **参数**
* `msg: str`: 消息开头字符串
## `endswith(msg)`
* **说明**
匹配消息结尾
* **参数**
* `msg: str`: 消息结尾字符串
## `keyword(*keywords)`
* **说明**
匹配消息关键词
* **参数**
* `*keywords: str`: 关键词
## `command(*cmds)`
* **说明**
命令形式匹配,根据配置里提供的 `command_start`, `command_sep` 判断消息是否为命令。
可以通过 `state["_prefix"]["command"]` 获取匹配成功的命令(例:`("test",)`),通过 `state["_prefix"]["raw_command"]` 获取匹配成功的原始命令文本(例:`"/test"`)。
* **参数**
* `*cmds: Union[str, Tuple[str, ...]]`: 命令内容
* **示例**
使用默认 `command_start`, `command_sep` 配置
命令 `("test",)` 可以匹配:`/test` 开头的消息
命令 `("test", "sub")` 可以匹配”`/test.sub` 开头的消息
:::tip 提示
命令内容与后续消息间无需空格!
:::
## `regex(regex, flags=0)`
* **说明**
根据正则表达式进行匹配。
可以通过 `state["_matched"]` 获取正则表达式匹配成功的文本。
* **参数**
* `regex: str`: 正则表达式
* `flags: Union[int, re.RegexFlag]`: 正则标志
:::tip 提示
正则表达式匹配使用 search 而非 match如需从头匹配请使用 `r"^xxx"` 来确保匹配开头
:::
## `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,9 @@ NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人
## 它如何工作? ## 它如何工作?
NoneBot 的运行离不开 酷 Q 和 CQHTTP 插件。酷 Q 扮演着「无头 QQ 客户端」的角色,它进行实际的消息、通知、请求的接收和发送,当 酷 Q 收到消息时,它将这个消息包装为一个事件(通知和请求同理),并通过它自己的插件机制将事件传送给 CQHTTP 插件,后者再根据其配置中的 `post_url``ws_reverse_url` 等项来将事件发送至 NoneBot。 <!-- TODO: how to work -->
在 NoneBot 收到事件前,它底层的 aiocqhttp 实际已经先看到了事件aiocqhttp 根据事件的类型信息,通知到 NoneBot 的相应函数。特别地,对于消息类型的事件,还将消息内容转换成了 `aiocqhttp.message.Message` 类型,以便处理。 ~~未填坑~~
NoneBot 的事件处理函数收到通知后对于不同类型的事件再做相应的预处理和解析然后调用对应的插件并向其提供适合此类事件的会话Session对象。NoneBot 插件的编写者要做的,就是利用 Session 对象中提供的数据,在插件的处理函数中实现所需的功能。
## 特色 ## 特色

View File

@@ -136,11 +136,11 @@ QQ 协议端举例:
现在,尝试向你的 QQ 机器人账号发送如下内容: 现在,尝试向你的 QQ 机器人账号发送如下内容:
```default ```default
/say 你好,世界 /echo 你好,世界
``` ```
到这里如果一切 OK你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅! 到这里如果一切 OK你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅!
<ClientOnly> <ClientOnly>
<Messenger :messages="[{ position: 'right', msg: '/say 你好,世界' }, { position: 'left', msg: '你好,世界' }]"/> <Messenger :messages="[{ position: 'right', msg: '/echo 你好,世界' }, { position: 'left', msg: '你好,世界' }]"/>
</ClientOnly> </ClientOnly>

View File

@@ -6,7 +6,10 @@
请确保你的 Python 版本 >= 3.7。 请确保你的 Python 版本 >= 3.7。
::: :::
请在安装 nonebot2 之前卸载 nonebot 1.x
```bash ```bash
pip uninstall nonebot
pip install nonebot2 pip install nonebot2
``` ```

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

@@ -1,4 +1,5 @@
{ {
"sidebar": {},
"locales": { "locales": {
"/": { "/": {
"label": "简体中文", "label": "简体中文",
@@ -17,6 +18,10 @@
{ {
"text": "API", "text": "API",
"link": "/api/" "link": "/api/"
},
{
"text": "插件广场",
"link": "/plugin-store"
} }
], ],
"sidebarDepth": 2, "sidebarDepth": 2,
@@ -47,21 +52,17 @@
"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.plugin 模块",
"path": "sched" "path": "plugin"
}, },
{ {
"title": "nonebot.log 模块", "title": "nonebot.matcher 模块",
"path": "log" "path": "matcher"
}, },
{ {
"title": "nonebot.rule 模块", "title": "nonebot.rule 模块",
@@ -71,13 +72,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

@@ -0,0 +1,162 @@
<template>
<div class="plugins">
<v-app>
<v-main>
<v-row>
<v-col cols="12" sm="4">
<v-text-field
v-model="filterText"
dense
rounded
outlined
clearable
hide-details
label="Filter Plugin"
>
<template v-slot:prepend-inner>
<div class="v-input__icon v-input__icon--prepend-inner">
<v-icon small>fa-filter</v-icon>
</div>
</template>
</v-text-field>
</v-col>
<v-col cols="12" sm="4">
<v-btn
block
color="primary"
target="_blank"
rel="noopener noreferrer"
href="https://github.com/nonebot/nonebot2/issues/new?template=plugin-publish.md"
>Publish Your Plugin
</v-btn>
</v-col>
<v-col cols="12" sm="4">
<v-pagination
v-model="page"
:length="pageNum"
prev-icon="fa-caret-left"
next-icon="fa-caret-right"
></v-pagination>
</v-col>
</v-row>
<hr />
<v-row>
<v-col
cols="12"
sm="6"
v-for="(plugin, index) in filteredPlugins"
:key="index"
>
<v-card>
<v-card-title>
{{ plugin.name }}
<v-spacer></v-spacer>
<a
class="repo-link"
v-if="repoLink(plugin.repo)"
rel="noopener noreferrer"
target="_blank"
:title="plugin.repo"
:href="repoLink(plugin.repo)"
>
<v-icon>fab fa-github</v-icon>
</a>
</v-card-title>
<v-card-text>{{ plugin.desc }}</v-card-text>
<v-card-text>
<v-icon x-small>fa-fingerprint</v-icon>
{{ plugin.id }}
<v-icon x-small class="ml-2">fa-user</v-icon>
{{ plugin.author }}
</v-card-text>
<v-card-actions>
<v-btn
block
depressed
class="btn-copy"
@click="copyCommand(plugin)"
>
copy nb-cli command
<v-icon right small>fa-copy</v-icon>
</v-btn>
<v-snackbar v-model="snackbar">Copied!</v-snackbar>
</v-card-actions>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-pagination
v-model="page"
:length="pageNum"
prev-icon="fa-caret-left"
next-icon="fa-caret-right"
></v-pagination>
</v-col>
</v-row>
</v-main>
</v-app>
</div>
</template>
<script>
import copy from "copy-to-clipboard";
import plugins from "../public/plugins.json";
export default {
name: "Plugins",
data() {
return {
plugins: plugins,
snackbar: false,
filterText: "",
page: 1
};
},
computed: {
pageNum() {
return Math.ceil(this.filteredPlugins.length / 10);
},
filteredPlugins() {
return this.plugins.filter(plugin => {
return (
plugin.id.indexOf(this.filterText) != -1 ||
plugin.name.indexOf(this.filterText) != -1 ||
plugin.desc.indexOf(this.filterText) != -1 ||
plugin.author.indexOf(this.filterText) != -1
);
});
}
},
methods: {
repoLink(repo) {
if (repo) {
return /^https?:/.test(repo) ? repo : `https://github.com/${repo}`;
}
return null;
},
copyCommand(plugin) {
copy(`nb plugin install ${plugin.id}`, {
format: "text/plain"
});
this.snackbar = true;
}
}
};
</script>
<style>
.v-application--wrap {
min-height: 0 !important;
}
</style>
<style scoped>
.repo-link {
text-decoration: none !important;
display: inline-block;
}
.repo-link:hover i {
color: #ea5252;
}
</style>

View File

@@ -13,7 +13,8 @@ module.exports = context => ({
*/ */
head: [ head: [
["link", { rel: "icon", href: "/logo.png" }], ["link", { rel: "icon", href: "/logo.png" }],
["meta", { name: "theme-color", content: "#d32f2f" }], ["link", { rel: "manifest", href: "/manifest.json" }],
["meta", { name: "theme-color", content: "#ea5252" }],
["meta", { name: "application-name", content: "NoneBot" }], ["meta", { name: "application-name", content: "NoneBot" }],
["meta", { name: "apple-mobile-web-app-title", content: "NoneBot" }], ["meta", { name: "apple-mobile-web-app-title", content: "NoneBot" }],
["meta", { name: "apple-mobile-web-app-capable", content: "yes" }], ["meta", { name: "apple-mobile-web-app-capable", content: "yes" }],
@@ -21,6 +22,26 @@ module.exports = context => ({
"meta", "meta",
{ name: "apple-mobile-web-app-status-bar-style", content: "black" } { name: "apple-mobile-web-app-status-bar-style", content: "black" }
], ],
[
"link",
{ rel: "apple-touch-icon", href: "/icons/apple-touch-icon-180x180.png" }
],
[
"link",
{
rel: "mask-icon",
href: "/icons/safari-pinned-tab.svg",
color: "#ea5252"
}
],
[
"meta",
{
name: "msapplication-TileImage",
content: "/icons/mstile-150x150.png"
}
],
["meta", { name: "msapplication-TileColor", content: "#ea5252" }],
[ [
"link", "link",
{ {
@@ -38,12 +59,12 @@ module.exports = context => ({
} }
}, },
theme: "titanium", theme: "nonebot",
themeConfig: { themeConfig: {
logo: "/logo.png", logo: "/logo.png",
repo: "nonebot/nonebot", repo: "nonebot/nonebot2",
docsDir: "docs", docsDir: "docs",
docsBranch: "dev2", docsBranch: "dev",
docsDirVersioned: "archive", docsDirVersioned: "archive",
docsDirPages: "pages", docsDirPages: "pages",
editLinks: true, editLinks: true,
@@ -58,7 +79,8 @@ module.exports = context => ({
nav: [ nav: [
{ text: "主页", link: "/" }, { text: "主页", link: "/" },
{ text: "指南", link: "/guide/" }, { text: "指南", link: "/guide/" },
{ text: "API", link: "/api/" } { text: "API", link: "/api/" },
{ text: "插件广场", link: "/plugin-store" }
], ],
sidebarDepth: 2, sidebarDepth: 2,
sidebar: { sidebar: {
@@ -88,21 +110,17 @@ 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.plugin 模块",
path: "sched" path: "plugin"
}, },
{ {
title: "nonebot.log 模块", title: "nonebot.matcher 模块",
path: "log" path: "matcher"
}, },
{ {
title: "nonebot.rule 模块", title: "nonebot.rule 模块",
@@ -112,13 +130,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"
} }
] ]
} }
@@ -131,6 +177,16 @@ module.exports = context => ({
plugins: [ plugins: [
"@vuepress/plugin-back-to-top", "@vuepress/plugin-back-to-top",
"@vuepress/plugin-medium-zoom", "@vuepress/plugin-medium-zoom",
[
"@vuepress/pwa",
{
serviceWorker: true,
updatePopup: {
message: "发现新内容",
buttonText: "刷新"
}
}
],
[ [
"versioning", "versioning",
{ {

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="500.000000pt" height="500.000000pt" viewBox="0 0 500.000000 500.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,500.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2478 4525 c-2 -1 -57 -5 -122 -9 -65 -4 -122 -8 -125 -10 -3 -2 -30
-7 -60 -11 -141 -18 -404 -102 -561 -180 -68 -34 -238 -132 -250 -145 -3 -3
-32 -25 -65 -49 -246 -182 -465 -442 -604 -719 -212 -422 -271 -933 -159
-1377 11 -44 21 -88 23 -97 8 -38 86 -238 120 -308 206 -415 542 -749 952
-945 90 -43 265 -111 309 -119 11 -2 48 -12 84 -21 82 -21 94 -24 155 -31 28
-4 53 -8 56 -10 11 -6 172 -17 264 -17 88 -1 239 9 282 17 11 3 43 8 70 12
280 40 611 181 855 363 118 87 221 181 311 284 173 195 309 424 392 657 14 41
28 82 30 90 2 8 13 51 25 95 25 93 27 103 34 159 3 22 8 52 11 65 9 46 16 166
17 276 1 704 -363 1359 -960 1728 -201 125 -484 234 -677 263 -16 3 -52 9 -79
15 -27 5 -92 12 -145 15 -53 3 -115 7 -138 8 -23 2 -43 2 -45 1z m162 -89
c691 -39 1330 -482 1622 -1121 74 -163 137 -364 152 -490 4 -27 9 -63 11 -80
17 -106 21 -305 10 -430 -8 -84 -49 -307 -60 -325 -2 -4 -6 -20 -9 -36 -3 -17
-23 -77 -44 -135 -194 -521 -605 -937 -1124 -1135 -562 -214 -1202 -155 -1713
160 -317 195 -564 466 -735 806 -60 120 -141 358 -156 465 -3 17 -9 55 -15 85
-11 61 -23 217 -22 295 3 525 201 1002 571 1373 340 340 782 542 1247 568 55
3 101 7 102 8 2 1 20 1 40 0 21 -2 76 -5 123 -8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,20 @@
{
"name": "NoneBot",
"short_name": "NoneBot",
"background-color": "#ffffff",
"theme-color": "#ea5252",
"description": "An asynchronous python bot framework.",
"display": "standalone",
"icons": [
{
"src": "/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
}
]
}

View File

@@ -0,0 +1,10 @@
[
{
"id": "nonebot-plugin-status",
"link": "nonebot-plugin-status",
"name": "状态监控",
"desc": "通过戳一戳获取服务器状态",
"author": "nonebot",
"repo": "nonebot/nonebot2"
}
]

View File

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

View File

@@ -7,16 +7,13 @@
* [nonebot](nonebot.html) * [nonebot](nonebot.html)
* [nonebot.typing](typing.html)
* [nonebot.config](config.html) * [nonebot.config](config.html)
* [nonebot.sched](sched.html) * [nonebot.plugin](plugin.html)
* [nonebot.log](log.html) * [nonebot.matcher](matcher.html)
* [nonebot.rule](rule.html) * [nonebot.rule](rule.html)
@@ -25,7 +22,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()`
* **说明**
提取消息内纯文本消息

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

@@ -0,0 +1,415 @@
---
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)
CQHTTP 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。
## _class_ `Message`
基类:[`nonebot.adapters.BaseMessage`](#None)
CQHTTP 协议 Message 适配。

View File

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

246
docs/api/drivers/README.md Normal file
View File

@@ -0,0 +1,246 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers 模块
## 后端驱动适配基类
各驱动请继承以下基类
## _class_ `BaseDriver`
基类:`abc.ABC`
Driver 基类。将后端框架封装,以满足适配器使用。
### `_adapters`
* **类型**
`Dict[str, Type[Bot]]`
* **说明**
已注册的适配器列表
### _abstract_ `__init__(env, config)`
* **参数**
* `env: Env`: 包含环境信息的 Env 对象
* `config: Config`: 包含配置信息的 Config 对象
### `env`
* **类型**
`str`
* **说明**
环境名称
### `config`
* **类型**
`Config`
* **说明**
配置对象
### `_clients`
* **类型**
`Dict[str, Bot]`
* **说明**
已连接的 Bot
### _classmethod_ `register_adapter(name, adapter)`
* **说明**
注册一个协议适配器
* **参数**
* `name: str`: 适配器名称,用于在连接时进行识别
* `adapter: Type[Bot]`: 适配器 Class
### _abstract property_ `type`
驱动类型名称
### _abstract property_ `server_app`
驱动 APP 对象
### _abstract property_ `asgi`
驱动 ASGI 对象
### _abstract property_ `logger`
驱动专属 logger 日志记录器
### _property_ `bots`
* **类型**
`Dict[str, Bot]`
* **说明**
获取当前所有已连接的 Bot
### _abstract_ `on_startup(func)`
注册一个在驱动启动时运行的函数
### _abstract_ `on_shutdown(func)`
注册一个在驱动停止时运行的函数
### _abstract_ `run(host=None, port=None, *args, **kwargs)`
* **说明**
启动驱动框架
* **参数**
* `host: Optional[str]`: 驱动绑定 IP
* `post: Optional[int]`: 驱动绑定端口
* `*args`
* `**kwargs`
### _abstract async_ `_handle_http()`
用于处理 HTTP 类型请求的函数
### _abstract async_ `_handle_ws_reverse()`
用于处理 WebSocket 类型请求的函数
## _class_ `BaseWebSocket`
基类:`object`
WebSocket 连接封装,统一接口方便外部调用。
### _abstract_ `__init__(websocket)`
* **参数**
* `websocket: Any`: WebSocket 连接对象
### _property_ `websocket`
WebSocket 连接对象
### _abstract property_ `closed`
* **类型**
`bool`
* **说明**
连接是否已经关闭
### _abstract async_ `accept()`
接受 WebSocket 连接请求
### _abstract async_ `close(code)`
关闭 WebSocket 连接请求
### _abstract async_ `receive()`
接收一条 WebSocket 信息
### _abstract async_ `send(data)`
发送一条 WebSocket 信息

125
docs/api/drivers/fastapi.md Normal file
View File

@@ -0,0 +1,125 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers.fastapi 模块
## FastAPI 驱动适配
后端使用方法请参考: [FastAPI 文档](https://fastapi.tiangolo.com/)
## _class_ `Driver`
基类:[`nonebot.drivers.BaseDriver`](#None)
FastAPI 驱动框架
### `__init__(env, config)`
* **参数**
* `env: Env`: 包含环境信息的 Env 对象
* `config: Config`: 包含配置信息的 Config 对象
### _property_ `type`
驱动名称: `fastapi`
### _property_ `server_app`
`FastAPI APP` 对象
### _property_ `asgi`
`FastAPI APP` 对象
### _property_ `logger`
fastapi 使用的 logger
### `on_startup(func)`
参考文档: [Events](https://fastapi.tiangolo.com/advanced/events/#startup-event)
### `on_shutdown(func)`
参考文档: [Events](https://fastapi.tiangolo.com/advanced/events/#startup-event)
### `run(host=None, port=None, *, app=None, **kwargs)`
使用 `uvicorn` 启动 FastAPI
### _async_ `_handle_http(adapter, data=Body(Ellipsis), x_self_id=Header(None), x_signature=Header(None), auth=Depends(get_auth_bearer))`
用于处理 HTTP 类型请求的函数
### _async_ `_handle_ws_reverse(adapter, websocket, x_self_id=Header(None), auth=Depends(get_auth_bearer))`
用于处理 WebSocket 类型请求的函数
## _class_ `WebSocket`
基类:[`nonebot.drivers.BaseWebSocket`](#None)
### `__init__(websocket)`
* **参数**
* `websocket: Any`: WebSocket 连接对象
### _property_ `closed`
* **类型**
`bool`
* **说明**
连接是否已经关闭
### _async_ `accept()`
接受 WebSocket 连接请求
### _async_ `close(code=1000)`
关闭 WebSocket 连接请求
### _async_ `receive()`
接收一条 WebSocket 信息
### _async_ `send(data)`
发送一条 WebSocket 信息

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

@@ -0,0 +1,497 @@
---
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, **kwargs)`
* **说明**
发送一条消息给当前交互用户
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
### _async classmethod_ `finish(message=None, **kwargs)`
* **说明**
发送一条消息给当前交互用户并结束当前事件响应器
* **参数**
* `message: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
### _async classmethod_ `pause(prompt=None, **kwargs)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
### _async classmethod_ `reject(prompt=None, **kwargs)`
* **说明**
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后重新运行当前处理函数
* **参数**
* `prompt: Union[str, Message, MessageSegment]`: 消息内容
* `**kwargs`: 其他传递给 `bot.send` 的参数,请参考对应 adapter 的 bot 对象 api
## _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()`

629
docs/api/plugin.md Normal file
View File

@@ -0,0 +1,629 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.plugin 模块
## 插件
为 NoneBot 插件开发提供便携的定义函数。
## `plugins`
* **类型**
`Dict[str, Plugin]`
* **说明**
已加载的插件
## _class_ `Plugin`
基类:`object`
存储插件信息
### `name`
* **类型**: `str`
* **说明**: 插件名称,使用 文件/文件夹 名称作为插件名
### `module`
* **类型**: `ModuleType`
* **说明**: 插件模块对象
### `matcher`
* **类型**: `Set[Type[Matcher]]`
* **说明**: 插件内定义的 `Matcher`
## `on(type='', rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个基础事件响应器,可自定义类型。
* **参数**
* `type: str`: 事件响应器类型
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_metaevent(rule=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个元事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_message(rule=None, permission=None, *, handlers=None, temp=False, priority=1, block=True, state=None)`
* **说明**
注册一个消息事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_notice(rule=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个通知事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_request(rule=None, *, handlers=None, temp=False, priority=1, block=False, state=None)`
* **说明**
注册一个请求事件响应器。
* **参数**
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_startswith(msg, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息的\*\*文本部分\*\*以指定内容开头时响应。
* **参数**
* `msg: str`: 指定消息开头内容
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_endswith(msg, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息的\*\*文本部分\*\*以指定内容结尾时响应。
* **参数**
* `msg: str`: 指定消息结尾内容
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_keyword(keywords, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
* **参数**
* `keywords: Set[str]`: 关键词列表
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_command(cmd, rule=None, aliases=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息以指定命令开头时响应。
命令匹配规则参考: [命令形式匹配](rule.html#command-command)
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 指定命令内容
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `aliases: Optional[Set[Union[str, Tuple[str, ...]]]]`: 命令别名
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## `on_regex(pattern, flags=0, rule=None, **kwargs)`
* **说明**
注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
命令匹配规则参考: [正则匹配](rule.html#regex-regex-flags-0)
* **参数**
* `pattern: str`: 正则表达式
* `flags: Union[int, re.RegexFlag]`: 正则匹配标志
* `rule: Optional[Union[Rule, RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
* `handlers: Optional[List[Handler]]`: 事件处理函数列表
* `temp: bool`: 是否为临时事件响应器(仅执行一次)
* `priority: int`: 事件响应器优先级
* `block: bool`: 是否阻止事件向更低优先级传递
* `state: Optional[dict]`: 默认的 state
* **返回**
* `Type[Matcher]`
## _class_ `CommandGroup`
基类:`object`
命令组,用于声明一组有相同名称前缀的命令。
### `__init__(cmd, **kwargs)`
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 命令前缀
* `**kwargs`: 其他传递给 `on_command` 的参数默认值,参考 [on_command](#on-command-cmd-rule-none-aliases-none-kwargs)
### `basecmd`
* **类型**: `Tuple[str, ...]`
* **说明**: 命令前缀
### `base_kwargs`
* **类型**: `Dict[str, Any]`
* **说明**: 其他传递给 `on_command` 的参数默认值
### `command(cmd, **kwargs)`
* **说明**
注册一个新的命令。
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 命令前缀
* `**kwargs`: 其他传递给 `on_command` 的参数,将会覆盖命令组默认值
* **返回**
* `Type[Matcher]`
## `load_plugin(module_path)`
* **说明**
使用 `importlib` 加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
* **参数**
* `module_path: str`: 插件名称 `path.to.your.plugin`
* **返回**
* `Optional[Plugin]`
## `load_plugins(*plugin_dir)`
* **说明**
导入目录下多个插件,以 `_` 开头的插件不会被导入!
* **参数**
* `*plugin_dir: str`: 插件路径
* **返回**
* `Set[Plugin]`
## `load_builtin_plugins()`
* **说明**
导入 NoneBot 内置插件
* **返回**
* `Plugin`
## `get_loaded_plugins()`
* **说明**
获取当前已导入的插件。
* **返回**
* `Set[Plugin]`

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,91 @@ Rule(async_function, run_sync(sync_function))
* `msg: str`: 消息结尾字符串 * `msg: str`: 消息结尾字符串
## `keyword(*keywords)`
* **说明**
匹配消息关键词
* **参数**
* `*keywords: str`: 关键词
## `command(*cmds)`
* **说明**
命令形式匹配,根据配置里提供的 `command_start`, `command_sep` 判断消息是否为命令。
可以通过 `state["_prefix"]["command"]` 获取匹配成功的命令(例:`("test",)`),通过 `state["_prefix"]["raw_command"]` 获取匹配成功的原始命令文本(例:`"/test"`)。
* **参数**
* `*cmds: Union[str, Tuple[str, ...]]`: 命令内容
* **示例**
使用默认 `command_start`, `command_sep` 配置
命令 `("test",)` 可以匹配:`/test` 开头的消息
命令 `("test", "sub")` 可以匹配”`/test.sub` 开头的消息
:::tip 提示
命令内容与后续消息间无需空格!
:::
## `regex(regex, flags=0)`
* **说明**
根据正则表达式进行匹配。
可以通过 `state["_matched"]` 获取正则表达式匹配成功的文本。
* **参数**
* `regex: str`: 正则表达式
* `flags: Union[int, re.RegexFlag]`: 正则标志
:::tip 提示
正则表达式匹配使用 search 而非 match如需从头匹配请使用 `r"^xxx"` 来确保匹配开头
:::
## `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,9 @@ NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人
## 它如何工作? ## 它如何工作?
NoneBot 的运行离不开 酷 Q 和 CQHTTP 插件。酷 Q 扮演着「无头 QQ 客户端」的角色,它进行实际的消息、通知、请求的接收和发送,当 酷 Q 收到消息时,它将这个消息包装为一个事件(通知和请求同理),并通过它自己的插件机制将事件传送给 CQHTTP 插件,后者再根据其配置中的 `post_url``ws_reverse_url` 等项来将事件发送至 NoneBot。 <!-- TODO: how to work -->
在 NoneBot 收到事件前,它底层的 aiocqhttp 实际已经先看到了事件aiocqhttp 根据事件的类型信息,通知到 NoneBot 的相应函数。特别地,对于消息类型的事件,还将消息内容转换成了 `aiocqhttp.message.Message` 类型,以便处理。 ~~未填坑~~
NoneBot 的事件处理函数收到通知后对于不同类型的事件再做相应的预处理和解析然后调用对应的插件并向其提供适合此类事件的会话Session对象。NoneBot 插件的编写者要做的,就是利用 Session 对象中提供的数据,在插件的处理函数中实现所需的功能。
## 特色 ## 特色

View File

@@ -136,11 +136,11 @@ QQ 协议端举例:
现在,尝试向你的 QQ 机器人账号发送如下内容: 现在,尝试向你的 QQ 机器人账号发送如下内容:
```default ```default
/say 你好,世界 /echo 你好,世界
``` ```
到这里如果一切 OK你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅! 到这里如果一切 OK你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅!
<ClientOnly> <ClientOnly>
<Messenger :messages="[{ position: 'right', msg: '/say 你好,世界' }, { position: 'left', msg: '你好,世界' }]"/> <Messenger :messages="[{ position: 'right', msg: '/echo 你好,世界' }, { position: 'left', msg: '你好,世界' }]"/>
</ClientOnly> </ClientOnly>

View File

@@ -6,7 +6,10 @@
请确保你的 Python 版本 >= 3.7。 请确保你的 Python 版本 >= 3.7。
::: :::
请在安装 nonebot2 之前卸载 nonebot 1.x
```bash ```bash
pip uninstall nonebot
pip install nonebot2 pip install nonebot2
``` ```

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,17 @@ 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.plugin <plugin.html>`_
- `nonebot.log <log.html>`_ - `nonebot.matcher <matcher.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:

12
docs_build/plugin.rst Normal file
View File

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

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,25 @@
#!/usr/bin/env python3 """
# -*- 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 +129,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 +176,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 +234,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,9 @@
#!/usr/bin/env python3 """
# -*- coding: utf-8 -*- 协议适配基类
============
各协议请继承以下基类,并使用 ``driver.register_adapter`` 注册适配器
"""
import abc import abc
from functools import reduce, partial from functools import reduce, partial
@@ -11,6 +15,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 +26,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 +52,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 +114,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 +155,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 +166,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 +177,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 +188,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 +199,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 +210,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 +221,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 +232,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 +243,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 +260,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 +280,33 @@ 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)
def get(self, key, default=None):
return getattr(self, key, default)
@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 +340,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 +360,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 +385,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

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
CQHTTP (OneBot) v11 协议适配 CQHTTP (OneBot) v11 协议适配
============================ ============================
@@ -28,14 +26,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 码转义。 :说明:
``escape_comma`` 参数控制是否转义逗号(``,`` 对字符串进行 CQ 码转义
:参数:
* ``s: str``: 需要转义的字符串
* ``escape_comma: bool``: 是否转义逗号(``,``)。
""" """
s = s.replace("&", "&amp;") \ s = s.replace("&", "&amp;") \
.replace("[", "&#91;") \ .replace("[", "&#91;") \
@@ -46,7 +59,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 +75,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 +106,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 +161,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 +180,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 +197,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 +253,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 +263,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 +276,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 +311,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 +378,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 +433,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 +446,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 +467,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 +499,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 +513,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 +527,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 +541,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 +555,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 +569,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 +583,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 +597,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 +611,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
@@ -446,11 +633,14 @@ class Event(BaseEvent):
class MessageSegment(BaseMessageSegment): class MessageSegment(BaseMessageSegment):
"""
CQHTTP 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。
"""
@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)
@@ -624,6 +814,9 @@ class MessageSegment(BaseMessageSegment):
class Message(BaseMessage): class Message(BaseMessage):
"""
CQHTTP 协议 Message 适配。
"""
@staticmethod @staticmethod
@overrides(BaseMessage) @overrides(BaseMessage)

View File

@@ -1,15 +1,91 @@
from nonebot.adapters import BaseBot import asyncio
from nonebot.typing import Any, Dict, List, Union, Message, Optional
from nonebot.config import Config
from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
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 = ...,
no_cache: bool = False, self_id: Optional[int] = ...) -> Dict[str, Any]:
self_id: Optional[int] = None) -> 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 = ...,
type: str = "all", self_id: Optional[int] = ...
self_id: Optional[int] = None) -> Dict[str, Any]: ) -> 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(BaseEvent):
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(BaseMessageSegment):
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(BaseMessage):
@staticmethod
def _construct(msg: Union[str, dict, list]) -> Iterable[MessageSegment]:
...

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
配置 配置
==== ====
@@ -211,10 +209,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,9 @@
#!/usr/bin/env python3 """
# -*- coding: utf-8 -*- 后端驱动适配基类
===============
各驱动请继承以下基类
"""
import abc import abc
@@ -9,16 +13,48 @@ 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):
"""
:参数:
* ``env: Env``: 包含环境信息的 Env 对象
* ``config: Config``: 包含配置信息的 Config 对象
"""
self.env = env.environment self.env = env.environment
"""
:类型: ``str``
:说明: 环境名称
"""
self.config = config self.config = config
"""
:类型: ``Config``
:说明: 配置对象
"""
self._clients: Dict[str, Bot] = {} self._clients: Dict[str, Bot] = {}
"""
:类型: ``Dict[str, Bot]``
:说明: 已连接的 Bot
"""
@classmethod @classmethod
def register_adapter(cls, name: str, adapter: Type[Bot]): def register_adapter(cls, name: str, adapter: Type[Bot]):
"""
:说明:
注册一个协议适配器
:参数:
* ``name: str``: 适配器名称,用于在连接时进行识别
* ``adapter: Type[Bot]``: 适配器 Class
"""
cls._adapters[name] = adapter cls._adapters[name] = adapter
logger.opt( logger.opt(
colors=True).debug(f'Succeeded to load adapter "<y>{name}</y>"') colors=True).debug(f'Succeeded to load adapter "<y>{name}</y>"')
@@ -26,33 +62,43 @@ class BaseDriver(abc.ABC):
@property @property
@abc.abstractmethod @abc.abstractmethod
def type(self): def type(self):
"""驱动类型名称"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def server_app(self): def server_app(self):
"""驱动 APP 对象"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def asgi(self): def asgi(self):
"""驱动 ASGI 对象"""
raise NotImplementedError raise NotImplementedError
@property @property
@abc.abstractmethod @abc.abstractmethod
def logger(self): def logger(self):
"""驱动专属 logger 日志记录器"""
raise NotImplementedError raise NotImplementedError
@property @property
def bots(self) -> Dict[str, Bot]: def bots(self) -> Dict[str, Bot]:
"""
:类型: ``Dict[str, Bot]``
:说明: 获取当前所有已连接的 Bot
"""
return self._clients return self._clients
@abc.abstractmethod @abc.abstractmethod
def on_startup(self, func: Callable) -> Callable: def on_startup(self, func: Callable) -> Callable:
"""注册一个在驱动启动时运行的函数"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
def on_shutdown(self, func: Callable) -> Callable: def on_shutdown(self, func: Callable) -> Callable:
"""注册一个在驱动停止时运行的函数"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
@@ -61,44 +107,69 @@ class BaseDriver(abc.ABC):
port: Optional[int] = None, port: Optional[int] = None,
*args, *args,
**kwargs): **kwargs):
"""
:说明:
启动驱动框架
:参数:
* ``host: Optional[str]``: 驱动绑定 IP
* ``post: Optional[int]``: 驱动绑定端口
* ``*args``
* ``**kwargs``
"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def _handle_http(self): async def _handle_http(self):
"""用于处理 HTTP 类型请求的函数"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def _handle_ws_reverse(self): async def _handle_ws_reverse(self):
"""用于处理 WebSocket 类型请求的函数"""
raise NotImplementedError raise NotImplementedError
class BaseWebSocket(object): class BaseWebSocket(object):
"""WebSocket 连接封装,统一接口方便外部调用。"""
@abc.abstractmethod @abc.abstractmethod
def __init__(self, websocket): def __init__(self, websocket):
"""
:参数:
* ``websocket: Any``: WebSocket 连接对象
"""
self._websocket = websocket self._websocket = websocket
@property @property
def websocket(self): def websocket(self):
"""WebSocket 连接对象"""
return self._websocket return self._websocket
@property @property
@abc.abstractmethod @abc.abstractmethod
def closed(self): def closed(self):
"""
:类型: ``bool``
:说明: 连接是否已经关闭
"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def accept(self): async def accept(self):
"""接受 WebSocket 连接请求"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def close(self, code: int): async def close(self, code: int):
"""关闭 WebSocket 连接请求"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def receive(self) -> dict: async def receive(self) -> dict:
"""接收一条 WebSocket 信息"""
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod @abc.abstractmethod
async def send(self, data: dict): async def send(self, data: dict):
"""发送一条 WebSocket 信息"""
raise NotImplementedError raise NotImplementedError

View File

@@ -1,5 +1,12 @@
#!/usr/bin/env python3 """
# -*- coding: utf-8 -*- FastAPI 驱动适配
================
后端使用方法请参考: `FastAPI 文档`_
.. _FastAPI 文档:
https://fastapi.tiangolo.com/
"""
import hmac import hmac
import json import json
@@ -23,7 +30,7 @@ def get_auth_bearer(access_token: Optional[str] = Header(
if not access_token: if not access_token:
return None return None
scheme, _, param = access_token.partition(" ") scheme, _, param = access_token.partition(" ")
if scheme.lower() != "bearer": if scheme.lower() not in ["bearer", "token"]:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated", detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"}) headers={"WWW-Authenticate": "Bearer"})
@@ -31,6 +38,7 @@ def get_auth_bearer(access_token: Optional[str] = Header(
class Driver(BaseDriver): class Driver(BaseDriver):
"""FastAPI 驱动框架"""
def __init__(self, env: Env, config: Config): def __init__(self, env: Env, config: Config):
super().__init__(env, config) super().__init__(env, config)
@@ -50,29 +58,35 @@ class Driver(BaseDriver):
@property @property
@overrides(BaseDriver) @overrides(BaseDriver)
def type(self) -> str: def type(self) -> str:
"""驱动名称: ``fastapi``"""
return "fastapi" return "fastapi"
@property @property
@overrides(BaseDriver) @overrides(BaseDriver)
def server_app(self) -> FastAPI: def server_app(self) -> FastAPI:
"""``FastAPI APP`` 对象"""
return self._server_app return self._server_app
@property @property
@overrides(BaseDriver) @overrides(BaseDriver)
def asgi(self): def asgi(self):
"""``FastAPI APP`` 对象"""
return self._server_app return self._server_app
@property @property
@overrides(BaseDriver) @overrides(BaseDriver)
def logger(self) -> logging.Logger: def logger(self) -> logging.Logger:
"""fastapi 使用的 logger"""
return logging.getLogger("fastapi") return logging.getLogger("fastapi")
@overrides(BaseDriver) @overrides(BaseDriver)
def on_startup(self, func: Callable) -> Callable: def on_startup(self, func: Callable) -> Callable:
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_"""
return self.server_app.on_event("startup")(func) return self.server_app.on_event("startup")(func)
@overrides(BaseDriver) @overrides(BaseDriver)
def on_shutdown(self, func: Callable) -> Callable: def on_shutdown(self, func: Callable) -> Callable:
"""参考文档: `Events <https://fastapi.tiangolo.com/advanced/events/#startup-event>`_"""
return self.server_app.on_event("shutdown")(func) return self.server_app.on_event("shutdown")(func)
@overrides(BaseDriver) @overrides(BaseDriver)
@@ -82,6 +96,7 @@ class Driver(BaseDriver):
*, *,
app: Optional[str] = None, app: Optional[str] = None,
**kwargs): **kwargs):
"""使用 ``uvicorn`` 启动 FastAPI"""
LOGGING_CONFIG = { LOGGING_CONFIG = {
"version": 1, "version": 1,
"disable_existing_loggers": False, "disable_existing_loggers": False,
@@ -200,8 +215,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,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
异常 异常
==== ====

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
日志 日志
==== ====

View File

@@ -1,5 +1,9 @@
#!/usr/bin/env python3 """
# -*- coding: utf-8 -*- 事件响应器
==========
该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行 对话 。
"""
from nonebot.log import logger from nonebot.log import logger
import typing import typing
@@ -16,6 +20,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 +40,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 +116,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 +126,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 +164,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 +218,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 +244,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):
state["_current_key"] = key
if key not in state: if key not in state:
state["_current_key"] = key
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 +286,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)
@@ -193,36 +296,70 @@ class Matcher(metaclass=MatcherMeta):
return _decorator return _decorator
@classmethod @classmethod
async def finish( async def send(cls, message: Union[str, Message, MessageSegment], **kwargs):
cls, """
prompt: Optional[Union[str, Message, :说明:
MessageSegment]] = None) -> NoReturn: 发送一条消息给当前交互用户
bot: Bot = current_bot.get() :参数:
event: Event = current_event.get() * ``message: Union[str, Message, MessageSegment]``: 消息内容
if prompt: * ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
await bot.send(event=event, message=prompt) """
bot = current_bot.get()
event = current_event.get()
await bot.send(event=event, message=message, **kwargs)
@classmethod
async def finish(cls,
message: Optional[Union[str, Message,
MessageSegment]] = None,
**kwargs) -> NoReturn:
"""
:说明:
发送一条消息给当前交互用户并结束当前事件响应器
:参数:
* ``message: Union[str, Message, MessageSegment]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
bot = current_bot.get()
event = current_event.get()
if message:
await bot.send(event=event, message=message, **kwargs)
raise FinishedException raise FinishedException
@classmethod @classmethod
async def pause( async def pause(cls,
cls, prompt: Optional[Union[str, Message,
prompt: Optional[Union[str, Message, MessageSegment]] = None,
MessageSegment]] = None) -> NoReturn: **kwargs) -> NoReturn:
bot: Bot = current_bot.get() """
event: Event = current_event.get() :说明:
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数
:参数:
* ``prompt: Union[str, Message, MessageSegment]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
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, **kwargs)
raise PausedException raise PausedException
@classmethod @classmethod
async def reject( async def reject(cls,
cls, prompt: Optional[Union[str, Message,
prompt: Optional[Union[str, Message, MessageSegment]] = None,
MessageSegment]] = None) -> NoReturn: **kwargs) -> NoReturn:
bot: Bot = current_bot.get() """
event: Event = current_event.get() :说明:
发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后重新运行当前处理函数
:参数:
* ``prompt: Union[str, Message, MessageSegment]``: 消息内容
* ``**kwargs``: 其他传递给 ``bot.send`` 的参数,请参考对应 adapter 的 bot 对象 api
"""
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, **kwargs)
raise RejectedException raise RejectedException
# 运行handlers # 运行handlers
@@ -273,3 +410,198 @@ 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:
self._default_parser = func
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],
**kwargs):
bot = current_bot.get()
event = current_event.get()
await bot.send(event=event, message=message, **kwargs)
async def finish(self,
message: Optional[Union[str, Message,
MessageSegment]] = None,
**kwargs) -> NoReturn:
bot = current_bot.get()
event = current_event.get()
if message:
await bot.send(event=event, message=message, **kwargs)
raise FinishedException
async def pause(self,
prompt: Optional[Union[str, Message,
MessageSegment]] = None,
**kwargs) -> NoReturn:
bot = current_bot.get()
event = current_event.get()
if prompt:
await bot.send(event=event, message=prompt, **kwargs)
raise PausedException
async def reject(self,
prompt: Optional[Union[str, Message,
MessageSegment]] = None,
**kwargs) -> NoReturn:
bot = current_bot.get()
event = current_event.get()
if prompt:
await bot.send(event=event, message=prompt, **kwargs)
raise RejectedException

View File

@@ -1,11 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio import asyncio
from datetime import datetime 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
@@ -56,6 +54,7 @@ async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event,
async def handle_event(bot: Bot, event: Event): async def handle_event(bot: Bot, event: Event):
show_log = True
log_msg = f"<m>{bot.type.upper()} </m>| {event.self_id} [{event.name}]: " log_msg = f"<m>{bot.type.upper()} </m>| {event.self_id} [{event.name}]: "
if event.type == "message": if event.type == "message":
log_msg += f"Message {event.id} from " log_msg += f"Message {event.id} from "
@@ -64,15 +63,19 @@ 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) show_log = False
if show_log:
logger.opt(colors=True).info(log_msg)
coros = [] coros = []
state = {} state = {}
@@ -100,7 +103,8 @@ async def handle_event(bot: Bot, event: Event):
for matcher in matchers[priority] for matcher in matchers[priority]
] ]
logger.debug(f"Checking for matchers in priority {priority}...") if show_log:
logger.debug(f"Checking for matchers in priority {priority}...")
results = await asyncio.gather(*pending_tasks, return_exceptions=True) results = await asyncio.gather(*pending_tasks, return_exceptions=True)
i = 0 i = 0

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
权限 权限
==== ====
@@ -14,7 +12,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 +51,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

@@ -1,5 +1,9 @@
#!/usr/bin/env python3 """
# -*- coding: utf-8 -*- 插件
====
为 NoneBot 插件开发提供便携的定义函数。
"""
import re import re
import sys import sys
@@ -12,32 +16,65 @@ from nonebot.log import logger
from nonebot.matcher import Matcher 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.rule import Rule, startswith, endswith, command, regex from nonebot.rule import Rule, startswith, endswith, keyword, command, regex
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType from nonebot.typing import Any, Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
plugins: Dict[str, "Plugin"] = {} plugins: Dict[str, "Plugin"] = {}
"""
:类型: ``Dict[str, Plugin]``
:说明: 已加载的插件
"""
_tmp_matchers: Set[Type[Matcher]] = set() _tmp_matchers: Set[Type[Matcher]] = set()
@dataclass(eq=False) @dataclass(eq=False)
class Plugin(object): class Plugin(object):
"""存储插件信息"""
name: str name: str
"""
- **类型**: ``str``
- **说明**: 插件名称,使用 文件/文件夹 名称作为插件名
"""
module: ModuleType module: ModuleType
"""
- **类型**: ``ModuleType``
- **说明**: 插件模块对象
"""
matcher: Set[Type[Matcher]] matcher: Set[Type[Matcher]]
"""
- **类型**: ``Set[Type[Matcher]]``
- **说明**: 插件内定义的 ``Matcher``
"""
def on(rule: Union[Rule, RuleChecker] = Rule(), def on(type: str = "",
permission: Permission = Permission(), rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Optional[Permission] = None,
*, *,
handlers: Optional[List[Handler]] = None, handlers: Optional[List[Handler]] = None,
temp: bool = False, temp: bool = False,
priority: int = 1, priority: int = 1,
block: bool = False, block: bool = False,
state: Optional[dict] = None) -> Type[Matcher]: state: Optional[dict] = None) -> Type[Matcher]:
matcher = Matcher.new("", """
:说明:
注册一个基础事件响应器,可自定义类型。
:参数:
* ``type: str``: 事件响应器类型
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
matcher = Matcher.new(type,
Rule() & rule, Rule() & rule,
permission, permission or Permission(),
temp=temp, temp=temp,
priority=priority, priority=priority,
block=block, block=block,
@@ -47,13 +84,26 @@ 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,
priority: int = 1, priority: int = 1,
block: bool = False, block: bool = False,
state: Optional[dict] = None) -> Type[Matcher]: state: Optional[dict] = None) -> Type[Matcher]:
"""
:说明:
注册一个元事件响应器。
:参数:
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
matcher = Matcher.new("meta_event", matcher = Matcher.new("meta_event",
Rule() & rule, Rule() & rule,
Permission(), Permission(),
@@ -66,17 +116,31 @@ 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,
priority: int = 1, priority: int = 1,
block: bool = True, block: bool = True,
state: Optional[dict] = None) -> Type[Matcher]: state: Optional[dict] = None) -> Type[Matcher]:
"""
:说明:
注册一个消息事件响应器。
:参数:
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``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,13 +150,26 @@ 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,
priority: int = 1, priority: int = 1,
block: bool = False, block: bool = False,
state: Optional[dict] = None) -> Type[Matcher]: state: Optional[dict] = None) -> Type[Matcher]:
"""
:说明:
注册一个通知事件响应器。
:参数:
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
matcher = Matcher.new("notice", matcher = Matcher.new("notice",
Rule() & rule, Rule() & rule,
Permission(), Permission(),
@@ -105,13 +182,26 @@ 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,
priority: int = 1, priority: int = 1,
block: bool = False, block: bool = False,
state: Optional[dict] = None) -> Type[Matcher]: state: Optional[dict] = None) -> Type[Matcher]:
"""
:说明:
注册一个请求事件响应器。
:参数:
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
matcher = Matcher.new("request", matcher = Matcher.new("request",
Rule() & rule, Rule() & rule,
Permission(), Permission(),
@@ -125,29 +215,90 @@ 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) & """
rule, permission, **kwargs) if rule else on_message( :说明:
startswith(msg), permission, **kwargs) 注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。
:参数:
* ``msg: str``: 指定消息开头内容
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
return on_message(startswith(msg) & rule, **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) & """
rule, permission, **kwargs) if rule else on_message( :说明:
startswith(msg), permission, **kwargs) 注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。
:参数:
* ``msg: str``: 指定消息结尾内容
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
return on_message(endswith(msg) & rule, **kwargs)
def on_keyword(keywords: Set[str],
rule: Optional[Union[Rule, RuleChecker]] = None,
**kwargs) -> Type[Matcher]:
"""
:说明:
注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。
:参数:
* ``keywords: Set[str]``: 关键词列表
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
return on_message(keyword(*keywords) & rule, **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) -> Type[Matcher]:
if isinstance(cmd, str): """
cmd = (cmd,) :说明:
注册一个消息事件响应器,并且当消息以指定命令开头时响应。
命令匹配规则参考: `命令形式匹配 <rule.html#command-command>`_
:参数:
* ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
async def _strip_cmd(bot, event, state: dict): async def _strip_cmd(bot, event, state: dict):
message = event.message message = event.message
@@ -157,23 +308,85 @@ 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( commands = set([cmd]) | (aliases or set())
command(cmd) & return on_message(command(*commands) & rule, handlers=handlers, **kwargs)
rule, permission, handlers=handlers, **kwargs) if rule else on_message(
command(cmd), permission, 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) & """
rule, permission, **kwargs) if rule else on_message( :说明:
regex(pattern, flags), permission, **kwargs) 注册一个消息事件响应器,并且当消息匹配正则表达式时响应。
命令匹配规则参考: `正则匹配 <rule.html#regex-regex-flags-0>`_
:参数:
* ``pattern: str``: 正则表达式
* ``flags: Union[int, re.RegexFlag]``: 正则匹配标志
* ``rule: Optional[Union[Rule, RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
* ``priority: int``: 事件响应器优先级
* ``block: bool``: 是否阻止事件向更低优先级传递
* ``state: Optional[dict]``: 默认的 state
:返回:
- ``Type[Matcher]``
"""
return on_message(regex(pattern, flags) & rule, **kwargs)
class CommandGroup:
"""命令组,用于声明一组有相同名称前缀的命令。"""
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
"""
:参数:
* ``cmd: Union[str, Tuple[str, ...]]``: 命令前缀
* ``**kwargs``: 其他传递给 ``on_command`` 的参数默认值,参考 `on_command <#on-command-cmd-rule-none-aliases-none-kwargs>`_
"""
self.basecmd: Tuple[str, ...] = (cmd,) if isinstance(cmd, str) else cmd
"""
- **类型**: ``Tuple[str, ...]``
- **说明**: 命令前缀
"""
if "aliases" in kwargs:
del kwargs["aliases"]
self.base_kwargs: Dict[str, Any] = kwargs
"""
- **类型**: ``Dict[str, Any]``
- **说明**: 其他传递给 ``on_command`` 的参数默认值
"""
def command(self, cmd: Union[str, Tuple[str, ...]],
**kwargs) -> Type[Matcher]:
"""
:说明:
注册一个新的命令。
:参数:
* ``cmd: Union[str, Tuple[str, ...]]``: 命令前缀
* ``**kwargs``: 其他传递给 ``on_command`` 的参数,将会覆盖命令组默认值
:返回:
- ``Type[Matcher]``
"""
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)
def load_plugin(module_path: str) -> Optional[Plugin]: def load_plugin(module_path: str) -> Optional[Plugin]:
"""
:说明:
使用 ``importlib`` 加载单个插件,可以是本地插件或是通过 ``pip`` 安装的插件。
:参数:
* ``module_path: str``: 插件名称 ``path.to.your.plugin``
:返回:
- ``Optional[Plugin]``
"""
try: try:
_tmp_matchers.clear() _tmp_matchers.clear()
if module_path in plugins: if module_path in plugins:
@@ -198,6 +411,14 @@ def load_plugin(module_path: str) -> Optional[Plugin]:
def load_plugins(*plugin_dir: str) -> Set[Plugin]: def load_plugins(*plugin_dir: str) -> Set[Plugin]:
"""
:说明:
导入目录下多个插件,以 ``_`` 开头的插件不会被导入!
:参数:
- ``*plugin_dir: str``: 插件路径
:返回:
- ``Set[Plugin]``
"""
loaded_plugins = set() loaded_plugins = set()
for module_info in pkgutil.iter_modules(plugin_dir): for module_info in pkgutil.iter_modules(plugin_dir):
_tmp_matchers.clear() _tmp_matchers.clear()
@@ -205,7 +426,7 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
if name.startswith("_"): if name.startswith("_"):
continue continue
spec = module_info.module_finder.find_spec(name) spec = module_info.module_finder.find_spec(name, None)
if spec.name in plugins: if spec.name in plugins:
continue continue
elif spec.name in sys.modules: elif spec.name in sys.modules:
@@ -228,9 +449,21 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
return loaded_plugins return loaded_plugins
def load_builtin_plugins(): def load_builtin_plugins() -> Optional[Plugin]:
"""
:说明:
导入 NoneBot 内置插件
:返回:
- ``Plugin``
"""
return load_plugin("nonebot.plugins.base") return load_plugin("nonebot.plugins.base")
def get_loaded_plugins() -> Set[Plugin]: def get_loaded_plugins() -> Set[Plugin]:
"""
:说明:
获取当前已导入的插件。
:返回:
- ``Set[Plugin]``
"""
return set(plugins.values()) return set(plugins.values())

173
nonebot/plugin.pyi Normal file
View File

@@ -0,0 +1,173 @@
import re
from nonebot.typing import Rule, Matcher, Handler, Permission, RuleChecker
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(type: str = ...,
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]:
...
def on_keyword(keywords: Set[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_command(cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
*,
permission: Optional[Permission] = ...,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
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: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...

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

@@ -1,13 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
规则 规则
==== ====
每个 ``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 +18,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 +66,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
@@ -182,41 +182,97 @@ def endswith(msg: str) -> Rule:
return Rule(_endswith) return Rule(_endswith)
def keyword(msg: str) -> Rule: def keyword(*keywords: str) -> Rule:
"""
:说明:
匹配消息关键词
:参数:
* ``*keywords: 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
any(keyword in event.plain_text for keyword in keywords))
return Rule(_keyword) return Rule(_keyword)
def command(command: Tuple[str, ...]) -> Rule: def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule:
"""
:说明:
命令形式匹配,根据配置里提供的 ``command_start``, ``command_sep`` 判断消息是否为命令。
可以通过 ``state["_prefix"]["command"]`` 获取匹配成功的命令(例:``("test",)``),通过 ``state["_prefix"]["raw_command"]`` 获取匹配成功的原始命令文本(例:``"/test"``)。
:参数:
* ``*cmds: Union[str, Tuple[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
if len(command) == 1: commands = list(cmds)
for start in command_start: for index, command in enumerate(commands):
TrieRule.add_prefix(f"{start}{command[0]}", command) if isinstance(command, str):
else: commands[index] = command = (command,)
for start, sep in product(command_start, command_sep):
TrieRule.add_prefix(f"{start}{sep.join(command)}", command) if len(command) == 1:
for start in command_start:
TrieRule.add_prefix(f"{start}{command[0]}", command)
else:
for start, sep in product(command_start, command_sep):
TrieRule.add_prefix(f"{start}{sep.join(command)}", command)
async def _command(bot: Bot, event: Event, state: dict) -> bool: async def _command(bot: Bot, event: Event, state: dict) -> bool:
return command == state["_prefix"]["command"] return state["_prefix"]["command"] in commands
return Rule(_command) return Rule(_command)
def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule: def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
"""
:说明:
根据正则表达式进行匹配。
可以通过 ``state["_matched"]`` 获取正则表达式匹配成功的文本。
:参数:
* ``regex: str``: 正则表达式
* ``flags: Union[int, re.RegexFlag]``: 正则标志
\:\:\:tip 提示
正则表达式匹配使用 search 而非 match如需从头匹配请使用 ``r"^xxx"`` 来确保匹配开头
\:\:\:
"""
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:
return bool(pattern.search(str(event.message))) matched = pattern.search(str(event.message))
if matched:
state["_matched"] = matched.group()
return True
else:
state["_matched"] = None
return False
return Rule(_regex) return Rule(_regex)
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

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
计划任务 计划任务
======== ========

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" """
类型 类型
==== ====
@@ -28,10 +26,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 +110,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,4 @@
#!/usr/bin/env python3 import re
# -*- coding: utf-8 -*-
import json import json
import asyncio import asyncio
import dataclasses import dataclasses
@@ -9,6 +7,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]]:
""" """
:说明: :说明:

1695
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,14 +20,16 @@
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@vuepress/plugin-back-to-top": "^1.5.4", "@vuepress/plugin-back-to-top": "^1.7.1",
"@vuepress/plugin-medium-zoom": "^1.5.4", "@vuepress/plugin-medium-zoom": "^1.7.1",
"vuepress": "^1.5.4", "@vuepress/plugin-pwa": "^1.7.1",
"vuepress-plugin-versioning": "^4.5.0", "vuepress": "^1.7.1",
"vuepress-theme-titanium": "^4.5.1" "vuepress-plugin-versioning": "git+https://github.com/nonebot/vuepress-plugin-versioning.git",
"vuepress-theme-nonebot": "git+https://github.com/nonebot/vuepress-theme-nonebot.git"
}, },
"dependencies": { "dependencies": {
"vuetify": "^2.3.10", "copy-to-clipboard": "^3.3.1",
"vuetify": "^2.3.16",
"wowjs": "^1.1.3" "wowjs": "^1.1.3"
} }
} }

6
pages/plugin-store.md Normal file
View File

@@ -0,0 +1,6 @@
---
---
# 插件广场
<Plugins></Plugins>

1665
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.0a4"
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"
@@ -24,15 +24,15 @@ include = ["nonebot/py.typed"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.7" python = "^3.7"
httpx = "^0.13.3" httpx = "^0.16.1"
loguru = "^0.5.1" loguru = "^0.5.1"
pygtrie = "^2.3.3" pygtrie = "^2.3.3"
fastapi = "^0.58.1" 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.2.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

@@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os import os
import sys import sys

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)

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